Merge branch 'master' into bui-fix-table-sorting

This commit is contained in:
Charles de Dreuille
2025-10-13 10:07:56 +01:00
19 changed files with 1150 additions and 2 deletions
File diff suppressed because one or more lines are too long
+107
View File
@@ -0,0 +1,107 @@
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,
dialogFixedWidthAndHeightSnippet,
dialogWithFormSnippet,
dialogWithNoTriggerSnippet,
dialogCloseSnippet,
} 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
### DialogTrigger
Wraps a trigger element and the dialog content to handle open/close state.
<PropsTable data={dialogTriggerPropDefs} />
### Dialog
The main dialog container that renders as a modal overlay.
<PropsTable data={dialogPropDefs} />
### 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} />
If you want to close the dialog while pressing a button, you can use the `slot="close"` prop on the button.
<CodeBlock code={dialogCloseSnippet} />
## Examples
### Fixed Width and Height
Dialog with a fixed height body that scrolls when content overflows.
<Snippet
align="center"
py={4}
preview={<DialogSnippet story="PreviewFixedWidthAndHeight" />}
code={dialogFixedWidthAndHeightSnippet}
/>
### Dialog with Form
Dialog containing form elements for user input.
<Snippet
align="center"
py={4}
preview={<DialogSnippet story="PreviewWithForm" />}
code={dialogWithFormSnippet}
/>
### Dialog with no trigger and controlled by props
You can also control the dialog using your own states.
<CodeBlock code={dialogWithNoTriggerSnippet} />
<Theming component="Dialog" />
<ChangelogComponent component="dialog" />
@@ -0,0 +1,172 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const dialogTriggerPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
isOpen: {
type: 'boolean',
description: 'Whether the overlay is open by default (controlled).',
},
defaultOpen: {
type: 'boolean',
description: 'Whether the overlay is open by default (uncontrolled).',
},
onOpenChange: {
type: 'enum',
values: ['(isOpen: boolean) => void'],
description:
"Handler that is called when the overlay's open state changes.",
},
};
export const dialogPropDefs: Record<string, PropDef> = {
children: { type: 'enum', values: ['ReactNode'], responsive: false },
isOpen: {
type: 'boolean',
description: 'Whether the overlay is open by default (controlled).',
},
defaultOpen: {
type: 'boolean',
description: 'Whether the overlay is open by default (uncontrolled).',
},
onOpenChange: {
type: 'enum',
values: ['(isOpen: boolean) => void'],
description:
"Handler that is called when the overlay's open state changes.",
},
width: {
type: 'enum',
values: ['number', 'string'],
responsive: false,
},
height: {
type: 'enum',
values: ['number', 'string'],
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
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,
} from '@backstage/ui';
<DialogTrigger>
<Button>Open Dialog</Button>
<Dialog>
<DialogHeader>Title</DialogHeader>
<DialogBody>Content</DialogBody>
<DialogFooter>
<Button variant="secondary" slot="close">Close</Button>
</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.</Text>
</DialogBody>
<DialogFooter>
<Button variant="secondary" slot="close">Close</Button>
<Button variant="primary" slot="close">Save</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
export const dialogFixedWidthAndHeightSnippet = `<DialogTrigger>
<Button variant="secondary">Scrollable Dialog</Button>
<Dialog>
<DialogHeader>Long Content Dialog</DialogHeader>
<DialogBody width={600} height={400}>
...
</DialogBody>
<DialogFooter>
<Button variant="secondary" slot="close">Cancel</Button>
<Button variant="primary" slot="close">Accept</Button>
</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>
<Button variant="secondary" slot="close">Cancel</Button>
<Button variant="primary" slot="close">Create User</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>`;
export const dialogWithNoTriggerSnippet = `const [isOpen, setIsOpen] = useState(false);
<Dialog isOpen={isOpen} onOpenChange={setIsOpen}>
<DialogHeader>Create New User</DialogHeader>
<DialogBody>
Your content
</DialogBody>
<DialogFooter>
<Button variant="secondary" slot="close">Cancel</Button>
<Button variant="primary" slot="close">Create User</Button>
</DialogFooter>
</Dialog>`;
export const dialogCloseSnippet = `<Button slot="close">Close</Button>`;
@@ -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';
@@ -63,6 +64,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',