Skip to content

Commit

Permalink
next: AlertDialog action changes & Dialog + form docs (#718)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Oct 3, 2024
1 parent 0a8d06c commit c4fcb03
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/famous-suits-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"bits-ui": patch
---

Alert Dialog: `Action` button no longer closes the dialog to accomodate form submission / other asynchronous action
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,16 +13,15 @@
...restProps
}: ActionProps = $props();
const closeState = useDialogClose({
variant: box.with(() => "action"),
const actionState = useAlertDialogAction({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, closeState.props));
const mergedProps = $derived(mergeProps(restProps, actionState.props));
</script>

{#if child}
Expand Down
37 changes: 37 additions & 0 deletions packages/bits-ui/src/lib/bits/dialog/dialog.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ class DialogRootState {
return new AlertDialogCancelState(props, this);
}

createAction(props: DialogActionStateProps) {
return new DialogActionState(props, this);
}

sharedProps = $derived.by(
() =>
({
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
44 changes: 44 additions & 0 deletions sites/docs/content/components/alert-dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<script lang="ts">
import { AlertDialog } from "bits-ui";
function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
let open = $state(false);
</script>
<AlertDialog.Root bind:open>
<AlertDialog.Portal>
<AlertDialog.Overlay />
<AlertDialog.Content>
<AlertDialog.Title>Confirm your action</AlertDialog.Title>
<AlertDialog.Description>Are you sure you want to do this?</AlertDialog.Description>
<form
method="POST"
action="?/someAction"
onsubmit={() => {
wait(1000).then(() => (open = false));
}}
>
<AlertDialog.Cancel type="button">No, cancel (close dialog)</AlertDialog.Cancel>
<AlertDialog.Action type="submit">Yes (submit form)</AlertDialog.Action>
</form>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog.Root>
```

### 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.

<APISection {schemas} />
43 changes: 43 additions & 0 deletions sites/docs/content/components/dialog.md
Original file line number Diff line number Diff line change
Expand Up @@ -578,4 +578,47 @@ You can then use the `MyDialogOverlay` component alongside the other `Dialog` pr
</Dialog.Root>
```

## 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
<script lang="ts">
import { Dialog } from "bits-ui";
function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
let open = $state(false);
</script>
<Dialog.Root bind:open>
<Dialog.Portal>
<Dialog.Overlay />
<Dialog.Content>
<Dialog.Title>Confirm your action</Dialog.Title>
<Dialog.Description>Are you sure you want to do this?</Dialog.Description>
<form
method="POST"
action="?/someAction"
onsubmit={() => {
wait(1000).then(() => (open = false));
}}
>
<button type="submit">Submit form</Dialog.Action>
</form>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
```

### Inside a Form

If you're using a `Dialog` _within_ a form, you'll need to ensure that the `Portal` is disabled or not included in the `Dialog` structure. This is because the `Portal` will render the dialog content _outside_ of the form, which will prevent the form from being submitted correctly.

<APISection {schemas} />
3 changes: 2 additions & 1 deletion sites/docs/src/lib/content/api-reference/alert-dialog.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ const root = createApiSchema<AlertDialogRootPropsWithoutHTML>({

const action = createApiSchema<AlertDialogActionPropsWithoutHTML>({
title: "Action",
description: "A button used to close the alert dialog by taking an action.",
description:
"The button responsible for taking an action within the alert dialog. This button does not close the dialog out of the box. See the [Form Submission](#form-submission) documentation for more information.",
props: withChildProps({ elType: "HTMLButtonElement" }),
dataAttributes: [
createDataAttrSchema({
Expand Down

0 comments on commit c4fcb03

Please sign in to comment.