add documentation for dialog component

Signed-off-by: Sofia Sjöblad <ssjoblad@spotify.com>
This commit is contained in:
Sofia Sjöblad
2025-10-09 16:48:14 +02:00
parent 68036b78e8
commit c9778ca53e
6 changed files with 338 additions and 48 deletions
+101
View File
@@ -0,0 +1,101 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import {DialogSnippet} from '@/snippets/stories-snippets';
import {
dialogPropDefs,
dialogTriggerPropDefs,
dialogHeaderPropDefs,
dialogBodyPropDefs,
dialogFooterPropDefs,
dialogClosePropDefs,
dialogUsageSnippet,
dialogDefaultSnippet,
dialogScrollableSnippet,
dialogWithFormSnippet,
} from './dialog.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
<PageTitle
title="Dialog"
description="A modal dialog component that displays content in an overlay window."
/>
<Snippet
align="center"
py={4}
preview={<DialogSnippet story="Default" />}
code={dialogDefaultSnippet}
/>
## Usage
<CodeBlock code={dialogUsageSnippet} />
## API reference
### Dialog
The main dialog container that renders as a modal overlay.
<PropsTable data={dialogPropDefs} />
### DialogTrigger
Wraps a trigger element and the dialog content to handle open/close state.
<PropsTable data={dialogTriggerPropDefs} />
### DialogHeader
Displays the dialog title with a built-in close button.
<PropsTable data={dialogHeaderPropDefs} />
### DialogBody
The main content area of the dialog with optional scrolling.
<PropsTable data={dialogBodyPropDefs} />
### DialogFooter
Contains action buttons or other footer content.
<PropsTable data={dialogFooterPropDefs} />
### DialogClose
A button that closes the dialog when pressed.
<PropsTable data={dialogClosePropDefs} />
## Examples
### Scrollable Content
Dialog with a fixed height body that scrolls when content overflows.
<Snippet
align="center"
py={4}
preview={<DialogSnippet story="Scrollable" />}
code={dialogScrollableSnippet}
/>
### Dialog with Form
Dialog containing form elements for user input.
<Snippet
align="center"
py={4}
preview={<DialogSnippet story="WithForm" />}
code={dialogWithFormSnippet}
/>
<Theming component="Dialog" />
<ChangelogComponent component="dialog" />
@@ -0,0 +1,139 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const dialogPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
...classNamePropDefs,
...stylePropDefs,
};
export const dialogTriggerPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
};
export const dialogHeaderPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
...classNamePropDefs,
...stylePropDefs,
};
export const dialogBodyPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
height: {
type: 'enum',
values: ['number', 'string'],
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
export const dialogFooterPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
...classNamePropDefs,
...stylePropDefs,
};
export const dialogClosePropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary', 'tertiary'],
default: 'secondary',
responsive: false,
},
children: { type: 'enum', values: ['ReactNode'], responsive: false },
...classNamePropDefs,
...stylePropDefs,
};
export const dialogUsageSnippet = `import {
Dialog,
DialogTrigger,
DialogHeader,
DialogBody,
DialogFooter,
DialogClose
} from '@backstage/ui';
<DialogTrigger>
<Button>Open Dialog</Button>
<Dialog>
<DialogHeader>Title</DialogHeader>
<DialogBody>Content</DialogBody>
<DialogFooter>
<DialogClose>Close</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
export const dialogDefaultSnippet = `<DialogTrigger>
<Button variant="secondary">Open Dialog</Button>
<Dialog>
<DialogHeader>Example Dialog</DialogHeader>
<DialogBody>
<Text>This is a basic dialog example with React Aria Components.</Text>
</DialogBody>
<DialogFooter>
<DialogClose>Close</DialogClose>
<DialogClose variant="primary">Save</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
export const dialogScrollableSnippet = `<DialogTrigger>
<Button variant="secondary">Scrollable Dialog</Button>
<Dialog>
<DialogHeader>Long Content Dialog</DialogHeader>
<DialogBody height={200}>
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim
veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
commodo consequat.
</Text>
<Text>
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</Text>
<Text>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium
doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore
veritatis et quasi architecto beatae vitae dicta sunt explicabo.
</Text>
</DialogBody>
<DialogFooter>
<Flex gap="2" justify="end">
<DialogClose>Cancel</DialogClose>
<DialogClose variant="primary">Accept</DialogClose>
</Flex>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
export const dialogWithFormSnippet = `<DialogTrigger>
<Button variant="secondary">Create User</Button>
<Dialog>
<DialogHeader>Create New User</DialogHeader>
<DialogBody>
<Flex direction="column" gap="3">
<TextField label="Name" placeholder="Enter full name" />
<TextField label="Email" placeholder="Enter email address" />
<Select label="Role">
<SelectItem>Admin</SelectItem>
<SelectItem>User</SelectItem>
<SelectItem>Viewer</SelectItem>
</Select>
</Flex>
</DialogBody>
<DialogFooter>
<Flex gap="2" justify="end">
<DialogClose>Cancel</DialogClose>
<DialogClose variant="primary">Create User</DialogClose>
</Flex>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
@@ -17,6 +17,7 @@ import * as MenuStories from '../../../packages/ui/src/components/Menu/Menu.stor
import * as LinkStories from '../../../packages/ui/src/components/Link/Link.stories';
import * as AvatarStories from '../../../packages/ui/src/components/Avatar/Avatar.stories';
import * as CollapsibleStories from '../../../packages/ui/src/components/Collapsible/Collapsible.stories';
import * as DialogStories from '../../../packages/ui/src/components/Dialog/Dialog.stories';
import * as RadioGroupStories from '../../../packages/ui/src/components/RadioGroup/RadioGroup.stories';
import * as TabsStories from '../../../packages/ui/src/components/Tabs/Tabs.stories';
import * as SwitchStories from '../../../packages/ui/src/components/Switch/Switch.stories';
@@ -60,6 +61,7 @@ export const MenuSnippet = createSnippetComponent(MenuStories);
export const LinkSnippet = createSnippetComponent(LinkStories);
export const AvatarSnippet = createSnippetComponent(AvatarStories);
export const CollapsibleSnippet = createSnippetComponent(CollapsibleStories);
export const DialogSnippet = createSnippetComponent(DialogStories);
export const RadioGroupSnippet = createSnippetComponent(RadioGroupStories);
export const TabsSnippet = createSnippetComponent(TabsStories);
export const SwitchSnippet = createSnippetComponent(SwitchStories);
+5
View File
@@ -101,6 +101,11 @@ export const components: Page[] = [
slug: 'collapsible',
status: 'alpha',
},
{
title: 'Dialog',
slug: 'dialog',
status: 'alpha',
},
{
title: 'Header',
slug: 'header',
@@ -22,7 +22,7 @@ import {
DialogFooter,
DialogClose,
} from './Dialog';
import { Button, Flex, Text } from '@backstage/ui';
import { Button, Flex, Text, TextField, Select } from '@backstage/ui';
const meta = {
title: 'Components/Dialog',
@@ -33,64 +33,106 @@ export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: (
<>
<DialogHeader>Hello</DialogHeader>
render: () => (
<DialogTrigger>
<Button variant="secondary">Open Dialog</Button>
<Dialog>
<DialogHeader>Example Dialog</DialogHeader>
<DialogBody>
<Text>Hello world</Text>
<Text>
This is a basic dialog example with React Aria Components.
</Text>
</DialogBody>
<DialogFooter>
<Flex gap="2" align="center" justify="end">
<DialogClose>Close</DialogClose>
<DialogClose variant="primary" onPress={() => console.log('Save')}>
Save
</DialogClose>
</Flex>
<DialogClose>Close</DialogClose>
<DialogClose variant="primary">Save</DialogClose>
</DialogFooter>
</>
),
},
render: args => (
<DialogTrigger>
<Button size="small" variant="secondary">
Open Dialog
</Button>
<Dialog {...args} />
</Dialog>
</DialogTrigger>
),
};
export const CustomBodyMaxHeight: Story = {
args: {
children: (
<>
<DialogHeader> Hello</DialogHeader>
<DialogBody height={400}>
{Array.from({ length: 40 }).map((_, index) => (
<Text key={index}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam,
quos.
</Text>
))}
export const Scrollable: Story = {
render: () => (
<DialogTrigger>
<Button variant="secondary">Scrollable Dialog</Button>
<Dialog>
<DialogHeader>Long Content Dialog</DialogHeader>
<DialogBody height={200}>
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.
</Text>
<Text>
Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
cupidatat non proident, sunt in culpa qui officia deserunt mollit
anim id est laborum.
</Text>
<Text>
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
quae ab illo inventore veritatis et quasi architecto beatae vitae
dicta sunt explicabo.
</Text>
</DialogBody>
<DialogFooter>
<Flex gap="2" align="center" justify="end">
<DialogClose>Close</DialogClose>
<DialogClose variant="primary" onPress={() => console.log('Save')}>
Save
</DialogClose>
</Flex>
<DialogClose>Cancel</DialogClose>
<DialogClose variant="primary">Accept</DialogClose>
</DialogFooter>
</>
),
},
render: args => (
<DialogTrigger>
<Button size="small" variant="secondary">
Open Dialog
</Button>
<Dialog {...args} />
</Dialog>
</DialogTrigger>
),
};
export const Confirmation: Story = {
render: () => (
<DialogTrigger>
<Button variant="secondary">Delete Item</Button>
<Dialog>
<DialogHeader>Confirm Delete</DialogHeader>
<DialogBody>
<Text>
Are you sure you want to delete this item? This action cannot be
undone.
</Text>
</DialogBody>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<DialogClose variant="primary">Delete</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>
),
};
export const WithForm: Story = {
render: () => (
<DialogTrigger>
<Button variant="secondary">Create User</Button>
<Dialog>
<DialogHeader>Create New User</DialogHeader>
<DialogBody>
<Flex direction="column" gap="3">
<TextField label="Name" placeholder="Enter full name" />
<TextField label="Email" placeholder="Enter email address" />
<Select
label="Role"
options={[
{ value: 'admin', label: 'Admin' },
{ value: 'user', label: 'User' },
{ value: 'viewer', label: 'Viewer' },
]}
/>
</Flex>
</DialogBody>
<DialogFooter>
<DialogClose>Cancel</DialogClose>
<DialogClose variant="primary">Create User</DialogClose>
</DialogFooter>
</Dialog>
</DialogTrigger>
),
};
@@ -9,6 +9,7 @@
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.bui-Dialog[data-entering] {