Add docs for Popover
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories';
|
||||
|
||||
export const Default = () => {
|
||||
const { CustomField } = stories;
|
||||
return <CustomField.Component />;
|
||||
};
|
||||
@@ -13,53 +13,11 @@ import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PasswordFieldDefinition } from '../utils/definitions';
|
||||
import { Default } from './PasswordField';
|
||||
|
||||
<PageTitle
|
||||
title="PasswordField"
|
||||
description="A password field component for your forms."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PasswordFieldSnippet story="WithLabel" />}
|
||||
code={passwordFieldDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={passwordFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={inputPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="Sizes" />}
|
||||
code={passwordFieldSizesSnippet}
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
Here's a simple PasswordField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="WithDescription" />}
|
||||
code={passwordFieldDescriptionSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={PasswordFieldDefinition} />
|
||||
|
||||
<ChangelogComponent component="password-field" />
|
||||
<Default />
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { PopoverSnippet } from '@/snippets/stories-snippets';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
popoverPropDefs,
|
||||
popoverUsageSnippet,
|
||||
popoverDefaultSnippet,
|
||||
popoverPlacementsSnippet,
|
||||
popoverNoArrowSnippet,
|
||||
popoverRichContentSnippet,
|
||||
popoverNonModalSnippet,
|
||||
} from './popover.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { PopoverDefinition } from '../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Popover"
|
||||
description="A popover displays floating content anchored to a trigger element with automatic positioning and collision detection."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="Default" />}
|
||||
code={popoverDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={popoverUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Popover
|
||||
|
||||
The main popover component that displays floating content with automatic positioning.
|
||||
|
||||
<PropsTable data={popoverPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Placements
|
||||
|
||||
Popover supports multiple placement options to position the content relative to the trigger.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="DocsAllPlacements" />}
|
||||
code={popoverPlacementsSnippet}
|
||||
/>
|
||||
|
||||
### Without Arrow
|
||||
|
||||
You can hide the arrow by setting the `hideArrow` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="Default" />}
|
||||
code={popoverNoArrowSnippet}
|
||||
/>
|
||||
|
||||
### Rich Content
|
||||
|
||||
Popovers can contain complex content including buttons, forms, and other interactive elements.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="Default" />}
|
||||
code={popoverRichContentSnippet}
|
||||
/>
|
||||
|
||||
### Non-Modal
|
||||
|
||||
Non-modal popovers allow interaction with other elements on the page while the popover is open.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="Default" />}
|
||||
code={popoverNonModalSnippet}
|
||||
/>
|
||||
|
||||
### Long Content
|
||||
|
||||
Popovers automatically handle scrolling when content exceeds the available viewport space.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PopoverSnippet story="Default" />}
|
||||
code={popoverDefaultSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={PopoverDefinition} />
|
||||
|
||||
<ChangelogComponent component="popover" />
|
||||
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const popoverPropDefs: Record<string, PropDef> = {
|
||||
hideArrow: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether to hide the arrow pointing to the trigger element. Arrow is also automatically hidden for MenuTrigger and SubmenuTrigger contexts.',
|
||||
},
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left',
|
||||
'left start',
|
||||
'left end',
|
||||
'right',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
default: 'bottom',
|
||||
description: 'The placement of the popover relative to the trigger.',
|
||||
},
|
||||
containerPadding: {
|
||||
type: 'number',
|
||||
default: '12',
|
||||
description:
|
||||
'The padding between the popover and the edge of the viewport.',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
description: 'The offset from the trigger element.',
|
||||
},
|
||||
crossOffset: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
description: 'The cross-axis offset from the trigger element.',
|
||||
},
|
||||
shouldFlip: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
description:
|
||||
'Whether the popover should flip to the opposite side when there is not enough space.',
|
||||
},
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the popover is open (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the popover is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the popover's open state changes.",
|
||||
},
|
||||
isNonModal: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether the popover is non-modal. Non-modal popovers do not block interaction with the rest of the page.',
|
||||
},
|
||||
isKeyboardDismissDisabled: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether pressing the escape key to close the popover should be disabled.',
|
||||
},
|
||||
shouldCloseOnBlur: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
description:
|
||||
'Whether to close the popover when the user interacts outside it.',
|
||||
},
|
||||
triggerRef: {
|
||||
type: 'enum',
|
||||
values: ['RefObject<Element | null>'],
|
||||
description: 'A ref for the trigger element.',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const popoverUsageSnippet = `import { Popover, DialogTrigger, Button } from '@backstage/ui';
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Text>This is a popover</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverDefaultSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Text>This is a popover</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverPlacementsSnippet = `<DialogTrigger>
|
||||
<Button>Top</Button>
|
||||
<Popover placement="top">
|
||||
<Text>Top placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Right</Button>
|
||||
<Popover placement="right">
|
||||
<Text>Right placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Bottom</Button>
|
||||
<Popover placement="bottom">
|
||||
<Text>Bottom placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Left</Button>
|
||||
<Popover placement="left">
|
||||
<Text>Left placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverNoArrowSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover hideArrow>
|
||||
<Text>Popover without arrow</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverRichContentSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Flex direction="column" gap="3">
|
||||
<Text style={{ fontWeight: 'bold' }}>Popover Title</Text>
|
||||
<Text>
|
||||
This is a popover with rich content. It can contain multiple
|
||||
elements and formatted text.
|
||||
</Text>
|
||||
<Flex gap="2" justify="end">
|
||||
<Button variant="tertiary" size="small">Cancel</Button>
|
||||
<Button variant="primary" size="small">Confirm</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverNonModalSnippet = `<DialogTrigger>
|
||||
<Button>Open Non-Modal Popover</Button>
|
||||
<Popover isNonModal>
|
||||
<Text>
|
||||
This is a non-modal popover. You can interact with other
|
||||
elements on the page while it's open.
|
||||
</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
@@ -1,6 +1,5 @@
|
||||
'use client';
|
||||
|
||||
import { composeStories } from '@storybook/react';
|
||||
import * as BoxStories from '../../../packages/ui/src/components/Box/Box.stories';
|
||||
import * as ButtonStories from '../../../packages/ui/src/components/Button/Button.stories';
|
||||
import * as ButtonIconStories from '../../../packages/ui/src/components/ButtonIcon/ButtonIcon.stories';
|
||||
@@ -29,6 +28,7 @@ import * as HeaderPageStories from '../../../packages/ui/src/components/HeaderPa
|
||||
import * as TableStories from '../../../packages/ui/src/components/Table/stories/Table.docs.stories';
|
||||
import * as TagGroupStories from '../../../packages/ui/src/components/TagGroup/TagGroup.stories';
|
||||
import * as PasswordFieldStories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories';
|
||||
import * as PopoverStories from '../../../packages/ui/src/components/Popover/Popover.stories';
|
||||
import * as VisuallyHiddenStories from '../../../packages/ui/src/components/VisuallyHidden/VisuallyHidden.stories';
|
||||
import * as ToggleButtonStories from '../../../packages/ui/src/components/ToggleButton/ToggleButton.stories';
|
||||
import * as ToggleButtonGroupStories from '../../../packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories';
|
||||
@@ -37,11 +37,9 @@ import * as ToggleButtonGroupStories from '../../../packages/ui/src/components/T
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const createSnippetComponent = (stories: any) => {
|
||||
return function SnippetComponent({ story }: { story: string }) {
|
||||
const composedStories = composeStories(stories);
|
||||
const StoryComponent = composedStories[
|
||||
story as keyof typeof composedStories
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
] as any;
|
||||
// In CSF3, stories are already React components that can be rendered directly
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const StoryComponent = stories[story as keyof typeof stories] as any;
|
||||
|
||||
return StoryComponent ? <StoryComponent /> : null;
|
||||
};
|
||||
@@ -58,6 +56,7 @@ export const GridSnippet = createSnippetComponent(GridStories);
|
||||
export const TextFieldSnippet = createSnippetComponent(TextFieldStories);
|
||||
export const PasswordFieldSnippet =
|
||||
createSnippetComponent(PasswordFieldStories);
|
||||
export const PopoverSnippet = createSnippetComponent(PopoverStories);
|
||||
export const TextSnippet = createSnippetComponent(TextStories);
|
||||
export const FlexSnippet = createSnippetComponent(FlexStories);
|
||||
export const SelectSnippet = createSnippetComponent(SelectStories);
|
||||
|
||||
@@ -76,6 +76,11 @@ export const components: Page[] = [
|
||||
title: 'PasswordField',
|
||||
slug: 'password-field',
|
||||
},
|
||||
{
|
||||
title: 'Popover',
|
||||
slug: 'popover',
|
||||
status: 'new',
|
||||
},
|
||||
{
|
||||
title: 'RadioGroup',
|
||||
slug: 'radio-group',
|
||||
|
||||
Reference in New Issue
Block a user