diff --git a/docs-ui/src/content/components/dialog.mdx b/docs-ui/src/content/components/dialog.mdx index 5f465ddb0f..4aa43c69de 100644 --- a/docs-ui/src/content/components/dialog.mdx +++ b/docs-ui/src/content/components/dialog.mdx @@ -11,9 +11,10 @@ import { dialogClosePropDefs, dialogUsageSnippet, dialogDefaultSnippet, - dialogScrollableSnippet, + dialogFixedWidthAndHeightSnippet, dialogWithFormSnippet, dialogWithNoTriggerSnippet, + dialogCloseSnippet, } from './dialog.props'; import { PageTitle } from '@/components/PageTitle'; import { Theming } from '@/components/Theming'; @@ -37,18 +38,18 @@ import { ChangelogComponent } from '@/components/ChangelogComponent'; ## API reference -### Dialog - -The main dialog container that renders as a modal overlay. - - - ### DialogTrigger Wraps a trigger element and the dialog content to handle open/close state. +### Dialog + +The main dialog container that renders as a modal overlay. + + + ### DialogHeader Displays the dialog title with a built-in close button. @@ -67,23 +68,21 @@ Contains action buttons or other footer content. -### DialogClose +If you want to close the dialog while pressing a button, you can use the `slot="close"` prop on the button. -A button that closes the dialog when pressed. - - + ## Examples -### Scrollable Content +### Fixed Width and Height Dialog with a fixed height body that scrolls when content overflows. } - code={dialogScrollableSnippet} + preview={} + code={dialogFixedWidthAndHeightSnippet} /> ### Dialog with Form diff --git a/docs-ui/src/content/components/dialog.props.ts b/docs-ui/src/content/components/dialog.props.ts index 8f5d2f6f87..162ec307dc 100644 --- a/docs-ui/src/content/components/dialog.props.ts +++ b/docs-ui/src/content/components/dialog.props.ts @@ -4,14 +4,52 @@ import { type PropDef, } from '@/utils/propDefs'; -export const dialogPropDefs: Record = { - children: { type: 'enum', values: ['ReactNode'], responsive: false }, - ...classNamePropDefs, - ...stylePropDefs, -}; - export const dialogTriggerPropDefs: Record = { 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 = { + 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 = { @@ -55,7 +93,6 @@ export const dialogUsageSnippet = `import { DialogHeader, DialogBody, DialogFooter, - DialogClose } from '@backstage/ui'; @@ -64,7 +101,7 @@ export const dialogUsageSnippet = `import { Title Content - Close + `; @@ -77,39 +114,22 @@ export const dialogDefaultSnippet = ` This is a basic dialog example. - Close - Save + + `; -export const dialogScrollableSnippet = ` +export const dialogFixedWidthAndHeightSnippet = ` Long Content Dialog - - - 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. - - - 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. - - - 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. - + + ... - - Cancel - Accept - + + `; @@ -130,10 +150,8 @@ export const dialogWithFormSnippet = ` - - Cancel - Create User - + + `; @@ -146,9 +164,9 @@ export const dialogWithNoTriggerSnippet = `const [isOpen, setIsOpen] = useState( Your content - - Cancel - Create User - + + `; + +export const dialogCloseSnippet = ``; diff --git a/packages/ui/report.api.md b/packages/ui/report.api.md index e231cfb8e2..1674fef093 100644 --- a/packages/ui/report.api.md +++ b/packages/ui/report.api.md @@ -443,8 +443,8 @@ export const componentDefinitions: { }; readonly Dialog: { readonly classNames: { - readonly root: 'bui-Dialog'; - readonly content: 'bui-DialogContent'; + readonly overlay: 'bui-DialogOverlay'; + readonly dialog: 'bui-Dialog'; readonly header: 'bui-DialogHeader'; readonly headerTitle: 'bui-DialogHeaderTitle'; readonly body: 'bui-DialogBody'; @@ -729,19 +729,6 @@ export interface DialogBodyProps { children?: React.ReactNode; // (undocumented) className?: string; - // (undocumented) - height?: number | string; -} - -// @public (undocumented) -export const DialogClose: ForwardRefExoticComponent< - DialogCloseProps & RefAttributes ->; - -// @public -export interface DialogCloseProps extends Omit { - // (undocumented) - variant?: 'primary' | 'secondary' | 'tertiary'; } // @public (undocumented) @@ -772,6 +759,10 @@ export interface DialogProps extends ModalOverlayProps { children?: React.ReactNode; // (undocumented) className?: string; + // (undocumented) + height?: number | string; + // (undocumented) + width?: number | string; } // @public (undocumented) diff --git a/packages/ui/src/components/Dialog/Dialog.stories.tsx b/packages/ui/src/components/Dialog/Dialog.stories.tsx index f1b89bc5ad..98d2492913 100644 --- a/packages/ui/src/components/Dialog/Dialog.stories.tsx +++ b/packages/ui/src/components/Dialog/Dialog.stories.tsx @@ -20,7 +20,6 @@ import { DialogHeader, DialogBody, DialogFooter, - DialogClose, } from './Dialog'; import { Button, Flex, Text, TextField, Select } from '@backstage/ui'; import { useArgs } from 'storybook/preview-api'; @@ -44,16 +43,20 @@ type Story = StoryObj; export const Default: Story = { render: args => { return ( - + - + Example Dialog This is a basic dialog example. - Close - Save + + @@ -87,52 +90,89 @@ export const NoTrigger: Story = { This is a basic dialog example. - Close - Save + + ); }, }; -export const Scrollable: Story = { +export const FixedWidth: Story = { args: { - isOpen: true, + defaultOpen: true, + width: 600, }, render: args => ( - - - + + + Long Content Dialog - - - 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. - - - 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. - - - 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. - + + + + 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. + + + 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. + + + 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. + + - Cancel - Accept + + ), }; +export const FixedHeight: Story = { + args: { + defaultOpen: true, + height: 500, + }, + render: FixedWidth.render, +}; + +export const FixedWidthAndHeight: Story = { + args: { + defaultOpen: true, + width: 600, + height: 400, + }, + render: FixedWidth.render, +}; + +export const FullWidthAndHeight: Story = { + args: { + defaultOpen: true, + width: '100%', + height: '100%', + }, + render: FixedWidth.render, +}; + export const Confirmation: Story = { args: { isOpen: true, @@ -149,8 +189,12 @@ export const Confirmation: Story = { - Cancel - Delete + + @@ -181,26 +225,30 @@ export const WithForm: Story = { - Cancel - Create User + + ), }; -export const PreviewScrollable: Story = { +export const PreviewFixedWidthAndHeight: Story = { args: { - ...Scrollable.args, - isOpen: false, + defaultOpen: undefined, + width: 600, + height: 400, }, - render: Scrollable.render, + render: FixedWidth.render, }; export const PreviewWithForm: Story = { args: { - ...WithForm.args, - isOpen: false, + defaultOpen: undefined, }, render: WithForm.render, }; diff --git a/packages/ui/src/components/Dialog/Dialog.styles.css b/packages/ui/src/components/Dialog/Dialog.styles.css index 5e7cd7afc9..9a2ffc52ca 100644 --- a/packages/ui/src/components/Dialog/Dialog.styles.css +++ b/packages/ui/src/components/Dialog/Dialog.styles.css @@ -1,5 +1,5 @@ /* Backdrop */ -.bui-Dialog { +.bui-DialogOverlay { position: fixed; top: 0; left: 0; @@ -16,34 +16,36 @@ background: rgba(0, 0, 0, 0.5); } -.bui-Dialog[data-entering] { +.bui-DialogOverlay[data-entering] { animation: fade-in 200ms ease-out forwards; } -.bui-Dialog[data-exiting] { +.bui-DialogOverlay[data-exiting] { animation: fade-out 150ms ease-out forwards; } -.bui-DialogContent { +.bui-Dialog { background: var(--bui-bg-surface-1); border-radius: 0.5rem; border: 1px solid var(--bui-border); color: var(--bui-fg-primary); position: relative; - width: 24rem; + width: min(var(--bui-dialog-min-width, 400px), calc(100vw - 3rem)); max-width: calc(100vw - 3rem); + height: min(var(--bui-dialog-min-height, auto), calc(100vh - 3rem)); + max-height: calc(100vh - 3rem); display: flex; flex-direction: column; outline: none; } /* Dialog entering animation */ -.bui-Dialog[data-entering] .bui-DialogContent { +.bui-DialogOverlay[data-entering] .bui-Dialog { animation: dialog-enter 150ms ease-out forwards; } /* Dialog exiting animation */ -.bui-Dialog[data-exiting] .bui-DialogContent { +.bui-DialogOverlay[data-exiting] .bui-Dialog { animation: dialog-exit 150ms ease-out forwards; } @@ -74,6 +76,8 @@ .bui-DialogBody { padding: var(--bui-space-3); + flex: 1; + overflow-y: auto; } /* Keyframe animations */ diff --git a/packages/ui/src/components/Dialog/Dialog.tsx b/packages/ui/src/components/Dialog/Dialog.tsx index 40c6a5c277..49eeac1508 100644 --- a/packages/ui/src/components/Dialog/Dialog.tsx +++ b/packages/ui/src/components/Dialog/Dialog.tsx @@ -27,11 +27,9 @@ import type { DialogHeaderProps, DialogProps, DialogBodyProps, - DialogCloseProps, } from './types'; import './Dialog.styles.css'; import { RiCloseLine } from '@remixicon/react'; -import { ScrollArea } from '../ScrollArea'; import { Button } from '../Button'; import { useStyles } from '../../hooks/useStyles'; import { Flex } from '../Flex'; @@ -41,6 +39,41 @@ export const DialogTrigger = (props: DialogTriggerProps) => { return ; }; +/** @public */ +export const Dialog = forwardRef, DialogProps>( + ({ className, children, width, height, style, ...props }, ref) => { + const { classNames } = useStyles('Dialog'); + + return ( + + + {children} + + + ); + }, +); + +Dialog.displayName = 'Dialog'; + /** @public */ export const DialogHeader = forwardRef< React.ElementRef<'div'>, @@ -63,27 +96,13 @@ DialogHeader.displayName = 'DialogHeader'; /** @public */ export const DialogBody = forwardRef, DialogBodyProps>( - ({ className, children, height, ...props }, ref) => { + ({ className, children, ...props }, ref) => { const { classNames } = useStyles('Dialog'); return ( - - {children} - - - - +
+ {children} +
); }, ); @@ -104,43 +123,3 @@ export const DialogFooter = forwardRef< ); }); DialogFooter.displayName = 'DialogFooter'; - -/** @public */ -export const DialogClose = forwardRef< - React.ElementRef, - DialogCloseProps ->(({ className, variant = 'secondary', children, ...props }, ref) => { - return ( - - ); -}); -DialogClose.displayName = 'DialogClose'; - -/** @public */ -export const Dialog = forwardRef, DialogProps>( - ({ className, children, ...props }, ref) => { - const { classNames } = useStyles('Dialog'); - - return ( - - {children} - - ); - }, -); - -Dialog.displayName = 'Dialog'; diff --git a/packages/ui/src/components/Dialog/types.ts b/packages/ui/src/components/Dialog/types.ts index b13f496d58..04f9dd4dfc 100644 --- a/packages/ui/src/components/Dialog/types.ts +++ b/packages/ui/src/components/Dialog/types.ts @@ -19,7 +19,6 @@ import type { ModalOverlayProps as RAModalProps, HeadingProps as RAHeadingProps, } from 'react-aria-components'; -import type { ButtonProps } from '../Button'; /** * Props for the DialogTrigger component. @@ -27,6 +26,17 @@ import type { ButtonProps } from '../Button'; */ export interface DialogTriggerProps extends RADialogTriggerProps {} +/** + * Props for the Dialog component. + * @public + */ +export interface DialogProps extends RAModalProps { + className?: string; + children?: React.ReactNode; + width?: number | string; + height?: number | string; +} + /** * Props for the DialogHeader component. * @public @@ -43,22 +53,4 @@ export interface DialogHeaderProps extends RAHeadingProps { export interface DialogBodyProps { children?: React.ReactNode; className?: string; - height?: number | string; -} - -/** - * Props for the DialogClose component. - * @public - */ -export interface DialogCloseProps extends Omit { - variant?: 'primary' | 'secondary' | 'tertiary'; -} - -/** - * Props for the Dialog component. - * @public - */ -export interface DialogProps extends RAModalProps { - className?: string; - children?: React.ReactNode; } diff --git a/packages/ui/src/utils/componentDefinitions.ts b/packages/ui/src/utils/componentDefinitions.ts index 92991db990..5f71a2a21f 100644 --- a/packages/ui/src/utils/componentDefinitions.ts +++ b/packages/ui/src/utils/componentDefinitions.ts @@ -87,8 +87,8 @@ export const componentDefinitions = { }, Dialog: { classNames: { - root: 'bui-Dialog', - content: 'bui-DialogContent', + overlay: 'bui-DialogOverlay', + dialog: 'bui-Dialog', header: 'bui-DialogHeader', headerTitle: 'bui-DialogHeaderTitle', body: 'bui-DialogBody',