-
Notifications
You must be signed in to change notification settings - Fork 13
feat: add AlertDialog component #699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| 'use client'; | ||
|
|
||
| import { AlertDialog, Button, Flex } from '@raystack/apsara'; | ||
| import PlaygroundLayout from './playground-layout'; | ||
|
|
||
| export function AlertDialogExamples() { | ||
| return ( | ||
| <PlaygroundLayout title='AlertDialog'> | ||
| <Flex gap='medium'> | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button color='danger' />}> | ||
| Delete Item | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width='400px'> | ||
| <AlertDialog.Header> | ||
| <AlertDialog.Title>Are you sure?</AlertDialog.Title> | ||
| </AlertDialog.Header> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Description> | ||
| This action cannot be undone. This will permanently delete the | ||
| item from our servers. | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close | ||
| render={ | ||
| <Button variant='outline' color='neutral'> | ||
| Cancel | ||
| </Button> | ||
| } | ||
| /> | ||
| <Button color='danger'>Delete</Button> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button variant='outline' />}> | ||
| Discard Changes | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={400} showCloseButton={false}> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Title>Unsaved Changes</AlertDialog.Title> | ||
| <AlertDialog.Description> | ||
| You have unsaved changes. Do you want to discard them? | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close | ||
| render={ | ||
| <Button variant='outline' color='neutral'> | ||
| Continue Editing | ||
| </Button> | ||
| } | ||
| /> | ||
| <Button color='danger'>Discard</Button> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| </Flex> | ||
| </PlaygroundLayout> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| 'use client'; | ||
|
|
||
| export const getCode = (props: { title?: string; description?: string }) => { | ||
| const { title, description } = props; | ||
| return ` | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button color="danger" />}> | ||
| Discard draft | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={400} showCloseButton={false}> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Title>${title}</AlertDialog.Title> | ||
| <AlertDialog.Description> | ||
| ${description} | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} /> | ||
| <AlertDialog.Close render={<Button color="danger">Discard</Button>} /> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog>`; | ||
| }; | ||
|
|
||
| export const playground = { | ||
| type: 'playground', | ||
| controls: { | ||
| title: { type: 'text', initialValue: 'Discard draft?' }, | ||
| description: { | ||
| type: 'text', | ||
| initialValue: "You can't undo this action." | ||
| } | ||
| }, | ||
| getCode | ||
| }; | ||
|
|
||
| export const controlledDemo = { | ||
| type: 'code', | ||
| code: ` | ||
| function ControlledAlertDialog() { | ||
| const [open, setOpen] = React.useState(false); | ||
|
|
||
| return ( | ||
| <AlertDialog open={open} onOpenChange={setOpen}> | ||
| <AlertDialog.Trigger render={<Button color="danger" />}> | ||
| Delete Account | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={450} showCloseButton={false}> | ||
| <AlertDialog.Header> | ||
| <AlertDialog.Title>Delete Account</AlertDialog.Title> | ||
| </AlertDialog.Header> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Description> | ||
| Are you sure you want to delete your account? All of your data | ||
| will be permanently removed. This action cannot be undone. | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} /> | ||
| <Button color="danger" onClick={() => setOpen(false)}>Yes, delete account</Button> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| ); | ||
| }` | ||
| }; | ||
|
|
||
| export const menuDemo = { | ||
| type: 'code', | ||
| code: ` | ||
| function MenuWithAlertDialog() { | ||
| const [dialogOpen, setDialogOpen] = React.useState(false); | ||
|
|
||
| return ( | ||
| <React.Fragment> | ||
| <Menu> | ||
| <Menu.Trigger render={<Button variant="outline" />}> | ||
| Actions | ||
| </Menu.Trigger> | ||
| <Menu.Content> | ||
| <Menu.Item>Edit</Menu.Item> | ||
| <Menu.Item>Duplicate</Menu.Item> | ||
| <Menu.Separator /> | ||
| <Menu.Item onClick={() => setDialogOpen(true)}> | ||
| Delete | ||
| </Menu.Item> | ||
| </Menu.Content> | ||
| </Menu> | ||
|
|
||
| <AlertDialog open={dialogOpen} onOpenChange={setDialogOpen}> | ||
| <AlertDialog.Content width={400} showCloseButton={false}> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Title>Delete item?</AlertDialog.Title> | ||
| <AlertDialog.Description> | ||
| This will permanently delete the item. You can't undo this action. | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} /> | ||
| <Button color="danger" onClick={() => setDialogOpen(false)}>Delete</Button> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| </React.Fragment> | ||
| ); | ||
| }` | ||
| }; | ||
|
|
||
| export const discardDemo = { | ||
| type: 'code', | ||
| code: ` | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button variant="outline" />}> | ||
| Discard Changes | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={400} showCloseButton={false}> | ||
| <AlertDialog.Header> | ||
| <AlertDialog.Title>Unsaved Changes</AlertDialog.Title> | ||
| </AlertDialog.Header> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Description> | ||
| You have unsaved changes. Do you want to discard them or continue editing? | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Continue Editing</Button>} /> | ||
| <AlertDialog.Close render={<Button color="danger">Discard</Button>} /> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog>` | ||
| }; | ||
|
|
||
| export const nestedDemo = { | ||
| type: 'code', | ||
| code: ` | ||
| function NestedAlertDialogExample() { | ||
| return ( | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button color="danger" />}> | ||
| Delete Workspace | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={450}> | ||
| <AlertDialog.Header> | ||
| <AlertDialog.Title>Delete Workspace</AlertDialog.Title> | ||
| </AlertDialog.Header> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Description> | ||
| This will delete the workspace and all its projects. Are you sure? | ||
| </AlertDialog.Description> | ||
| <AlertDialog> | ||
| <AlertDialog.Trigger render={<Button color="danger" size="small" />}> | ||
| Confirm Delete | ||
| </AlertDialog.Trigger> | ||
| <AlertDialog.Content width={400} showCloseButton={false}> | ||
| <AlertDialog.Body> | ||
| <AlertDialog.Title>Final Confirmation</AlertDialog.Title> | ||
| <AlertDialog.Description> | ||
| This is your last chance. This action is permanent and cannot be reversed. | ||
| </AlertDialog.Description> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Go Back</Button>} /> | ||
| <AlertDialog.Close render={<Button color="danger">Delete Everything</Button>} /> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| </AlertDialog.Body> | ||
| <AlertDialog.Footer> | ||
| <AlertDialog.Close render={<Button variant="outline" color="neutral">Cancel</Button>} /> | ||
| </AlertDialog.Footer> | ||
| </AlertDialog.Content> | ||
| </AlertDialog> | ||
| ); | ||
| }` | ||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,123 @@ | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
| title: AlertDialog | ||||||||||||||||||||||
| description: A modal dialog that interrupts the user with important content and expects a response. Unlike Dialog, it does not close on outside click, requiring explicit user action. | ||||||||||||||||||||||
| source: packages/raystack/components/alert-dialog | ||||||||||||||||||||||
| tag: new | ||||||||||||||||||||||
| --- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import { playground, controlledDemo, menuDemo, discardDemo, nestedDemo } from "./demo.ts"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <Demo data={playground} /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Anatomy | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Import and assemble the component: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```tsx | ||||||||||||||||||||||
| import { AlertDialog } from '@raystack/apsara' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <AlertDialog> | ||||||||||||||||||||||
| <AlertDialog.Trigger /> | ||||||||||||||||||||||
| <AlertDialog.Content> | ||||||||||||||||||||||
| <AlertDialog.Header> | ||||||||||||||||||||||
| <AlertDialog.Title /> | ||||||||||||||||||||||
| </AlertDialog.Header> | ||||||||||||||||||||||
| <AlertDialog.Body> | ||||||||||||||||||||||
| <AlertDialog.Description /> | ||||||||||||||||||||||
| </AlertDialog.Body> | ||||||||||||||||||||||
| <AlertDialog.Footer> | ||||||||||||||||||||||
| <AlertDialog.Close /> | ||||||||||||||||||||||
| </AlertDialog.Footer> | ||||||||||||||||||||||
| </AlertDialog.Content> | ||||||||||||||||||||||
| </AlertDialog> | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## API Reference | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Root | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Groups all parts of the alert dialog. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Content | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the alert dialog panel overlay. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogContentProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Header | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the alert dialog header section. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogHeaderProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Title | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the alert dialog title text. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogTitleProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Body | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the main content area of the alert dialog. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogBodyProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Description | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders supplementary text within the alert dialog body. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogDescriptionProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Trigger | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the element that opens the alert dialog. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogTriggerProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### CloseButton | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders a button that closes the alert dialog. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogCloseButtonProps" /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Footer | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Renders the alert dialog footer section. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <auto-type-table path="./props.ts" name="AlertDialogFooterProps" /> | ||||||||||||||||||||||
|
Comment on lines
+73
to
+89
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The anatomy block and 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Examples | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Controlled | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Example of a controlled alert dialog with custom state management. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <Demo data={controlledDemo} /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Composing with Menu | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Open an alert dialog from a menu item. Since the trigger is outside the `AlertDialog.Root`, use the controlled `open` / `onOpenChange` props. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <Demo data={menuDemo} /> | ||||||||||||||||||||||
|
Comment on lines
+99
to
+103
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the exported root name in this note. The public API is 📝 Suggested wording-Open an alert dialog from a menu item. Since the trigger is outside the `AlertDialog.Root`, use the controlled `open` / `onOpenChange` props.
+Open an alert dialog from a menu item. Since the trigger is outside `AlertDialog`, use the controlled `open` / `onOpenChange` props.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Discard Changes | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A common pattern for confirming destructive navigation. Both actions use `AlertDialog.Close` to dismiss the dialog. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <Demo data={discardDemo} /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### Nested Alert Dialogs | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| You can nest alert dialogs for multi-step confirmation flows. When a nested alert dialog opens, the parent dialog automatically scales down and becomes slightly transparent. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| <Demo data={nestedDemo} /> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Accessibility | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - Alert dialog has `role="alertdialog"` and `aria-modal="true"` | ||||||||||||||||||||||
| - Does not close on outside click (backdrop), requiring explicit user action | ||||||||||||||||||||||
| - Uses `aria-label` or `aria-labelledby` to identify the dialog | ||||||||||||||||||||||
| - Uses `aria-describedby` to provide additional context | ||||||||||||||||||||||
| - Focus is trapped within the alert dialog while open | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Function signature mismatch with
GetCodeTypeinterface.The
getCodefunction accepts one parameter, but the playground infrastructure calls it with two:getCode(updatedProps, componentProps)(seedemo-playground.tsxlines 66-75). TheGetCodeTypeinterface intypes.tsexpects(updatedProps: Record<string, any>, props: Record<string, any>) => string.Currently,
propsreceivesupdatedProps(only changed values), not the full props object with defaults. If a user doesn't modifytitleordescription, they won't be inupdatedProps, causingundefinedto appear in the rendered code.Proposed fix to match the expected signature
📝 Committable suggestion
🤖 Prompt for AI Agents