diff --git a/docs-ui/src/content/PasswordField.tsx b/docs-ui/src/content/PasswordField.tsx
new file mode 100644
index 0000000000..953ec3515e
--- /dev/null
+++ b/docs-ui/src/content/PasswordField.tsx
@@ -0,0 +1,8 @@
+'use client';
+
+import * as stories from '../../../packages/ui/src/components/PasswordField/PasswordField.stories';
+
+export const Default = () => {
+ const { CustomField } = stories;
+ return ;
+};
diff --git a/docs-ui/src/content/password-field.mdx b/docs-ui/src/content/password-field.mdx
index 8dda2b4f2a..2b6d3c1ca6 100644
--- a/docs-ui/src/content/password-field.mdx
+++ b/docs-ui/src/content/password-field.mdx
@@ -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';
-}
- code={passwordFieldDefaultSnippet}
-/>
-
-## Usage
-
-
-
-## API reference
-
-
-
-## Examples
-
-### Sizes
-
-We support two different sizes: `small`, `medium`.
-
-}
- code={passwordFieldSizesSnippet}
-/>
-
-### With description
-
-Here's a simple PasswordField with a description.
-
-}
- code={passwordFieldDescriptionSnippet}
-/>
-
-
-
-
+
diff --git a/docs-ui/src/content/popover.mdx b/docs-ui/src/content/popover.mdx
new file mode 100644
index 0000000000..ca3f728b01
--- /dev/null
+++ b/docs-ui/src/content/popover.mdx
@@ -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';
+
+
+
+}
+ code={popoverDefaultSnippet}
+/>
+
+## Usage
+
+
+
+## API reference
+
+### Popover
+
+The main popover component that displays floating content with automatic positioning.
+
+
+
+## Examples
+
+### Placements
+
+Popover supports multiple placement options to position the content relative to the trigger.
+
+}
+ code={popoverPlacementsSnippet}
+/>
+
+### Without Arrow
+
+You can hide the arrow by setting the `hideArrow` prop.
+
+}
+ code={popoverNoArrowSnippet}
+/>
+
+### Rich Content
+
+Popovers can contain complex content including buttons, forms, and other interactive elements.
+
+}
+ code={popoverRichContentSnippet}
+/>
+
+### Non-Modal
+
+Non-modal popovers allow interaction with other elements on the page while the popover is open.
+
+}
+ code={popoverNonModalSnippet}
+/>
+
+### Long Content
+
+Popovers automatically handle scrolling when content exceeds the available viewport space.
+
+}
+ code={popoverDefaultSnippet}
+/>
+
+
+
+
diff --git a/docs-ui/src/content/popover.props.ts b/docs-ui/src/content/popover.props.ts
new file mode 100644
index 0000000000..74194c6120
--- /dev/null
+++ b/docs-ui/src/content/popover.props.ts
@@ -0,0 +1,174 @@
+import {
+ childrenPropDefs,
+ classNamePropDefs,
+ stylePropDefs,
+ type PropDef,
+} from '@/utils/propDefs';
+
+export const popoverPropDefs: Record = {
+ 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'],
+ description: 'A ref for the trigger element.',
+ },
+ ...childrenPropDefs,
+ ...classNamePropDefs,
+ ...stylePropDefs,
+};
+
+export const popoverUsageSnippet = `import { Popover, DialogTrigger, Button } from '@backstage/ui';
+
+
+
+
+ This is a popover
+
+`;
+
+export const popoverDefaultSnippet = `
+
+
+ This is a popover
+
+`;
+
+export const popoverPlacementsSnippet = `
+
+
+ Top placement
+
+
+
+
+
+
+ Right placement
+
+
+
+
+
+
+ Bottom placement
+
+
+
+
+
+
+ Left placement
+
+`;
+
+export const popoverNoArrowSnippet = `
+
+
+ Popover without arrow
+
+`;
+
+export const popoverRichContentSnippet = `
+
+
+
+ Popover Title
+
+ This is a popover with rich content. It can contain multiple
+ elements and formatted text.
+
+
+
+
+
+
+
+`;
+
+export const popoverNonModalSnippet = `
+
+
+
+ This is a non-modal popover. You can interact with other
+ elements on the page while it's open.
+
+
+`;
diff --git a/docs-ui/src/snippets/stories-snippets.tsx b/docs-ui/src/snippets/stories-snippets.tsx
index ba722a5edb..9297844baa 100644
--- a/docs-ui/src/snippets/stories-snippets.tsx
+++ b/docs-ui/src/snippets/stories-snippets.tsx
@@ -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 ? : 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);
diff --git a/docs-ui/src/utils/data.ts b/docs-ui/src/utils/data.ts
index b20725142c..1b046fd3e5 100644
--- a/docs-ui/src/utils/data.ts
+++ b/docs-ui/src/utils/data.ts
@@ -76,6 +76,11 @@ export const components: Page[] = [
title: 'PasswordField',
slug: 'password-field',
},
+ {
+ title: 'Popover',
+ slug: 'popover',
+ status: 'new',
+ },
{
title: 'RadioGroup',
slug: 'radio-group',