From c4fcb03ee3e48e89366939be96725969b8b0eb54 Mon Sep 17 00:00:00 2001 From: Hunter Johnston <64506580+huntabyte@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:57:44 -0400 Subject: [PATCH] next: `AlertDialog` action changes & Dialog + form docs (#718) --- .changeset/famous-suits-wash.md | 5 +++ .../components/alert-dialog-action.svelte | 7 ++- .../src/lib/bits/dialog/dialog.svelte.ts | 37 ++++++++++++++++ sites/docs/content/components/alert-dialog.md | 44 +++++++++++++++++++ sites/docs/content/components/dialog.md | 43 ++++++++++++++++++ .../content/api-reference/alert-dialog.api.ts | 3 +- 6 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 .changeset/famous-suits-wash.md diff --git a/.changeset/famous-suits-wash.md b/.changeset/famous-suits-wash.md new file mode 100644 index 000000000..29096a933 --- /dev/null +++ b/.changeset/famous-suits-wash.md @@ -0,0 +1,5 @@ +--- +"bits-ui": patch +--- + +Alert Dialog: `Action` button no longer closes the dialog to accomodate form submission / other asynchronous action diff --git a/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte b/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte index 68f430448..439e28466 100644 --- a/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte +++ b/packages/bits-ui/src/lib/bits/alert-dialog/components/alert-dialog-action.svelte @@ -3,7 +3,7 @@ import type { ActionProps } from "../index.js"; import { mergeProps } from "$lib/internal/mergeProps.js"; import { useId } from "$lib/internal/useId.js"; - import { useDialogClose } from "$lib/bits/dialog/dialog.svelte.js"; + import { useAlertDialogAction } from "$lib/bits/dialog/dialog.svelte.js"; let { children, @@ -13,8 +13,7 @@ ...restProps }: ActionProps = $props(); - const closeState = useDialogClose({ - variant: box.with(() => "action"), + const actionState = useAlertDialogAction({ id: box.with(() => id), ref: box.with( () => ref, @@ -22,7 +21,7 @@ ), }); - const mergedProps = $derived(mergeProps(restProps, closeState.props)); + const mergedProps = $derived(mergeProps(restProps, actionState.props)); {#if child} diff --git a/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts b/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts index 94df325a2..c57fa754d 100644 --- a/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts +++ b/packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts @@ -83,6 +83,10 @@ class DialogRootState { return new AlertDialogCancelState(props, this); } + createAction(props: DialogActionStateProps) { + return new DialogActionState(props, this); + } + sharedProps = $derived.by( () => ({ @@ -170,6 +174,35 @@ class DialogCloseState { ); } +type DialogActionStateProps = WithRefProps; + +class DialogActionState { + #id: DialogActionStateProps["id"]; + #ref: DialogActionStateProps["ref"]; + #root: DialogRootState; + #attr = $derived.by(() => this.#root.attrs.action); + + constructor(props: DialogActionStateProps, root: DialogRootState) { + this.#id = props.id; + this.#ref = props.ref; + this.#root = root; + + useRefById({ + id: this.#id, + ref: this.#ref, + }); + } + + props = $derived.by( + () => + ({ + id: this.#id.current, + [this.#attr]: "", + ...this.#root.sharedProps, + }) as const + ); +} + type DialogTitleStateProps = WithRefProps< ReadableBoxedValues<{ level: 1 | 2 | 3 | 4 | 5 | 6; @@ -382,3 +415,7 @@ export function useDialogClose(props: DialogCloseStateProps) { export function useAlertDialogCancel(props: AlertDialogCancelStateProps) { return getDialogRootContext().createCancel(props); } + +export function useAlertDialogAction(props: DialogActionStateProps) { + return getDialogRootContext().createAction(props); +} diff --git a/sites/docs/content/components/alert-dialog.md b/sites/docs/content/components/alert-dialog.md index 98b06a25a..b202a0a01 100644 --- a/sites/docs/content/components/alert-dialog.md +++ b/sites/docs/content/components/alert-dialog.md @@ -418,4 +418,48 @@ Dialogs can be nested within each other to create more complex layouts. See the See the [Dialog](/docs/components/dialog) component for more information on Svelte Transitions with dialog components. +## Working with Forms + +### Form Submission + +When using the `AlertDialog` component, often you'll want to submit a form or perform an asynchronous action when the user clicks the `Action` button. + +This can be done by waiting for the asynchronous action to complete, then programmatically closing the dialog. + +```svelte + + + + + + + Confirm your action + Are you sure you want to do this? +
{ + wait(1000).then(() => (open = false)); + }} + > + No, cancel (close dialog) + Yes (submit form) +
+
+
+
+``` + +### Inside a Form + +If you're using an `AlertDialog` _within_ a form, you'll need to ensure that the `Portal` is disabled or not included in the `AlertDialog` structure. This is because the `Portal` will render the dialog content _outside_ of the form, which will prevent the form from being submitted correctly. + diff --git a/sites/docs/content/components/dialog.md b/sites/docs/content/components/dialog.md index 38a929843..76fc90409 100644 --- a/sites/docs/content/components/dialog.md +++ b/sites/docs/content/components/dialog.md @@ -578,4 +578,47 @@ You can then use the `MyDialogOverlay` component alongside the other `Dialog` pr ``` +## Working with Forms + +### Form Submission + +When using the `Dialog` component, often you'll want to submit a form or perform an asynchronous action and then close the dialog. + +This can be done by waiting for the asynchronous action to complete, then programmatically closing the dialog. + +```svelte + + + + + + + Confirm your action + Are you sure you want to do this? +
{ + wait(1000).then(() => (open = false)); + }} + > +