From 32e2002f8e2f3d7b9d4ed7a5c71ba0ed87aab768 Mon Sep 17 00:00:00 2001 From: Klaus Zanders Date: Tue, 2 Jun 2026 15:23:22 +0200 Subject: [PATCH] Disable Async Dialog trigger while the dialog is loading --- .../controllers/async-dialog.controller.ts | 16 ++++++++++++++++ lookbook/docs/patterns/05-dialogs.md.erb | 14 ++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/frontend/src/stimulus/controllers/async-dialog.controller.ts b/frontend/src/stimulus/controllers/async-dialog.controller.ts index 6279b34b814..684d96a6d39 100644 --- a/frontend/src/stimulus/controllers/async-dialog.controller.ts +++ b/frontend/src/stimulus/controllers/async-dialog.controller.ts @@ -33,6 +33,12 @@ import { renderStreamMessage } from '@hotwired/turbo'; import { TurboHelpers } from 'core-turbo/helpers'; export default class AsyncDialogController extends ApplicationController { + static values = { disableDuringLoad: { type: Boolean, default: true } }; + + declare disableDuringLoadValue:boolean; + + private loading = false; + connect() { // Only bind events if we have an href to work with if (this.href) { @@ -55,6 +61,12 @@ export default class AsyncDialogController extends ApplicationController { } private triggerTurboStream(url:string):void { + if (this.disableDuringLoadValue && this.loading) return; + + if (this.disableDuringLoadValue) { + this.loading = true; + (this.element as HTMLElement).setAttribute('aria-disabled', 'true'); + } TurboHelpers.showProgressBar(); void fetch(url, { @@ -74,6 +86,10 @@ export default class AsyncDialogController extends ApplicationController { }).then((html) => { renderStreamMessage(html); }).finally(() => { + if (this.disableDuringLoadValue) { + this.loading = false; + (this.element as HTMLElement).removeAttribute('aria-disabled'); + } TurboHelpers.hideProgressBar(); }); } diff --git a/lookbook/docs/patterns/05-dialogs.md.erb b/lookbook/docs/patterns/05-dialogs.md.erb index 05a805a2f46..fbfb170e6d5 100644 --- a/lookbook/docs/patterns/05-dialogs.md.erb +++ b/lookbook/docs/patterns/05-dialogs.md.erb @@ -23,6 +23,20 @@ end %> ``` +By default, the trigger element is disabled (via `aria-disabled`) while the request is in flight, preventing double-clicks from opening the dialog twice. This is especially useful when the dialog content is expensive to render. To opt out of this behaviour, set the `data-async-dialog-disable-during-load-value` attribute to `false`: + +```erb +<%%= render(Primer::Beta::Button.new( + tag: :a, + href: link_to_dialog_path, + data: { controller: "async-dialog", async_dialog_disable_during_load_value: false } +)) do |button| + button.with_leading_visual_icon(icon: :edit) + "Edit something" +end +%> +``` + On the Rails controller you wish to render the dialog, you need to respond to the request with the dialog content. The async-controller stimulus controller will ensure that a loading progress bar will be shown on the top of the page.