Migrate all components to their new format

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2026-01-15 17:01:49 +01:00
parent ca2e767204
commit c29416593e
181 changed files with 5189 additions and 3966 deletions
@@ -1,119 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const accordionPropDefs: Record<string, PropDef> = {
children: {
type: 'enum',
values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'],
},
defaultExpanded: {
type: 'boolean',
default: 'false',
},
isExpanded: {
type: 'boolean',
},
onExpandedChange: {
type: 'enum',
values: ['(isExpanded: boolean) => void'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const accordionTriggerPropDefs: Record<string, PropDef> = {
level: {
type: 'enum',
values: ['1', '2', '3', '4', '5', '6'],
default: '3',
},
title: {
type: 'string',
},
subtitle: {
type: 'string',
},
children: {
type: 'enum',
values: ['ReactNode'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const accordionPanelPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const accordionGroupPropDefs: Record<string, PropDef> = {
allowsMultiple: {
type: 'boolean',
default: 'false',
},
...classNamePropDefs,
...stylePropDefs,
};
export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
<Accordion>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>Your content</AccordionPanel>
</Accordion>`;
export const accordionWithSubtitleSnippet = `<Accordion>
<AccordionTrigger
title="Advanced Settings"
subtitle="Configure additional options"
/>
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const accordionCustomTriggerSnippet = `<Accordion>
<AccordionTrigger>
<Box>
<Text as="div" weight="bold">Custom Multi-line Trigger</Text>
<Text as="div" size="small" color="secondary">
Click to expand additional details
</Text>
</Box>
</AccordionTrigger>
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const accordionDefaultExpandedSnippet = `<Accordion defaultExpanded>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const accordionGroupSingleOpenSnippet = `<AccordionGroup>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>Content 1</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>Content 2</AccordionPanel>
</Accordion>
</AccordionGroup>`;
export const accordionGroupMultipleOpenSnippet = `<AccordionGroup allowsMultiple>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>Content 1</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>Content 2</AccordionPanel>
</Accordion>
</AccordionGroup>`;
@@ -0,0 +1,130 @@
'use client';
import {
Accordion,
AccordionTrigger,
AccordionPanel,
AccordionGroup,
} from '../../../../../packages/ui/src/components/Accordion/Accordion';
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
const Content = () => (
<Box>
<Text as="p">
It&apos;s the edge of the world and all of Western civilization
</Text>
<Text as="p">
The sun may rise in the East, at least it settled in a final location
</Text>
<Text as="p">
It&apos;s understood that Hollywood sells Californication
</Text>
</Box>
);
export const Default = () => {
return (
<Accordion>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const WithSubtitle = () => {
return (
<Accordion>
<AccordionTrigger
title="Advanced Settings"
subtitle="Configure additional options"
/>
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const CustomTrigger = () => {
return (
<Accordion>
<AccordionTrigger>
<Box>
<Text as="div" variant="body-large" weight="bold">
Custom Multi-line Trigger
</Text>
<Text as="div" variant="body-medium" color="secondary">
Click to expand additional details and configuration options
</Text>
</Box>
</AccordionTrigger>
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const DefaultExpanded = () => {
return (
<Accordion defaultExpanded>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const GroupSingleOpen = () => {
return (
<AccordionGroup>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Third Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
</AccordionGroup>
);
};
export const GroupMultipleOpen = () => {
return (
<AccordionGroup allowsMultiple>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Third Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
</AccordionGroup>
);
};
+22 -19
View File
@@ -1,6 +1,21 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import {
accordionPropDefs,
accordionTriggerPropDefs,
accordionPanelPropDefs,
accordionGroupPropDefs,
} from './props-definition';
import {
accordionUsageSnippet,
defaultSnippet,
withSubtitleSnippet,
customTriggerSnippet,
defaultExpandedSnippet,
groupSingleOpenSnippet,
groupMultipleOpenSnippet,
} from './snippets';
import {
Default,
WithSubtitle,
@@ -8,19 +23,7 @@ import {
DefaultExpanded,
GroupSingleOpen,
GroupMultipleOpen,
} from './stories';
import {
accordionPropDefs,
accordionTriggerPropDefs,
accordionPanelPropDefs,
accordionGroupPropDefs,
accordionUsageSnippet,
accordionWithSubtitleSnippet,
accordionCustomTriggerSnippet,
accordionDefaultExpandedSnippet,
accordionGroupSingleOpenSnippet,
accordionGroupMultipleOpenSnippet,
} from './accordion.props';
} from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { AccordionDefinition } from '../../../utils/definitions';
@@ -36,7 +39,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
py={4}
height={240}
preview={<Default />}
code={accordionUsageSnippet}
code={defaultSnippet}
/>
## Usage
@@ -80,7 +83,7 @@ Here's a view when using both title and subtitle props.
py={4}
height={240}
preview={<WithSubtitle />}
code={accordionWithSubtitleSnippet}
code={withSubtitleSnippet}
/>
### Custom Trigger
@@ -92,7 +95,7 @@ Here's a view when providing custom multi-line content as children.
py={4}
height={280}
preview={<CustomTrigger />}
code={accordionCustomTriggerSnippet}
code={customTriggerSnippet}
/>
### Default Expanded
@@ -104,7 +107,7 @@ Here's a view when the panel is expanded by default.
py={4}
height={280}
preview={<DefaultExpanded />}
code={accordionDefaultExpandedSnippet}
code={defaultExpandedSnippet}
/>
### Group Single Open
@@ -116,7 +119,7 @@ Here's a view when only one accordion can be open at a time.
py={4}
height={280}
preview={<GroupSingleOpen />}
code={accordionGroupSingleOpenSnippet}
code={groupSingleOpenSnippet}
/>
### Group Multiple Open
@@ -128,7 +131,7 @@ Here's a view when multiple accordions can be open simultaneously.
py={4}
height={280}
preview={<GroupMultipleOpen />}
code={accordionGroupMultipleOpenSnippet}
code={groupMultipleOpenSnippet}
/>
<Theming definition={AccordionDefinition} />
@@ -0,0 +1,59 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const accordionPropDefs: Record<string, PropDef> = {
children: {
type: 'enum',
values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'],
},
defaultExpanded: {
type: 'boolean',
default: 'false',
},
isExpanded: {
type: 'boolean',
},
onExpandedChange: {
type: 'enum',
values: ['(isExpanded: boolean) => void'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const accordionTriggerPropDefs: Record<string, PropDef> = {
level: {
type: 'enum',
values: ['1', '2', '3', '4', '5', '6'],
default: '3',
},
title: {
type: 'string',
},
subtitle: {
type: 'string',
},
children: {
type: 'enum',
values: ['ReactNode'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const accordionPanelPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const accordionGroupPropDefs: Record<string, PropDef> = {
allowsMultiple: {
type: 'boolean',
default: 'false',
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,68 @@
export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
<Accordion>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>Your content</AccordionPanel>
</Accordion>`;
export const defaultSnippet = `<Accordion>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const withSubtitleSnippet = `<Accordion>
<AccordionTrigger
title="Advanced Settings"
subtitle="Configure additional options"
/>
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const customTriggerSnippet = `<Accordion>
<AccordionTrigger>
<Box>
<Text as="div" variant="body-large" weight="bold">
Custom Multi-line Trigger
</Text>
<Text as="div" variant="body-medium" color="secondary">
Click to expand additional details and configuration options
</Text>
</Box>
</AccordionTrigger>
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const defaultExpandedSnippet = `<Accordion defaultExpanded>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Text>Your content here</Text>
</AccordionPanel>
</Accordion>`;
export const groupSingleOpenSnippet = `<AccordionGroup>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>Content 1</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>Content 2</AccordionPanel>
</Accordion>
</AccordionGroup>`;
export const groupMultipleOpenSnippet = `<AccordionGroup allowsMultiple>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>Content 1</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>Content 2</AccordionPanel>
</Accordion>
</AccordionGroup>`;
@@ -1,19 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Accordion/Accordion.stories';
const {
Default: DefaultStory,
WithSubtitle: WithSubtitleStory,
CustomTrigger: CustomTriggerStory,
DefaultExpanded: DefaultExpandedStory,
GroupSingleOpen: GroupSingleOpenStory,
GroupMultipleOpen: GroupMultipleOpenStory,
} = stories;
export const Default = () => <DefaultStory.Component />;
export const WithSubtitle = () => <WithSubtitleStory.Component />;
export const CustomTrigger = () => <CustomTriggerStory.Component />;
export const DefaultExpanded = () => <DefaultExpandedStory.Component />;
export const GroupSingleOpen = () => <GroupSingleOpenStory.Component />;
export const GroupMultipleOpen = () => <GroupMultipleOpenStory.Component />;
@@ -1,89 +0,0 @@
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const avatarPropDefs: Record<string, PropDef> = {
src: {
type: 'string',
},
name: {
type: 'string',
},
size: {
type: 'enum',
values: ['x-small', 'small', 'medium', 'large', 'x-large'],
default: 'medium',
responsive: true,
},
purpose: {
type: 'enum',
values: ['informative', 'decoration'],
default: 'informative',
},
...classNamePropDefs,
...stylePropDefs,
};
export const snippetUsage = `import { Avatar } from '@backstage/ui';
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
/>`;
export const snippetSizes = `<Flex gap="4" direction="column">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille" size="x-small"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille" size="small"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille" size="medium"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille" size="large"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille" size="x-large"
/>
</Flex>`;
export const snippetFallback = `<Avatar
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
name="Charles de Dreuille"
/>`;
export const snippetPurpose = `<Flex direction="column" gap="4">
<Flex direction="column" gap="1">
<Text variant="title-x-small">Informative (default)</Text>
<Text variant="body-medium">
Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="informative"
/>
</Flex>
</Flex>
<Flex direction="column" gap="1">
<Text variant="title-x-small">Decoration</Text>
<Text variant="body-medium">
Use when name appears adjacent to avatar. Hidden from screen readers to avoid redundancy:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="decoration"
/>
<Text>Charles de Dreuille</Text>
</Flex>
</Flex>
</Flex>`;
@@ -0,0 +1,100 @@
'use client';
import { Avatar } from '../../../../../packages/ui/src/components/Avatar/Avatar';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
export const Default = () => {
return (
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
/>
);
};
export const Fallback = () => {
return (
<Avatar
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
name="Charles de Dreuille"
/>
);
};
export const Sizes = () => {
return (
<Flex direction="column" gap="6">
<Flex>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
size="x-small"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
size="small"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
size="medium"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
size="large"
/>
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
size="x-large"
/>
</Flex>
<Flex>
<Avatar name="Charles de Dreuille" size="x-small" src="" />
<Avatar name="Charles de Dreuille" size="small" src="" />
<Avatar name="Charles de Dreuille" size="medium" src="" />
<Avatar name="Charles de Dreuille" size="large" src="" />
<Avatar name="Charles de Dreuille" size="x-large" src="" />
</Flex>
</Flex>
);
};
export const Purpose = () => {
return (
<Flex direction="column" gap="4">
<Flex direction="column" gap="1">
<Text variant="title-x-small">Informative (default)</Text>
<Text variant="body-medium">
Use when avatar appears alone. Announced as &quot;Charles de
Dreuille&quot; to screen readers:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="informative"
/>
</Flex>
</Flex>
<Flex direction="column" gap="1">
<Text variant="title-x-small">Decoration</Text>
<Text variant="body-medium">
Use when avatar appears with adjacent text. Hidden from screen
readers:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="decoration"
/>
<Text>Charles de Dreuille</Text>
</Flex>
</Flex>
</Flex>
);
};
+13 -17
View File
@@ -1,14 +1,15 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Default, Sizes, Fallback, Purpose } from './stories';
import { avatarPropDefs } from './props-definition';
import {
avatarPropDefs,
snippetUsage,
snippetSizes,
snippetFallback,
snippetPurpose,
} from './avatar.props';
avatarUsageSnippet,
defaultSnippet,
sizesSnippet,
fallbackSnippet,
purposeSnippet,
} from './snippets';
import { Default, Sizes, Fallback, Purpose } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { AvatarDefinition } from '../../../utils/definitions';
@@ -19,16 +20,11 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="An avatar component with a fallback for initials."
/>
<Snippet
align="center"
py={4}
preview={<Default />}
code={`<Avatar src="https://avatars.githubusercontent.com/u/1540635?v=4" name="Charles de Dreuille" />`}
/>
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
## Usage
<CodeBlock code={snippetUsage} />
<CodeBlock code={avatarUsageSnippet} />
## API reference
@@ -45,7 +41,7 @@ Avatar sizes can be set using the `size` prop.
py={4}
open
preview={<Sizes />}
code={snippetSizes}
code={sizesSnippet}
/>
### Fallback
@@ -57,7 +53,7 @@ If the image is not available, the avatar will show the initials of the name.
py={4}
open
preview={<Fallback />}
code={snippetFallback}
code={fallbackSnippet}
/>
### The `purpose` prop
@@ -68,7 +64,7 @@ Control how the avatar is announced to screen readers using the `purpose` prop.
align="center"
py={4}
preview={<Purpose />}
code={snippetPurpose}
code={purposeSnippet}
/>
<Theming definition={AvatarDefinition} />
@@ -0,0 +1,28 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const avatarPropDefs: Record<string, PropDef> = {
src: {
type: 'string',
},
name: {
type: 'string',
required: true,
},
size: {
type: 'enum',
values: ['x-small', 'small', 'medium', 'large', 'x-large'],
default: 'medium',
responsive: true,
},
purpose: {
type: 'enum',
values: ['informative', 'decoration'],
default: 'informative',
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,52 @@
export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui';
<Avatar />`;
export const defaultSnippet = `<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
/>`;
export const fallbackSnippet = `<Avatar
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
name="Charles de Dreuille"
/>`;
export const sizesSnippet = `<Flex direction="column" gap="6">
<Flex>
<Avatar src="..." name="Charles de Dreuille" size="x-small" />
<Avatar src="..." name="Charles de Dreuille" size="small" />
<Avatar src="..." name="Charles de Dreuille" size="medium" />
<Avatar src="..." name="Charles de Dreuille" size="large" />
<Avatar src="..." name="Charles de Dreuille" size="x-large" />
</Flex>
<Flex>
<Avatar name="Charles de Dreuille" size="x-small" src="" />
<Avatar name="Charles de Dreuille" size="small" src="" />
<Avatar name="Charles de Dreuille" size="medium" src="" />
<Avatar name="Charles de Dreuille" size="large" src="" />
<Avatar name="Charles de Dreuille" size="x-large" src="" />
</Flex>
</Flex>`;
export const purposeSnippet = `<Flex direction="column" gap="4">
<Flex direction="column" gap="1">
<Text variant="title-x-small">Informative (default)</Text>
<Text variant="body-medium">
Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers:
</Text>
<Flex gap="2" align="center">
<Avatar src="..." name="Charles de Dreuille" purpose="informative" />
</Flex>
</Flex>
<Flex direction="column" gap="1">
<Text variant="title-x-small">Decoration</Text>
<Text variant="body-medium">
Use when avatar appears with adjacent text. Hidden from screen readers:
</Text>
<Flex gap="2" align="center">
<Avatar src="..." name="Charles de Dreuille" purpose="decoration" />
<Text>Charles de Dreuille</Text>
</Flex>
</Flex>
</Flex>`;
@@ -1,15 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Avatar/Avatar.stories';
const {
Default: DefaultStory,
Sizes: SizesStory,
Fallback: FallbackStory,
Purpose: PurposeStory,
} = stories;
export const Default = () => <DefaultStory.Component />;
export const Sizes = () => <SizesStory.Component />;
export const Fallback = () => <FallbackStory.Component />;
export const Purpose = () => <PurposeStory.Component />;
@@ -0,0 +1,34 @@
'use client';
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
const diagonalStripePattern = (() => {
const svg = `
<svg width="6" height="6" viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
<g fill="#2563eb" fill-opacity="0.6" fill-rule="evenodd">
<path d="M5 0h1L0 6V5zM6 5v1H5z"/>
</g>
</svg>
`.trim();
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
})();
export const Default = () => {
return (
<Box
width="64px"
height="64px"
style={{
background: '#eaf2fd',
borderRadius: '4px',
border: '1px solid #2563eb',
backgroundImage: `url("${diagonalStripePattern}")`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 'bold',
color: '#2563eb',
}}
/>
);
};
+5 -10
View File
@@ -1,14 +1,14 @@
import { CodeBlock } from '@/components/CodeBlock';
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Default } from './stories';
import { boxPropDefs } from './props-definition';
import {
boxPropDefs,
snippetUsage,
boxPreviewSnippet,
defaultSnippet,
boxSimpleSnippet,
boxResponsiveSnippet,
} from './box.props';
} from './snippets';
import { Default } from './components';
import { spacingPropDefs } from '@/utils/propDefs';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -21,12 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout."
/>
<Snippet
py={4}
preview={<Default />}
code={boxPreviewSnippet}
align="center"
/>
<Snippet py={4} preview={<Default />} code={defaultSnippet} align="center" />
## Usage
@@ -22,19 +22,3 @@ export const boxPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const snippetUsage = `import { Box } from '@backstage/ui';
<Box />`;
export const boxPreviewSnippet = `<Box>
<DecorativeBox />
</Box>`;
export const boxSimpleSnippet = `<Box padding="md" borderRadius="md">Hello World</Box>`;
export const boxResponsiveSnippet = `<Box
padding={{ xs: 'sm', md: 'md' }}
borderRadius={{ xs: 'sm', md: 'md' }}>
Hello World
</Box>`;
@@ -0,0 +1,21 @@
export const snippetUsage = `import { Box } from '@backstage/ui';
<Box />`;
export const defaultSnippet = `<Box
width="64px"
height="64px"
style={{
background: '#eaf2fd',
borderRadius: '4px',
border: '1px solid #2563eb',
}}
/>`;
export const boxSimpleSnippet = `<Box padding="md" borderRadius="md">Hello World</Box>`;
export const boxResponsiveSnippet = `<Box
padding={{ xs: 'sm', md: 'md' }}
borderRadius={{ xs: 'sm', md: 'md' }}>
Hello World
</Box>`;
@@ -1,7 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Box/Box.stories';
const { Default: DefaultStory } = stories;
export const Default = () => <DefaultStory.Component />;
@@ -1,62 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const buttonIconPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
icon: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
loading: { type: 'boolean', default: 'false', responsive: false },
type: {
type: 'enum',
values: ['button', 'submit', 'reset'],
default: 'button',
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui';
<ButtonIcon />`;
export const buttonIconDefaultSnippet = `<Flex align="center">
<ButtonIcon icon={<Icon name="cloud" />} variant="primary" />
<ButtonIcon icon={<Icon name="cloud" />} variant="secondary" />
</Flex>`;
export const buttonIconVariantsSnippet = `<Flex align="center">
<ButtonIcon icon={<Icon name="cloud" />} variant="primary" />
<ButtonIcon icon={<Icon name="cloud" />} variant="secondary" />
</Flex>`;
export const buttonIconSizesSnippet = `<Flex align="center">
<ButtonIcon icon={<Icon name="cloud" />} size="small" />
<ButtonIcon icon={<Icon name="cloud" />} size="medium" />
</Flex>`;
export const buttonIconDisabledSnippet = `<ButtonIcon icon={<Icon name="cloud" />} isDisabled />`;
export const buttonIconLoadingSnippet = `<ButtonIcon icon={<Icon name="cloud" />} variant="primary" loading={isLoading} onPress={handleClick} />`;
export const buttonIconResponsiveSnippet = `<ButtonIcon icon={<Icon name="cloud" />} variant={{ initial: 'primary', lg: 'secondary' }} />`;
export const buttonIconAsLinkSnippet = `import { ButtonLink } from '@backstage/ui';
<ButtonLink href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>`;
@@ -0,0 +1,68 @@
'use client';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { RiCloudLine } from '@remixicon/react';
export const Variants = () => {
return (
<Flex align="center" gap="2">
<ButtonIcon icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
<ButtonIcon
icon={<RiCloudLine />}
variant="secondary"
aria-label="Cloud"
/>
<ButtonIcon
icon={<RiCloudLine />}
variant="tertiary"
aria-label="Cloud"
/>
</Flex>
);
};
export const Sizes = () => {
return (
<Flex align="center" gap="2">
<ButtonIcon icon={<RiCloudLine />} size="small" aria-label="Cloud" />
<ButtonIcon icon={<RiCloudLine />} size="medium" aria-label="Cloud" />
</Flex>
);
};
export const Disabled = () => {
return (
<Flex direction="row" gap="2">
<ButtonIcon
isDisabled
icon={<RiCloudLine />}
variant="primary"
aria-label="Cloud"
/>
<ButtonIcon
isDisabled
icon={<RiCloudLine />}
variant="secondary"
aria-label="Cloud"
/>
<ButtonIcon
isDisabled
icon={<RiCloudLine />}
variant="tertiary"
aria-label="Cloud"
/>
</Flex>
);
};
export const Loading = () => {
return (
<ButtonIcon
icon={<RiCloudLine />}
variant="primary"
loading
aria-label="Cloud"
/>
);
};
+12 -26
View File
@@ -1,18 +1,15 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Variants, Sizes, Disabled, Loading } from './stories';
import { buttonIconPropDefs } from './props-definition';
import {
buttonIconPropDefs,
buttonIconUsageSnippet,
buttonIconDefaultSnippet,
buttonIconVariantsSnippet,
buttonIconSizesSnippet,
buttonIconDisabledSnippet,
buttonIconLoadingSnippet,
buttonIconResponsiveSnippet,
buttonIconAsLinkSnippet,
} from './button-icon.props';
variantsSnippet,
sizesSnippet,
disabledSnippet,
loadingSnippet,
} from './snippets';
import { Variants, Sizes, Disabled, Loading } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ButtonIconDefinition } from '../../../utils/definitions';
@@ -23,12 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="A button component with a single icon that can be used to trigger actions."
/>
<Snippet
align="center"
py={4}
preview={<Variants />}
code={buttonIconDefaultSnippet}
/>
<Snippet align="center" py={4} preview={<Variants />} code={variantsSnippet} />
## Usage
@@ -49,7 +41,7 @@ Here's a view when buttons have different variants.
py={4}
open
preview={<Variants />}
code={buttonIconVariantsSnippet}
code={variantsSnippet}
/>
### Sizes
@@ -61,7 +53,7 @@ Here's a view when buttons have different sizes.
py={4}
open
preview={<Sizes />}
code={buttonIconSizesSnippet}
code={sizesSnippet}
/>
### Disabled
@@ -73,7 +65,7 @@ Here's a view when buttons are disabled.
py={4}
open
preview={<Disabled />}
code={buttonIconDisabledSnippet}
code={disabledSnippet}
/>
### Loading
@@ -85,15 +77,9 @@ Here's a view when buttons are in a loading state.
py={4}
open
preview={<Loading />}
code={buttonIconLoadingSnippet}
code={loadingSnippet}
/>
### Responsive
Here's a view when buttons are responsive.
<CodeBlock code={buttonIconResponsiveSnippet} />
<Theming definition={ButtonIconDefinition} />
<ChangelogComponent component="button-icon" />
@@ -0,0 +1,31 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const buttonIconPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary', 'tertiary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
icon: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
loading: { type: 'boolean', default: 'false', responsive: false },
type: {
type: 'enum',
values: ['button', 'submit', 'reset'],
default: 'button',
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,22 @@
export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui';
<ButtonIcon />`;
export const variantsSnippet = `<Flex align="center" gap="2">
<ButtonIcon icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
<ButtonIcon icon={<RiCloudLine />} variant="secondary" aria-label="Cloud" />
<ButtonIcon icon={<RiCloudLine />} variant="tertiary" aria-label="Cloud" />
</Flex>`;
export const sizesSnippet = `<Flex align="center" gap="2">
<ButtonIcon icon={<RiCloudLine />} size="small" aria-label="Cloud" />
<ButtonIcon icon={<RiCloudLine />} size="medium" aria-label="Cloud" />
</Flex>`;
export const disabledSnippet = `<Flex direction="row" gap="2">
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="secondary" aria-label="Cloud" />
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="tertiary" aria-label="Cloud" />
</Flex>`;
export const loadingSnippet = `<ButtonIcon icon={<RiCloudLine />} variant="primary" loading aria-label="Cloud" />`;
@@ -1,15 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/ButtonIcon/ButtonIcon.stories';
const {
Variants: VariantsStory,
Sizes: SizesStory,
Disabled: DisabledStory,
Loading: LoadingStory,
} = stories;
export const Variants = () => <VariantsStory.Component />;
export const Sizes = () => <SizesStory.Component />;
export const Disabled = () => <DisabledStory.Component />;
export const Loading = () => <LoadingStory.Component />;
@@ -1,73 +0,0 @@
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const buttonLinkPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
href: { type: 'string', responsive: false },
hrefLang: { type: 'string', responsive: false },
target: {
type: 'enum',
values: ['HTMLAttributeAnchorTarget'],
default: '_self',
responsive: false,
},
rel: { type: 'string', responsive: false },
children: { type: 'enum', values: ['ReactNode'], responsive: false },
...classNamePropDefs,
...stylePropDefs,
};
export const buttonLinkSnippetUsage = `import { ButtonLink } from '@backstage/ui';
<ButtonLink />`;
export const buttonLinkVariantsSnippet = `<Flex align="center">
<ButtonLink iconStart={<Icon name="cloud" />} variant="primary">
Button
</ButtonLink>
<ButtonLink iconStart={<Icon name="cloud" />} variant="secondary">
Button
</ButtonLink>
</Flex>`;
export const buttonLinkSizesSnippet = `<Flex align="center">
<ButtonLink size="small">Small</ButtonLink>
<ButtonLink size="medium">Medium</ButtonLink>
</Flex>`;
export const buttonLinkIconsSnippet = `<Flex align="center">
<ButtonLink iconStart={<Icon name="cloud" />}>Button</ButtonLink>
<ButtonLink iconEnd={<Icon name="chevronRight" />}>Button</ButtonLink>
<ButtonLink
iconStart={<Icon name="cloud" />}
iconEnd={<Icon name="chevronRight" />}>
Button
</ButtonLink>
</Flex>`;
export const buttonLinkDisabledSnippet = `<Flex gap="4">
<ButtonLink variant="primary" isDisabled>
Primary
</ButtonLink>
<ButtonLink variant="secondary" isDisabled>
Secondary
</ButtonLink>
</Flex>`;
export const buttonLinkResponsiveSnippet = `<ButtonLink variant={{ initial: 'primary', lg: 'secondary' }}>
Responsive Button
</ButtonLink>`;
@@ -0,0 +1,122 @@
'use client';
import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { MemoryRouter } from 'react-router-dom';
import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react';
export const Variants = () => {
return (
<MemoryRouter>
<Flex align="center">
<ButtonLink
iconStart={<RiCloudLine />}
variant="primary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
variant="secondary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
variant="tertiary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
</Flex>
</MemoryRouter>
);
};
export const Sizes = () => {
return (
<MemoryRouter>
<Flex align="center">
<ButtonLink size="small" href="https://ui.backstage.io" target="_blank">
Small
</ButtonLink>
<ButtonLink
size="medium"
href="https://ui.backstage.io"
target="_blank"
>
Medium
</ButtonLink>
</Flex>
</MemoryRouter>
);
};
export const WithIcons = () => {
return (
<MemoryRouter>
<Flex align="center">
<ButtonLink
iconStart={<RiCloudLine />}
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconEnd={<RiArrowRightSLine />}
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
iconEnd={<RiArrowRightSLine />}
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
</Flex>
</MemoryRouter>
);
};
export const Disabled = () => {
return (
<MemoryRouter>
<Flex gap="4">
<ButtonLink
variant="primary"
isDisabled
href="https://ui.backstage.io"
target="_blank"
>
Primary
</ButtonLink>
<ButtonLink
variant="secondary"
isDisabled
href="https://ui.backstage.io"
target="_blank"
>
Secondary
</ButtonLink>
<ButtonLink
variant="tertiary"
isDisabled
href="https://ui.backstage.io"
target="_blank"
>
Tertiary
</ButtonLink>
</Flex>
</MemoryRouter>
);
};
+14 -21
View File
@@ -1,16 +1,15 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Variants, Sizes, WithIcons, Disabled } from './stories';
import { buttonLinkPropDefs } from './props-definition';
import {
buttonLinkPropDefs,
buttonLinkSnippetUsage,
buttonLinkVariantsSnippet,
buttonLinkSizesSnippet,
buttonLinkIconsSnippet,
buttonLinkDisabledSnippet,
buttonLinkResponsiveSnippet,
} from './button-link.props';
buttonLinkUsageSnippet,
variantsSnippet,
sizesSnippet,
withIconsSnippet,
disabledSnippet,
} from './snippets';
import { Variants, Sizes, WithIcons, Disabled } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ButtonLinkDefinition } from '../../../utils/definitions';
@@ -25,12 +24,12 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
align="center"
py={4}
preview={<Variants />}
code={buttonLinkVariantsSnippet}
code={variantsSnippet}
/>
## Usage
<CodeBlock code={buttonLinkSnippetUsage} />
<CodeBlock code={buttonLinkUsageSnippet} />
## API reference
@@ -47,7 +46,7 @@ Here's a view when buttons have different variants.
py={4}
open
preview={<Variants />}
code={buttonLinkVariantsSnippet}
code={variantsSnippet}
/>
### Sizes
@@ -59,7 +58,7 @@ Here's a view when buttons have different sizes.
py={4}
open
preview={<Sizes />}
code={buttonLinkSizesSnippet}
code={sizesSnippet}
/>
### With Icons
@@ -71,7 +70,7 @@ Here's a view when buttons have icons.
py={4}
open
preview={<WithIcons />}
code={buttonLinkIconsSnippet}
code={withIconsSnippet}
/>
### Disabled
@@ -83,15 +82,9 @@ Here's a view when buttons are disabled.
py={4}
open
preview={<Disabled />}
code={buttonLinkDisabledSnippet}
code={disabledSnippet}
/>
### Responsive
Here's a view when buttons are responsive.
<CodeBlock code={buttonLinkResponsiveSnippet} />
<Theming definition={ButtonLinkDefinition} />
<ChangelogComponent component="button-link" />
@@ -0,0 +1,32 @@
import {
classNamePropDefs,
stylePropDefs,
childrenPropDefs,
type PropDef,
} from '@/utils/propDefs';
export const buttonLinkPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary', 'tertiary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
href: { type: 'string', required: true },
target: {
type: 'enum',
values: ['_self', '_blank', '_parent', '_top'],
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,68 @@
export const buttonLinkUsageSnippet = `import { ButtonLink } from '@backstage/ui';
<ButtonLink href="https://ui.backstage.io">Button</ButtonLink>`;
export const variantsSnippet = `<Flex align="center">
<ButtonLink
iconStart={<RiCloudLine />}
variant="primary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
variant="secondary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
variant="tertiary"
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
</Flex>`;
export const sizesSnippet = `<Flex align="center">
<ButtonLink size="small" href="https://ui.backstage.io" target="_blank">
Small
</ButtonLink>
<ButtonLink size="medium" href="https://ui.backstage.io" target="_blank">
Medium
</ButtonLink>
</Flex>`;
export const withIconsSnippet = `<Flex align="center">
<ButtonLink iconStart={<RiCloudLine />} href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>
<ButtonLink iconEnd={<RiArrowRightSLine />} href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>
<ButtonLink
iconStart={<RiCloudLine />}
iconEnd={<RiArrowRightSLine />}
href="https://ui.backstage.io"
target="_blank"
>
Button
</ButtonLink>
</Flex>`;
export const disabledSnippet = `<Flex gap="4">
<ButtonLink variant="primary" isDisabled href="https://ui.backstage.io" target="_blank">
Primary
</ButtonLink>
<ButtonLink variant="secondary" isDisabled href="https://ui.backstage.io" target="_blank">
Secondary
</ButtonLink>
<ButtonLink variant="tertiary" isDisabled href="https://ui.backstage.io" target="_blank">
Tertiary
</ButtonLink>
</Flex>`;
@@ -1,15 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/ButtonLink/ButtonLink.stories';
const {
Variants: VariantsStory,
Sizes: SizesStory,
WithIcons: WithIconsStory,
Disabled: DisabledStory,
} = stories;
export const Variants = () => <VariantsStory.Component />;
export const Sizes = () => <SizesStory.Component />;
export const WithIcons = () => <WithIconsStory.Component />;
export const Disabled = () => <DisabledStory.Component />;
@@ -1,77 +0,0 @@
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const buttonPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
loading: { type: 'boolean', default: 'false', responsive: false },
children: { type: 'enum', values: ['ReactNode'], responsive: false },
type: {
type: 'enum',
values: ['button', 'submit', 'reset'],
default: 'button',
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
export const buttonSnippetUsage = `import { Button } from '@backstage/ui';
<Button />`;
export const buttonVariantsSnippet = `<Flex align="center">
<Button iconStart="cloud" variant="primary">
Button
</Button>
<Button iconStart="cloud" variant="secondary">
Button
</Button>
</Flex>`;
export const buttonSizesSnippet = `<Flex align="center">
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
</Flex>`;
export const buttonIconsSnippet = `<Flex align="center">
<Button iconStart={<Icon name="cloud" />}>Button</Button>
<Button iconEnd={<Icon name="chevronRight" />}>Button</Button>
<Button iconStart={<Icon name="cloud" />} iconEnd={<Icon name="chevronRight" />}>Button</Button>
</Flex>`;
export const buttonDisabledSnippet = `<Flex gap="4">
<Button variant="primary" isDisabled>
Primary
</Button>
<Button variant="secondary" isDisabled>
Secondary
</Button>
</Flex>`;
export const buttonResponsiveSnippet = `<Button variant={{ initial: 'primary', lg: 'secondary' }}>
Responsive Button
</Button>`;
export const buttonLoadingSnippet = `<Button variant="primary" loading={isLoading} onPress={handleClick}>
Load more items
</Button>`;
export const buttonAsLinkSnippet = `import { ButtonLink } from '@backstage/ui';
<ButtonLink href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>`;
@@ -0,0 +1,78 @@
'use client';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react';
import { MemoryRouter } from 'react-router-dom';
export const Variants = () => {
return (
<Flex>
<Button variant="primary" iconStart={<RiCloudLine />}>
Button
</Button>
<Button variant="secondary" iconStart={<RiCloudLine />}>
Button
</Button>
<Button variant="tertiary" iconStart={<RiCloudLine />}>
Button
</Button>
</Flex>
);
};
export const Sizes = () => {
return (
<Flex align="center">
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
</Flex>
);
};
export const WithIcons = () => {
return (
<Flex align="center">
<Button iconStart={<RiCloudLine />}>Button</Button>
<Button iconEnd={<RiArrowRightSLine />}>Button</Button>
<Button iconStart={<RiCloudLine />} iconEnd={<RiArrowRightSLine />}>
Button
</Button>
</Flex>
);
};
export const Disabled = () => {
return (
<Flex gap="4">
<Button variant="primary" isDisabled>
Primary
</Button>
<Button variant="secondary" isDisabled>
Secondary
</Button>
<Button variant="tertiary" isDisabled>
Tertiary
</Button>
</Flex>
);
};
export const Loading = () => {
return (
<Button variant="primary" loading={true}>
Load more items
</Button>
);
};
export const AsLink = () => {
return (
<MemoryRouter>
<ButtonLink href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>
</MemoryRouter>
);
};
+23 -34
View File
@@ -1,35 +1,36 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Variants, Sizes, WithIcons, Disabled, Loading } from './stories';
import { Variants as ButtonLinkVariants } from '../button-link/stories';
import { buttonPropDefs } from './props-definition';
import {
buttonPropDefs,
variantsSnippet,
sizesSnippet,
withIconsSnippet,
disabledSnippet,
loadingSnippet,
asLinkSnippet,
buttonSnippetUsage,
buttonVariantsSnippet,
buttonSizesSnippet,
buttonIconsSnippet,
buttonDisabledSnippet,
buttonLoadingSnippet,
buttonResponsiveSnippet,
buttonAsLinkSnippet,
} from './button.props';
} from './snippets';
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ButtonDefinition } from '../../../utils/definitions';
import {
Variants,
Sizes,
WithIcons,
Disabled,
Loading,
AsLink,
} from './components';
<PageTitle
title="Button"
description="A button component that can be used to trigger actions."
/>
<Snippet
align="center"
py={4}
preview={<Variants />}
code={buttonVariantsSnippet}
/>
<Snippet align="center" py={4} preview={<Variants />} code={variantsSnippet} />
## Usage
@@ -50,20 +51,14 @@ Here's a view when buttons have different variants.
py={4}
open
preview={<Variants />}
code={buttonVariantsSnippet}
code={variantsSnippet}
/>
### Sizes
Here's a view when buttons have different sizes.
<Snippet
align="center"
py={4}
open
preview={<Sizes />}
code={buttonSizesSnippet}
/>
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
### With Icons
@@ -74,7 +69,7 @@ Here's a view when buttons have icons.
py={4}
open
preview={<WithIcons />}
code={buttonIconsSnippet}
code={withIconsSnippet}
/>
### Disabled
@@ -86,7 +81,7 @@ Here's a view when buttons are disabled.
py={4}
open
preview={<Disabled />}
code={buttonDisabledSnippet}
code={disabledSnippet}
/>
### Loading
@@ -98,7 +93,7 @@ Here's a view when buttons are in a loading state.
py={4}
open
preview={<Loading />}
code={buttonLoadingSnippet}
code={loadingSnippet}
/>
### Responsive
@@ -111,13 +106,7 @@ Here's a view when buttons are responsive.
If you want to use a button as a link, please use the `ButtonLink` component.
<Snippet
align="center"
py={4}
open
preview={<ButtonLinkVariants />}
code={buttonAsLinkSnippet}
/>
<Snippet align="center" py={4} open preview={<AsLink />} code={asLinkSnippet} />
<Theming definition={ButtonDefinition} />
@@ -0,0 +1,30 @@
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const buttonPropDefs: Record<string, PropDef> = {
variant: {
type: 'enum',
values: ['primary', 'secondary'],
default: 'primary',
responsive: true,
},
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'medium',
responsive: true,
},
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
isDisabled: { type: 'boolean', default: 'false', responsive: false },
loading: { type: 'boolean', default: 'false', responsive: false },
children: { type: 'enum', values: ['ReactNode'], responsive: false },
type: {
type: 'enum',
values: ['button', 'submit', 'reset'],
default: 'button',
responsive: false,
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,54 @@
export const buttonSnippetUsage = `import { Button } from '@backstage/ui';
<Button />`;
export const buttonResponsiveSnippet = `<Button variant={{ initial: 'primary', lg: 'secondary' }}>
Responsive Button
</Button>`;
export const variantsSnippet = `<Flex>
<Button variant="primary" iconStart={<RiCloudLine />}>
Button
</Button>
<Button variant="secondary" iconStart={<RiCloudLine />}>
Button
</Button>
<Button variant="tertiary" iconStart={<RiCloudLine />}>
Button
</Button>
</Flex>`;
export const sizesSnippet = `<Flex align="center">
<Button size="small">Small</Button>
<Button size="medium">Medium</Button>
</Flex>`;
export const withIconsSnippet = `<Flex align="center">
<Button iconStart={<RiCloudLine />}>Button</Button>
<Button iconEnd={<RiArrowRightSLine />}>Button</Button>
<Button iconStart={<RiCloudLine />} iconEnd={<RiArrowRightSLine />}>
Button
</Button>
</Flex>`;
export const disabledSnippet = `<Flex gap="4">
<Button variant="primary" isDisabled>
Primary
</Button>
<Button variant="secondary" isDisabled>
Secondary
</Button>
<Button variant="tertiary" isDisabled>
Tertiary
</Button>
</Flex>`;
export const loadingSnippet = `<Button variant="primary" loading={true}>
Load more items
</Button>`;
export const asLinkSnippet = `<MemoryRouter>
<ButtonLink href="https://ui.backstage.io" target="_blank">
Button
</ButtonLink>
</MemoryRouter>`;
@@ -1,17 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Button/Button.stories';
const {
Variants: VariantsStory,
Sizes: SizesStory,
WithIcons: WithIconsStory,
Disabled: DisabledStory,
Loading: LoadingStory,
} = stories;
export const Variants = () => <VariantsStory.Component />;
export const Sizes = () => <SizesStory.Component />;
export const WithIcons = () => <WithIconsStory.Component />;
export const Disabled = () => <DisabledStory.Component />;
export const Loading = () => <LoadingStory.Component />;
@@ -1,83 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const cardPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const cardHeaderPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const cardBodyPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const cardFooterPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const cardUsageSnippet = `import { card } from '@backstage/ui';
<Card>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>`;
export const cardDefaultSnippet = `<Card>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>`;
export const cardLongBodySnippet = `<Card style={{ width: '300px', height: '200px' }}>
<CardHeader>
<Text>Header</Text>
</CardHeader>
<CardBody>
<Text>
This is the first paragraph of a long body text that demonstrates how
the Card component handles extensive content. The card should adjust
accordingly to display all the text properly while maintaining its
structure.
</Text>
<Text>
Here's a second paragraph that adds more content to our card body.
Having multiple paragraphs helps to visualize how spacing works within
the card component.
</Text>
<Text>
This third paragraph continues to add more text to ensure we have a
proper demonstration of a card with significant content. This makes it
easier to test scrolling behavior and overall layout when content
exceeds the initial view.
</Text>
</CardBody>
<CardFooter>
<Text>Footer</Text>
</CardFooter>
</Card>`;
export const cardListRowSnippet = `<Card style={{ width: '300px', height: '200px' }}>
<CardHeader>
<Text>Header</Text>
</CardHeader>
<CardBody>
<ListRow>Hello world</ListRow>
<ListRow>Hello world</ListRow>
<ListRow>Hello world</ListRow>
<ListRow>Hello world</ListRow>
...
</CardBody>
<CardFooter>
<Text>Footer</Text>
</CardFooter>
</Card>`;
@@ -0,0 +1,66 @@
'use client';
import {
Card,
CardHeader,
CardBody,
CardFooter,
} from '../../../../../packages/ui/src/components/Card/Card';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
export const Default = () => {
return (
<Card>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>
);
};
export const CustomSize = () => {
return (
<Card
style={{
width: '300px',
height: '200px',
}}
>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>
);
};
export const WithLongBody = () => {
return (
<Card style={{ width: '300px', height: '200px' }}>
<CardHeader>
<Text>Header</Text>
</CardHeader>
<CardBody>
<Text>
This is the first paragraph of a long body text that demonstrates how
the Card component handles extensive content. The card should adjust
accordingly to display all the text properly while maintaining its
structure.
</Text>
<Text>
Here&apos;s a second paragraph that adds more content to our card
body. Having multiple paragraphs helps to visualize how spacing works
within the card component.
</Text>
<Text>
This third paragraph continues to add more text to ensure we have a
proper demonstration of a card with significant content. This makes it
easier to test scrolling behavior and overall layout when content
exceeds the initial view.
</Text>
</CardBody>
<CardFooter>
<Text>Footer</Text>
</CardFooter>
</Card>
);
};
+23 -21
View File
@@ -1,17 +1,19 @@
import { PropsTable } from '@/components/PropsTable';
import { CustomSize, WithLongBody, WithListRow } from './stories';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import {
cardPropDefs,
cardUsageSnippet,
cardDefaultSnippet,
cardHeaderPropDefs,
cardBodyPropDefs,
cardFooterPropDefs,
cardLongBodySnippet,
cardListRowSnippet,
} from './card.props';
} from './props-definition';
import {
cardUsageSnippet,
defaultSnippet,
customSizeSnippet,
withLongBodySnippet,
} from './snippets';
import { Default, CustomSize, WithLongBody } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { CardDefinition } from '../../../utils/definitions';
@@ -25,8 +27,8 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
<Snippet
align="center"
py={4}
preview={<CustomSize />}
code={cardDefaultSnippet}
preview={<Default />}
code={defaultSnippet}
/>
## Usage
@@ -61,6 +63,18 @@ To display a footer in a card, use the `CardFooter` component. This will be fixe
## Examples
### Custom size
Here's a view when card has a custom size.
<Snippet
align="center"
py={4}
preview={<CustomSize />}
code={customSizeSnippet}
open
/>
### With long body
Here's a view when card has a long body.
@@ -69,19 +83,7 @@ Here's a view when card has a long body.
align="center"
py={4}
preview={<WithLongBody />}
code={cardLongBodySnippet}
open
/>
### With list
Here's a view when card has a list.
<Snippet
align="center"
py={4}
preview={<WithListRow />}
code={cardListRowSnippet}
code={withLongBodySnippet}
open
/>
@@ -0,0 +1,30 @@
import {
classNamePropDefs,
stylePropDefs,
childrenPropDefs,
type PropDef,
} from '@/utils/propDefs';
export const cardPropDefs: Record<string, PropDef> = {
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const cardHeaderPropDefs: Record<string, PropDef> = {
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const cardBodyPropDefs: Record<string, PropDef> = {
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const cardFooterPropDefs: Record<string, PropDef> = {
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,41 @@
export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter } from '@backstage/ui';
<Card>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>`;
export const defaultSnippet = `<Card>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>`;
export const customSizeSnippet = `<Card style={{ width: '300px', height: '200px' }}>
<CardHeader>Header</CardHeader>
<CardBody>Body</CardBody>
<CardFooter>Footer</CardFooter>
</Card>`;
export const withLongBodySnippet = `<Card style={{ width: '300px', height: '200px' }}>
<CardHeader>
<Text>Header</Text>
</CardHeader>
<CardBody>
<Text>
This is the first paragraph of a long body text that demonstrates how
the Card component handles extensive content.
</Text>
<Text>
Here's a second paragraph that adds more content to our card body.
</Text>
<Text>
This third paragraph continues to add more text to ensure we have a
proper demonstration of a card with significant content.
</Text>
</CardBody>
<CardFooter>
<Text>Footer</Text>
</CardFooter>
</Card>`;
@@ -1,13 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Card/Card.stories';
const {
CustomSize: CustomSizeStory,
WithLongBody: WithLongBodyStory,
WithListRow: WithListRowStory,
} = stories;
export const CustomSize = () => <CustomSizeStory.Component />;
export const WithLongBody = () => <WithLongBodyStory.Component />;
export const WithListRow = () => <WithListRowStory.Component />;
@@ -0,0 +1,21 @@
'use client';
import { Checkbox } from '../../../../../packages/ui/src/components/Checkbox/Checkbox';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
export const Default = () => {
return <Checkbox>Accept terms and conditions</Checkbox>;
};
export const AllVariants = () => {
return (
<Flex direction="column" gap="2">
<Checkbox>Unchecked</Checkbox>
<Checkbox isSelected>Checked</Checkbox>
<Checkbox isDisabled>Disabled</Checkbox>
<Checkbox isSelected isDisabled>
Checked & Disabled
</Checkbox>
</Flex>
);
};
+7 -12
View File
@@ -1,13 +1,13 @@
import { PropsTable } from '@/components/PropsTable';
import { Default, AllVariants } from './stories';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { checkboxPropDefs } from './props-definition';
import {
checkboxPropDefs,
checkboxUsageSnippet,
checkboxDefaultSnippet,
checkboxVariantsSnippet,
} from './checkbox.props';
defaultSnippet,
allVariantsSnippet,
} from './snippets';
import { Default, AllVariants } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { CheckboxDefinition } from '../../../utils/definitions';
@@ -18,12 +18,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="A checkbox component that can be used to trigger actions."
/>
<Snippet
align="center"
py={4}
preview={<Default />}
code={checkboxDefaultSnippet}
/>
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
## Usage
@@ -44,7 +39,7 @@ Here's a view when checkboxes have different variants.
py={4}
open
preview={<AllVariants />}
code={checkboxVariantsSnippet}
code={allVariantsSnippet}
/>
<Theming definition={CheckboxDefinition} />
@@ -46,16 +46,3 @@ export const checkboxPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui';
<Checkbox>Accept terms</Checkbox>`;
export const checkboxDefaultSnippet = `<Checkbox>Accept terms and conditions</Checkbox>`;
export const checkboxVariantsSnippet = `<Flex direction="column" gap="2">
<Checkbox>Unchecked</Checkbox>
<Checkbox isSelected>Checked</Checkbox>
<Checkbox isDisabled>Disabled</Checkbox>
<Checkbox isSelected isDisabled>Checked & Disabled</Checkbox>
</Flex>`;
@@ -0,0 +1,12 @@
export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui';
<Checkbox>Accept terms</Checkbox>`;
export const defaultSnippet = `<Checkbox>Accept terms and conditions</Checkbox>`;
export const allVariantsSnippet = `<Flex direction="column" gap="2">
<Checkbox>Unchecked</Checkbox>
<Checkbox isSelected>Checked</Checkbox>
<Checkbox isDisabled>Disabled</Checkbox>
<Checkbox isSelected isDisabled>Checked & Disabled</Checkbox>
</Flex>`;
@@ -1,8 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Checkbox/Checkbox.stories';
const { Default: DefaultStory, AllVariants: AllVariantsStory } = stories;
export const Default = () => <DefaultStory.Component />;
export const AllVariants = () => <AllVariantsStory.Component />;
@@ -0,0 +1,24 @@
'use client';
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
const DecorativeBox = () => (
<Box
style={{
height: '64px',
background: '#eaf2fd',
borderRadius: '4px',
border: '1px solid #2563eb',
backgroundImage:
'url("data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E")',
}}
/>
);
export const Preview = () => {
return (
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<DecorativeBox />
</div>
);
};
@@ -1,14 +1,14 @@
import { CodeBlock } from '@/components/CodeBlock';
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Preview } from './stories';
import { containerPropDefs } from './props-definition';
import {
containerPropDefs,
containerUsageSnippet,
containerDefaultSnippet,
previewSnippet,
containerSimpleSnippet,
containerResponsiveSnippet,
} from './container.props';
} from './snippets';
import { Preview } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ContainerDefinition } from '../../../utils/definitions';
@@ -20,11 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="The container component let you use our default max-width and center the content on the page."
/>
<Snippet
py={4}
preview={<Preview />}
code={containerDefaultSnippet}
/>
<Snippet py={4} preview={<Preview />} code={previewSnippet} />
## Usage
@@ -0,0 +1,12 @@
import {
classNamePropDefs,
stylePropDefs,
gapPropDefs,
type PropDef,
} from '@/utils/propDefs';
export const containerPropDefs: Record<string, PropDef> = {
...gapPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -1,23 +1,10 @@
import {
classNamePropDefs,
stylePropDefs,
gapPropDefs,
type PropDef,
} from '@/utils/propDefs';
export const containerPropDefs: Record<string, PropDef> = {
...gapPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const containerUsageSnippet = `import { Container } from "@backstage/ui";
<Container>Hello World!</Container>`;
export const containerDefaultSnippet = `<Container>
export const previewSnippet = `<div style={{ maxWidth: '600px', margin: '0 auto' }}>
<DecorativeBox />
</Container>`;
</div>`;
export const containerSimpleSnippet = `<Container>
<Box>Hello World</Box>
@@ -1,7 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Container/Container.stories';
const { Preview: PreviewStory } = stories;
export const Preview = () => <PreviewStory.Component />;
@@ -0,0 +1,104 @@
'use client';
import {
Dialog,
DialogTrigger,
DialogHeader,
DialogBody,
DialogFooter,
} from '../../../../../packages/ui/src/components/Dialog/Dialog';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
import { TextField } from '../../../../../packages/ui/src/components/TextField/TextField';
import { Select } from '../../../../../packages/ui/src/components/Select/Select';
export const Default = () => (
<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 PreviewFixedWidthAndHeight = () => (
<DialogTrigger>
<Button variant="secondary">Scrollable Dialog</Button>
<Dialog width={600} height={400}>
<DialogHeader>Long Content Dialog</DialogHeader>
<DialogBody>
<Flex direction="column" gap="3">
<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>
</Flex>
</DialogBody>
<DialogFooter>
<Button variant="secondary" slot="close">
Cancel
</Button>
<Button variant="primary" slot="close">
Accept
</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>
);
export const PreviewWithForm = () => (
<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>
<Button variant="secondary" slot="close">
Cancel
</Button>
<Button variant="primary" slot="close">
Create User
</Button>
</DialogFooter>
</Dialog>
</DialogTrigger>
);
+4 -2
View File
@@ -1,7 +1,7 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Default, PreviewFixedWidthAndHeight, PreviewWithForm } from './stories';
import { Default, PreviewFixedWidthAndHeight, PreviewWithForm } from './components';
import {
dialogPropDefs,
dialogTriggerPropDefs,
@@ -9,13 +9,15 @@ import {
dialogBodyPropDefs,
dialogFooterPropDefs,
dialogClosePropDefs,
} from './props-definition';
import {
dialogUsageSnippet,
dialogDefaultSnippet,
dialogFixedWidthAndHeightSnippet,
dialogWithFormSnippet,
dialogWithNoTriggerSnippet,
dialogCloseSnippet,
} from './dialog.props';
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { DialogDefinition } from '../../../utils/definitions';
@@ -0,0 +1,88 @@
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,
};
@@ -1,92 +1,3 @@
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,
@@ -122,9 +33,9 @@ export const dialogDefaultSnippet = `<DialogTrigger>
export const dialogFixedWidthAndHeightSnippet = `<DialogTrigger>
<Button variant="secondary">Scrollable Dialog</Button>
<Dialog>
<Dialog width={600} height={400}>
<DialogHeader>Long Content Dialog</DialogHeader>
<DialogBody width={600} height={400}>
<DialogBody>
...
</DialogBody>
<DialogFooter>
@@ -1,15 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Dialog/Dialog.stories';
const {
Default: DefaultStory,
PreviewFixedWidthAndHeight: PreviewFixedWidthAndHeightStory,
PreviewWithForm: PreviewWithFormStory,
} = stories;
export const Default = () => <DefaultStory.Component />;
export const PreviewFixedWidthAndHeight = () => (
<PreviewFixedWidthAndHeightStory.Component />
);
export const PreviewWithForm = () => <PreviewWithFormStory.Component />;
@@ -0,0 +1,38 @@
'use client';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
export const Default = () => {
return (
<Flex gap="4">
<Box
style={{
width: '64px',
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
width: '64px',
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
width: '64px',
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
</Flex>
);
};
@@ -1,55 +0,0 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const flexPropDefs: Record<string, PropDef> = {
align: {
type: 'enum',
values: ['start', 'center', 'end', 'baseline', 'stretch'],
responsive: true,
},
direction: {
type: 'enum',
values: ['row', 'column', 'row-reverse', 'column-reverse'],
responsive: true,
},
justify: {
type: 'enum',
values: ['start', 'center', 'end', 'between'],
responsive: true,
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const flexUsageSnippet = `import { Flex } from '@backstage/ui';
<Flex />`;
export const flexDefaultSnippet = `<Flex>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Flex>`;
export const flexSimpleSnippet = `<Flex gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const flexResponsiveSnippet = `<Flex gap={{ xs: 'xs', md: 'md' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const flexAlignSnippet = `<Flex align={{ xs: 'start', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
+5 -11
View File
@@ -1,16 +1,15 @@
import { PropsTable } from '@/components/PropsTable';
import { CodeBlock } from '@/components/CodeBlock';
import { Snippet } from '@/components/Snippet';
import { Default } from './stories';
import { flexPropDefs } from './props-definition';
import {
flexPropDefs,
flexUsageSnippet,
flexDefaultSnippet,
flexFAQ1Snippet,
defaultSnippet,
flexSimpleSnippet,
flexResponsiveSnippet,
flexAlignSnippet,
} from './flex.props';
} from './snippets';
import { Default } from './components';
import { spacingPropDefs } from '@/utils/propDefs';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
@@ -23,12 +22,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="A responsive flex container component for vertical stacking with customizable gaps."
/>
<Snippet
py={4}
align="center"
preview={<Default />}
code={flexDefaultSnippet}
/>
<Snippet py={4} align="center" preview={<Default />} code={defaultSnippet} />
## Usage
@@ -0,0 +1,27 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const flexPropDefs: Record<string, PropDef> = {
align: {
type: 'enum',
values: ['start', 'center', 'end', 'baseline', 'stretch'],
responsive: true,
},
direction: {
type: 'enum',
values: ['row', 'column', 'row-reverse', 'column-reverse'],
responsive: true,
},
justify: {
type: 'enum',
values: ['start', 'center', 'end', 'between'],
responsive: true,
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,27 @@
export const flexUsageSnippet = `import { Flex } from '@backstage/ui';
<Flex />`;
export const defaultSnippet = `<Flex gap="4">
<Box style={{ width: '64px', height: '64px' }} />
<Box style={{ width: '64px', height: '64px' }} />
<Box style={{ width: '64px', height: '64px' }} />
</Flex>`;
export const flexSimpleSnippet = `<Flex gap="4">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const flexResponsiveSnippet = `<Flex gap={{ initial: '2', md: '4' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
export const flexAlignSnippet = `<Flex align={{ initial: 'start', md: 'center' }}>
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Flex>`;
@@ -1,7 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Flex/Flex.stories';
const { Default: DefaultStory } = stories;
export const Default = () => <DefaultStory.Component />;
@@ -0,0 +1,59 @@
'use client';
import { Grid } from '../../../../../packages/ui/src/components/Grid/Grid';
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
export const Default = () => {
return (
<Grid.Root columns="3" gap="4">
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
<Box
style={{
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
borderRadius: '4px',
}}
/>
</Grid.Root>
);
};
@@ -1,111 +0,0 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
const columnsValues = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
];
export const gridPropDefs: Record<string, PropDef> = {
columns: {
type: 'enum | string',
values: [...columnsValues, 'auto'],
responsive: true,
default: 'auto',
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const gridItemPropDefs: Record<string, PropDef> = {
colSpan: {
type: 'enum | string',
values: [...columnsValues, 'full'],
responsive: true,
},
rowSpan: {
type: 'enum | string',
values: [...columnsValues, 'full'],
responsive: true,
},
colStart: {
type: 'enum | string',
values: [...columnsValues, 'auto'],
responsive: true,
},
colEnd: {
type: 'enum | string',
values: [...columnsValues, 'auto'],
responsive: true,
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const gridUsageSnippet = `import { Grid } from '@backstage/ui';
<Grid.Root />`;
export const gridDefaultSnippet = `<Grid.Root>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Grid.Root>`;
export const gridSimpleSnippet = `<Grid.Root columns="3" gap="md">
<Box>Hello World</Box>
<Box>Hello World</Box>
<Box>Hello World</Box>
</Grid.Root>`;
export const gridComplexSnippet = `<Grid.Root columns="3" gap="md">
<Grid.Item colSpan="1">
Hello World
</Grid.Item>
<Grid.Item colSpan="2">
Hello World
</Grid.Item>
</Grid.Root>`;
export const gridMixingRowsSnippet = `<Grid.Root columns="3" gap="md">
<Grid.Item colSpan="1" rowSpan="2">
Hello World
</Grid.Item>
<Grid.Item colSpan="2">
Hello World
</Grid.Item>
<Grid.Item colSpan="2">
Hello World
</Grid.Item>
</Grid.Root>`;
export const gridResponsiveSnippet = `<Grid.Root columns={{ xs: 1, md: 3 }} gap={{ xs: 'xs', md: 'md' }}>
<Grid.Item colSpan={{ xs: 1, md: 2 }}>
<Hello World
</Grid.Item>
<Grid.Item colSpan={{ xs: 1, md: 1 }}>
Hello World
</Grid.Item>
</Grid.Root>`;
export const gridStartEndSnippet = `<Grid.Root columns="3" gap="md">
<Grid.Item colStart="2" colEnd="4">
Hello World
</Grid.Item>
</Grid.Root>`;
+4 -61
View File
@@ -1,19 +1,10 @@
import { CodeBlock } from '@/components/CodeBlock';
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Default } from './stories';
import { gridPropDefs } from './props-definition';
import { gridUsageSnippet, defaultSnippet } from './snippets';
import { Default } from './components';
import { spacingPropDefs } from '@/utils/propDefs';
import {
gridPropDefs,
gridItemPropDefs,
gridUsageSnippet,
gridDefaultSnippet,
gridSimpleSnippet,
gridComplexSnippet,
gridMixingRowsSnippet,
gridResponsiveSnippet,
gridStartEndSnippet,
} from './grid.props';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { GridDefinition } from '../../../utils/definitions';
@@ -25,11 +16,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
description="A layout component that helps to create simple column-based layouts as well as more complex ones."
/>
<Snippet
py={4}
preview={<Default />}
code={gridDefaultSnippet}
/>
<Snippet py={4} preview={<Default />} code={defaultSnippet} />
## Usage
@@ -49,50 +36,6 @@ The grid component also accepts all the spacing props from the Box component.
<PropsTable data={spacingPropDefs} />
### Grid.Item
If you need more control over the columns, you can use the grid item
component. This will give you access to `rowSpan`, `colSpan`, `start` and
`end`. All values are responsive. This component is optional, you can use any
elements directly if you prefer.
<PropsTable data={gridItemPropDefs} />
## Examples
### Simple grid
A simple grid with 3 columns and a gap of md.
<CodeBlock code={gridSimpleSnippet} />
### Complex grid
You can also use the grid item to create more complex layouts. In this example
the first column will span 1 column and the second column will span 2 columns.
<CodeBlock code={gridComplexSnippet} />
### Mixing rows and columns
The grid item component also supports the `rowSpan` prop, which allows you to
span multiple rows within the grid layout. In this example, the first item
will span 2 rows to achieve a dynamic and flexible grid structure.
<CodeBlock code={gridMixingRowsSnippet} />
### Responsive grid
The grid component also supports responsive values, making it easy to create
responsive designs.
<CodeBlock code={gridResponsiveSnippet} />
### Start and End
The start and end props can be used to position the item in the grid.
<CodeBlock code={gridStartEndSnippet} />
<Theming definition={GridDefinition} />
@@ -0,0 +1,22 @@
import {
childrenPropDefs,
classNamePropDefs,
gapPropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const gridPropDefs: Record<string, PropDef> = {
columns: {
type: 'string',
responsive: true,
},
rows: {
type: 'string',
responsive: true,
},
...gapPropDefs,
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,12 @@
export const gridUsageSnippet = `import { Grid } from '@backstage/ui';
<Grid />`;
export const defaultSnippet = `<Grid.Root columns="3" gap="4">
<Box style={{ height: '64px' }} />
<Box style={{ height: '64px' }} />
<Box style={{ height: '64px' }} />
<Box style={{ height: '64px' }} />
<Box style={{ height: '64px' }} />
<Box style={{ height: '64px' }} />
</Grid.Root>`;
@@ -1,7 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Grid/Grid.stories';
const { Default: DefaultStory } = stories;
export const Default = () => <DefaultStory.Component />;
@@ -0,0 +1,107 @@
'use client';
import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import {
MenuTrigger,
Menu,
MenuItem,
} from '../../../../../packages/ui/src/components/Menu/Menu';
import { MemoryRouter } from 'react-router-dom';
import { RiMore2Line } from '@remixicon/react';
const tabs = [
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'checks', label: 'Checks', href: '/checks' },
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
];
const menuItems = [
{ label: 'Settings', value: 'settings', href: '/settings' },
{
label: 'Invite new members',
value: 'invite-new-members',
href: '/invite-new-members',
},
{
label: 'Logout',
value: 'logout',
onClick: () => {
alert('logout');
},
},
];
const breadcrumbs = [
{ label: 'Home', href: '/' },
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
{ label: 'Another Long Breadcrumb', href: '/another-long-breadcrumb' },
{
label: 'Yet Another Long Breadcrumb',
href: '/yet-another-long-breadcrumb',
},
];
export const WithEverything = () => (
<MemoryRouter>
<HeaderPage
title="Page Title"
tabs={tabs}
breadcrumbs={breadcrumbs}
customActions={
<>
<Button variant="secondary">Secondary</Button>
<Button variant="primary">Primary</Button>
</>
}
/>
</MemoryRouter>
);
export const WithLongBreadcrumbs = () => (
<MemoryRouter>
<HeaderPage title="Page Title" breadcrumbs={breadcrumbs} />
</MemoryRouter>
);
export const WithTabs = () => (
<MemoryRouter>
<HeaderPage title="Page Title" tabs={tabs} />
</MemoryRouter>
);
export const WithCustomActions = () => (
<MemoryRouter>
<HeaderPage
title="Page Title"
customActions={<Button>Custom action</Button>}
/>
</MemoryRouter>
);
export const WithMenuItems = () => (
<MemoryRouter>
<HeaderPage
title="Page Title"
customActions={
<MenuTrigger>
<ButtonIcon variant="tertiary" icon={<RiMore2Line />} />
<Menu placement="bottom end">
{menuItems.map(option => (
<MenuItem
key={option.value}
onAction={option.onClick}
href={option.href}
>
{option.label}
</MenuItem>
))}
</Menu>
</MenuTrigger>
}
/>
</MemoryRouter>
);
@@ -1,17 +1,16 @@
import { PropsTable } from '@/components/PropsTable';
import { CodeBlock } from '@/components/CodeBlock';
import { Snippet } from '@/components/Snippet';
import { WithEverything, WithLongBreadcrumbs, WithTabs, WithCustomActions, WithMenuItems } from './stories';
import { WithEverything, WithLongBreadcrumbs, WithTabs, WithCustomActions, WithMenuItems } from './components';
import { propDefs } from './props-definition';
import {
propDefs,
usage,
simple,
defaultSnippet,
withTabs,
withBreadcrumbs,
withCustomActions,
withMenuItems,
} from './header-page.props';
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { HeaderPageDefinition } from '../../../utils/definitions';
@@ -87,55 +87,3 @@ export const propDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const usage = `import { HeaderPage } from '@backstage/ui';
<HeaderPage />`;
export const defaultSnippet = `<HeaderPage
title="Page Title"
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
menuItems={[
{ label: 'Settings', value: 'settings' },
{ label: 'Invite new members', value: 'invite-new-members' },
]}
customActions={<Button>Custom action</Button>}
/>`;
export const withBreadcrumbs = `<HeaderPage
title="Page Title"
breadcrumbs={[
{ label: 'Home', href: '/' },
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
]}
/>`;
export const withTabs = `<HeaderPage
title="Page Title"
tabs={[
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'checks', label: 'Checks', href: '/checks' },
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
]}
/>`;
export const withCustomActions = `<HeaderPage
title="Page Title"
customActions={<Button>Custom action</Button>}
/>`;
export const withMenuItems = `<HeaderPage
title="Page Title"
menuItems={[
{ label: 'Settings', value: 'settings', onClick: () => {} },
{ label: 'Invite new members', value: 'invite-new-members', onClick: () => {} },
]}
/>`;
@@ -0,0 +1,51 @@
export const usage = `import { HeaderPage } from '@backstage/ui';
<HeaderPage />`;
export const defaultSnippet = `<HeaderPage
title="Page Title"
tabs={[
{ id: 'overview', label: 'Overview' },
{ id: 'checks', label: 'Checks' },
{ id: 'tracks', label: 'Tracks' },
{ id: 'campaigns', label: 'Campaigns' },
{ id: 'integrations', label: 'Integrations' },
]}
menuItems={[
{ label: 'Settings', value: 'settings' },
{ label: 'Invite new members', value: 'invite-new-members' },
]}
customActions={<Button>Custom action</Button>}
/>`;
export const withBreadcrumbs = `<HeaderPage
title="Page Title"
breadcrumbs={[
{ label: 'Home', href: '/' },
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
]}
/>`;
export const withTabs = `<HeaderPage
title="Page Title"
tabs={[
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'checks', label: 'Checks', href: '/checks' },
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
]}
/>`;
export const withCustomActions = `<HeaderPage
title="Page Title"
customActions={<Button>Custom action</Button>}
/>`;
export const withMenuItems = `<HeaderPage
title="Page Title"
menuItems={[
{ label: 'Settings', value: 'settings', onClick: () => {} },
{ label: 'Invite new members', value: 'invite-new-members', onClick: () => {} },
]}
/>`;
@@ -1,17 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/HeaderPage/HeaderPage.stories';
const {
WithEverything: WithEverythingStory,
WithLongBreadcrumbs: WithLongBreadcrumbsStory,
WithTabs: WithTabsStory,
WithCustomActions: WithCustomActionsStory,
WithMenuItems: WithMenuItemsStory,
} = stories;
export const WithEverything = () => <WithEverythingStory.Component />;
export const WithLongBreadcrumbs = () => <WithLongBreadcrumbsStory.Component />;
export const WithTabs = () => <WithTabsStory.Component />;
export const WithCustomActions = () => <WithCustomActionsStory.Component />;
export const WithMenuItems = () => <WithMenuItemsStory.Component />;
@@ -0,0 +1,90 @@
'use client';
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { MemoryRouter } from 'react-router-dom';
import {
RiHeartLine,
RiEmotionHappyLine,
RiCloudy2Line,
} from '@remixicon/react';
const tabs = [
{ id: 'overview', label: 'Overview', href: '/overview' },
{ id: 'checks', label: 'Checks', href: '/checks' },
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
];
const tabs2 = [
{ id: 'Banana', label: 'Banana', href: '/banana' },
{ id: 'Apple', label: 'Apple', href: '/apple' },
{ id: 'Orange', label: 'Orange', href: '/orange' },
];
const breadcrumbs = [
{ label: 'Home', href: '/' },
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Settings', href: '/settings' },
];
export const WithAllOptionsAndTabs = () => (
<MemoryRouter>
<Header
tabs={tabs}
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
/>
</MemoryRouter>
);
export const WithAllOptions = () => (
<MemoryRouter>
<Header
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
/>
</MemoryRouter>
);
export const WithBreadcrumbs = () => (
<MemoryRouter>
<Header tabs={tabs} />
</MemoryRouter>
);
export const WithHeaderPage = () => (
<MemoryRouter>
<>
<Header
tabs={tabs}
customActions={
<>
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
</>
}
/>
<HeaderPage
title="Page title"
tabs={tabs2}
customActions={<Button>Custom action</Button>}
breadcrumbs={breadcrumbs}
/>
</>
</MemoryRouter>
);
+3 -3
View File
@@ -1,16 +1,16 @@
import { PropsTable } from '@/components/PropsTable';
import { CodeBlock } from '@/components/CodeBlock';
import { Snippet } from '@/components/Snippet';
import { WithAllOptionsAndTabs, WithAllOptions, WithBreadcrumbs, WithHeaderPage } from './stories';
import { WithAllOptionsAndTabs, WithAllOptions, WithBreadcrumbs, WithHeaderPage } from './components';
import { propDefs } from './props-definition';
import {
propDefs,
usage,
simple,
defaultSnippet,
withTabs,
withBreadcrumbs,
withHeaderPage,
} from './header.props';
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { HeaderDefinition } from '../../../utils/definitions';
@@ -0,0 +1,83 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const propDefs: Record<string, PropDef> = {
icon: {
type: 'enum',
values: ['ReactNode'],
},
title: {
type: 'string',
default: 'Your plugin',
},
titleLink: {
type: 'string',
default: '/',
},
customActions: {
type: 'enum',
values: ['ReactNode'],
},
menuItems: {
type: 'complex',
complexType: {
name: 'MenuItem[]',
properties: {
label: {
type: 'string',
required: true,
description: 'Display text for the menu item',
},
value: {
type: 'string',
required: true,
description: 'Unique value for the menu item',
},
onClick: {
type: '() => void',
required: false,
description: 'Callback function when menu item is clicked',
},
},
},
},
tabs: {
type: 'complex',
complexType: {
name: 'HeaderTab[]',
properties: {
id: {
type: 'string',
required: true,
description: 'Unique identifier for the tab',
},
label: {
type: 'string',
required: true,
description: 'Display text for the tab',
},
href: {
type: 'string',
required: false,
description: 'URL to navigate to when tab is clicked',
},
matchStrategy: {
type: "'exact' | 'prefix'",
required: false,
description: 'How to match the current route to highlight the tab',
},
},
},
},
onTabSelectionChange: {
type: 'enum',
values: ['(key: string) => void'],
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -1,87 +1,3 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const propDefs: Record<string, PropDef> = {
icon: {
type: 'enum',
values: ['ReactNode'],
},
title: {
type: 'string',
default: 'Your plugin',
},
titleLink: {
type: 'string',
default: '/',
},
customActions: {
type: 'enum',
values: ['ReactNode'],
},
menuItems: {
type: 'complex',
complexType: {
name: 'MenuItem[]',
properties: {
label: {
type: 'string',
required: true,
description: 'Display text for the menu item',
},
value: {
type: 'string',
required: true,
description: 'Unique value for the menu item',
},
onClick: {
type: '() => void',
required: false,
description: 'Callback function when menu item is clicked',
},
},
},
},
tabs: {
type: 'complex',
complexType: {
name: 'HeaderTab[]',
properties: {
id: {
type: 'string',
required: true,
description: 'Unique identifier for the tab',
},
label: {
type: 'string',
required: true,
description: 'Display text for the tab',
},
href: {
type: 'string',
required: false,
description: 'URL to navigate to when tab is clicked',
},
matchStrategy: {
type: "'exact' | 'prefix'",
required: false,
description: 'How to match the current route to highlight the tab',
},
},
},
},
onTabSelectionChange: {
type: 'enum',
values: ['(key: string) => void'],
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const usage = `import { Header } from '@backstage/ui';
<Header />`;
@@ -1,17 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Header/Header.stories';
const {
WithAllOptionsAndTabs: WithAllOptionsAndTabsStory,
WithAllOptions: WithAllOptionsStory,
WithBreadcrumbs: WithBreadcrumbsStory,
WithHeaderPage: WithHeaderPageStory,
} = stories;
export const WithAllOptionsAndTabs = () => (
<WithAllOptionsAndTabsStory.Component />
);
export const WithAllOptions = () => <WithAllOptionsStory.Component />;
export const WithBreadcrumbs = () => <WithBreadcrumbsStory.Component />;
export const WithHeaderPage = () => <WithHeaderPageStory.Component />;
@@ -0,0 +1,111 @@
'use client';
import { Link } from '../../../../../packages/ui/src/components/Link/Link';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
import { MemoryRouter } from 'react-router-dom';
export const Default = () => {
return (
<MemoryRouter>
<Link href="/">Sign up for Backstage</Link>
</MemoryRouter>
);
};
export const ExternalLink = () => {
return (
<MemoryRouter>
<Link href="https://backstage.io" target="_blank">
Sign up for Backstage
</Link>
</MemoryRouter>
);
};
export const AllVariants = () => {
return (
<MemoryRouter>
<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" variant="title-large">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="title-medium">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="title-small">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="title-x-small">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-large">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-medium">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-small">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-x-small">
Sign up for Backstage
</Link>
</Flex>
</MemoryRouter>
);
};
export const AllColors = () => {
return (
<MemoryRouter>
<Flex gap="4" direction="column">
<Link
href="https://ui.backstage.io"
variant="title-small"
color="primary"
>
I am primary
</Link>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="secondary"
>
I am secondary
</Link>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="tertiary"
>
I am tertiary
</Link>
<Link
href="https://ui.backstage.io"
variant="title-small"
color="inherit"
>
I am inherit
</Link>
</Flex>
</MemoryRouter>
);
};
export const Underline = () => {
return (
<MemoryRouter>
<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" underline="always">
Always underlined
</Link>
<Link href="https://ui.backstage.io" underline="hover">
Underlined on hover
</Link>
<Link href="https://ui.backstage.io" underline="none">
Never underlined
</Link>
</Flex>
</MemoryRouter>
);
};
@@ -1,81 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const linkPropDefs: Record<string, PropDef> = {
href: {
type: 'string',
},
variant: {
type: 'enum',
values: [
'title-large',
'title-medium',
'title-small',
'title-x-small',
'body-large',
'body-medium',
'body-small',
'body-x-small',
],
default: 'body-medium',
responsive: true,
},
weight: {
type: 'enum',
values: ['regular', 'bold'],
default: 'regular',
responsive: true,
},
color: {
type: 'enum',
values: ['primary', 'secondary', 'danger', 'warning', 'success'],
default: 'primary',
responsive: true,
},
...classNamePropDefs,
...stylePropDefs,
};
export const linkUsageSnippet = `import { Link } from '@backstage/ui';
<Link href="/sign-up">Sign up for Backstage</Link>`;
export const linkDefaultSnippet = `<Link href="/">Sign up for Backstage</Link>`;
export const linkVariantsSnippet = `<Flex gap="4" direction="column">
<Link href="/" variant="title-large">...</Link>
<Link href="/" variant="title-medium">...</Link>
<Link href="/" variant="title-small">...</Link>
<Link href="/" variant="title-x-small">...</Link>
<Link href="/" variant="body-large">...</Link>
<Link href="/" variant="body-medium">...</Link>
<Link href="/" variant="body-small">...</Link>
<Link href="/" variant="body-x-small">...</Link>
</Flex>`;
export const linkWeightsSnippet = `<Flex gap="4" direction="column">
<Link href="/" weight="regular" />
<Link href="/" weight="bold" />
</Flex>`;
export const linkColorsSnippet = `<Flex gap="4" direction="column">
<Link href="/" color="primary">I am primary</Link>
<Link href="/" color="secondary">I am secondary</Link>
<Link href="/" color="danger">I am danger</Link>
<Link href="/" color="warning">I am warning</Link>
<Link href="/" color="success">I am success</Link>
</Flex>`;
export const linkRouterSnippet = `import { Link } from '@backstage/ui';
// Internal route
<Link href="/home">Home</Link>
// External URL
<Link href="https://backstage.io">Backstage</Link>
`;
export const linkTruncateSnippet = `<Link href="/" truncate>...</Link>`;
+29 -42
View File
@@ -1,17 +1,16 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Default, AllVariants, AllWeights, AllColors, Truncate } from './stories';
import { linkPropDefs } from './props-definition';
import {
linkPropDefs,
linkUsageSnippet,
linkDefaultSnippet,
linkVariantsSnippet,
linkWeightsSnippet,
linkColorsSnippet,
linkRouterSnippet,
linkTruncateSnippet,
} from './link.props';
defaultSnippet,
externalLinkSnippet,
allVariantsSnippet,
allColorsSnippet,
underlineSnippet,
} from './snippets';
import { Default, ExternalLink, AllVariants, AllColors, Underline } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
@@ -22,12 +21,7 @@ import { LinkDefinition } from '../../../utils/definitions';
description="A link component that renders a `<a>` element."
/>
<Snippet
align="center"
py={4}
preview={<Default />}
code={linkDefaultSnippet}
/>
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
## Usage
@@ -37,17 +31,20 @@ import { LinkDefinition } from '../../../utils/definitions';
<PropsTable data={linkPropDefs} />
## Router Integration
The `Link` component handles both internal and external navigation. It automatically detects whether the provided URL is internal (relative path) or external (absolute URL with protocol) and renders the appropriate element:
- **Internal routes**: Uses `react-router-dom`'s `Link` component for client-side navigation
- **External URLs**: Renders a standard `<a>` element for traditional navigation
<CodeBlock language="tsx" code={linkRouterSnippet} />
## Examples
### External Link
Here's how to create an external link that opens in a new tab.
<Snippet
align="center"
py={4}
open
preview={<ExternalLink />}
code={externalLinkSnippet}
/>
### Variants
Here's a view when links have different variants.
@@ -57,19 +54,7 @@ Here's a view when links have different variants.
py={4}
open
preview={<AllVariants />}
code={linkVariantsSnippet}
/>
### Weights
Here's a view when links have different weights.
<Snippet
align="center"
py={4}
open
preview={<AllWeights />}
code={linkWeightsSnippet}
code={allVariantsSnippet}
/>
### Colors
@@ -81,17 +66,19 @@ Here's a view when links have different colors.
py={4}
open
preview={<AllColors />}
code={linkColorsSnippet}
code={allColorsSnippet}
/>
### Truncate
### Underline
The `Link` component has a `truncate` prop that can be used to truncate the text.
Here's a view when links have different underline styles.
<Snippet
align="center"
py={4}
open
preview={<Truncate />}
code={linkTruncateSnippet}
preview={<Underline />}
code={underlineSnippet}
/>
<Theming definition={LinkDefinition} />
@@ -0,0 +1,45 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const linkPropDefs: Record<string, PropDef> = {
href: {
type: 'string',
required: true,
},
target: {
type: 'enum',
values: ['_self', '_blank', '_parent', '_top'],
},
variant: {
type: 'enum',
values: [
'title-large',
'title-medium',
'title-small',
'title-x-small',
'body-large',
'body-medium',
'body-small',
'body-x-small',
],
default: 'body-medium',
responsive: true,
},
color: {
type: 'enum',
values: ['primary', 'secondary', 'tertiary', 'inherit'],
default: 'primary',
},
underline: {
type: 'enum',
values: ['always', 'hover', 'none'],
default: 'hover',
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,54 @@
export const linkUsageSnippet = `import { Link } from '@backstage/ui';
<Link href="/">Sign up for Backstage</Link>`;
export const defaultSnippet = `<Link href="/">Sign up for Backstage</Link>`;
export const externalLinkSnippet = `<Link href="https://backstage.io" target="_blank">
Sign up for Backstage
</Link>`;
export const allVariantsSnippet = `<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" variant="title-large">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="title-medium">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="title-small">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-large">
Sign up for Backstage
</Link>
<Link href="https://ui.backstage.io" variant="body-medium">
Sign up for Backstage
</Link>
</Flex>`;
export const allColorsSnippet = `<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" variant="title-small" color="primary">
I am primary
</Link>
<Link href="https://ui.backstage.io" variant="title-small" color="secondary">
I am secondary
</Link>
<Link href="https://ui.backstage.io" variant="title-small" color="tertiary">
I am tertiary
</Link>
<Link href="https://ui.backstage.io" variant="title-small" color="inherit">
I am inherit
</Link>
</Flex>`;
export const underlineSnippet = `<Flex gap="4" direction="column">
<Link href="https://ui.backstage.io" underline="always">
Always underlined
</Link>
<Link href="https://ui.backstage.io" underline="hover">
Underlined on hover
</Link>
<Link href="https://ui.backstage.io" underline="none">
Never underlined
</Link>
</Flex>`;
@@ -1,17 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Link/Link.stories';
const {
Default: DefaultStory,
AllVariants: AllVariantsStory,
AllWeights: AllWeightsStory,
AllColors: AllColorsStory,
Truncate: TruncateStory,
} = stories;
export const Default = () => <DefaultStory.Component />;
export const AllVariants = () => <AllVariantsStory.Component />;
export const AllWeights = () => <AllWeightsStory.Component />;
export const AllColors = () => <AllColorsStory.Component />;
export const Truncate = () => <TruncateStory.Component />;
@@ -0,0 +1,143 @@
'use client';
import {
MenuTrigger,
Menu,
MenuItem,
MenuSection,
MenuSeparator,
SubmenuTrigger,
MenuAutocomplete,
MenuListBoxItem,
} from '../../../../../packages/ui/src/components/Menu/Menu';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
import { MemoryRouter } from 'react-router-dom';
import { RiFileLine, RiFolderLine, RiImageLine } from '@remixicon/react';
export const Preview = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New File</MenuItem>
<MenuItem>Open File</MenuItem>
<MenuItem>Save</MenuItem>
<MenuItem>Save As...</MenuItem>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewSubmenu = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New File</MenuItem>
<SubmenuTrigger>
<MenuItem>Open Recent</MenuItem>
<Menu>
<MenuItem>File 1.txt</MenuItem>
<MenuItem>File 2.txt</MenuItem>
<MenuItem>File 3.txt</MenuItem>
</Menu>
</SubmenuTrigger>
<MenuItem>Save</MenuItem>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewIcons = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem icon={<RiFileLine />}>New File</MenuItem>
<MenuItem icon={<RiFolderLine />}>New Folder</MenuItem>
<MenuItem icon={<RiImageLine />}>New Image</MenuItem>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewLinks = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem href="/home">Home</MenuItem>
<MenuItem href="/about">About</MenuItem>
<MenuItem href="/contact">Contact</MenuItem>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewSections = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuSection title="File">
<MenuItem>New</MenuItem>
<MenuItem>Open</MenuItem>
</MenuSection>
<MenuSection title="Edit">
<MenuItem>Cut</MenuItem>
<MenuItem>Copy</MenuItem>
<MenuItem>Paste</MenuItem>
</MenuSection>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewSeparators = () => (
<MemoryRouter>
<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New</MenuItem>
<MenuItem>Open</MenuItem>
<MenuSeparator />
<MenuItem>Save</MenuItem>
<MenuItem>Save As...</MenuItem>
</Menu>
</MenuTrigger>
</MemoryRouter>
);
export const PreviewAutocompleteMenu = () => (
<MemoryRouter>
<MenuAutocomplete label="Search" placeholder="Type to search...">
<MenuItem>Option 1</MenuItem>
<MenuItem>Option 2</MenuItem>
<MenuItem>Option 3</MenuItem>
</MenuAutocomplete>
</MemoryRouter>
);
export const PreviewAutocompleteListbox = () => (
<MemoryRouter>
<MenuAutocomplete label="Select an option" placeholder="Type to filter...">
<MenuListBoxItem>Option 1</MenuListBoxItem>
<MenuListBoxItem>Option 2</MenuListBoxItem>
<MenuListBoxItem>Option 3</MenuListBoxItem>
</MenuAutocomplete>
</MemoryRouter>
);
export const PreviewAutocompleteListboxMultiple = () => (
<MemoryRouter>
<MenuAutocomplete
label="Select multiple options"
placeholder="Type to filter..."
selectionMode="multiple"
>
<MenuListBoxItem>Option 1</MenuListBoxItem>
<MenuListBoxItem>Option 2</MenuListBoxItem>
<MenuListBoxItem>Option 3</MenuListBoxItem>
</MenuAutocomplete>
</MemoryRouter>
);
@@ -1,372 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
const placementValues = [
'bottom',
'bottom left',
'bottom right',
'bottom start',
'bottom end',
'top',
'top left',
'top right',
'top start',
'top end',
'left',
'left top',
'left bottom',
'start',
'start top',
'start bottom',
'right',
'right top',
'right bottom',
'end',
'end top',
'end bottom',
];
export const menuTriggerPropDefs: Record<string, PropDef> = {
isOpen: {
type: 'boolean',
},
defaultOpen: {
type: 'boolean',
},
onOpenChange: {
type: 'enum',
values: ['(isOpen: boolean) => void'],
},
};
export const submenuTriggerPropDefs: Record<string, PropDef> = {
delay: {
type: 'number',
default: '200',
},
};
export const menuPropDefs: Record<string, PropDef> = {
disabledKeys: {
type: 'enum',
values: ['Iterable<Key>'],
},
selectionMode: {
type: 'enum',
values: ['none', 'single', 'multiple'],
},
selectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
defaultSelectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
placement: {
type: 'enum',
values: placementValues,
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuListBoxPropDefs: Record<string, PropDef> = {
disabledKeys: {
type: 'enum',
values: ['Iterable<Key>'],
},
selectionMode: {
type: 'enum',
values: ['none', 'single', 'multiple'],
},
selectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
defaultSelectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
placement: {
type: 'enum',
values: placementValues,
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuAutocompletePropDefs: Record<string, PropDef> = {
placement: {
type: 'enum',
values: placementValues,
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuAutocompleteListboxPropDefs: Record<string, PropDef> = {
placement: {
type: 'enum',
values: placementValues,
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuItemPropDefs: Record<string, PropDef> = {
id: {
type: 'enum',
values: ['Key'],
},
value: {
type: 'string',
},
textValue: {
type: 'string',
},
isDisabled: {
type: 'boolean',
},
href: {
type: 'string',
},
onAction: {
type: 'enum',
values: ['(event) => void'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuListBoxItemPropDefs: Record<string, PropDef> = {
id: {
type: 'enum',
values: ['Key'],
},
value: {
type: 'string',
},
textValue: {
type: 'string',
},
isDisabled: {
type: 'boolean',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuSectionPropDefs: Record<string, PropDef> = {
title: {
type: 'string',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuSeparatorPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
export const usage = `import { MenuTrigger, Menu, MenuItem } from '@backstage/ui';
<MenuTrigger>
<Button>Menu</Button>
<Menu>
<MenuItem id="apple">Apple</MenuItem>
<MenuItem id="banana">Banana</MenuItem>
<MenuItem id="blueberry">Blueberry</MenuItem>
<MenuSeparator />
<SubmenuTrigger>
<MenuItem>Vegetables</MenuItem>
<Menu>
<MenuItem id="carrot">Carrot</MenuItem>
<MenuItem id="tomato">Tomato</MenuItem>
<MenuItem id="potato">Potato</MenuItem>
</Menu>
</SubmenuTrigger>
</Menu>
</MenuTrigger>`;
export const preview = `<MenuTrigger>
<Button>Menu</Button>
<Menu>
{options.map(option => (
<MenuItem key={option.value}>{option.label}</MenuItem>
))}
</Menu>
</MenuTrigger>`;
export const submenu = `<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<SubmenuTrigger>
<MenuItem>Submenu</MenuItem>
<Menu placement="right top">
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuItem>Rename</MenuItem>
<MenuSeparator />
<MenuItem>Share</MenuItem>
<MenuItem>Move</MenuItem>
<MenuSeparator />
<MenuItem iconStart={<RiChat1Line />}>Feedback</MenuItem>
</Menu>
</SubmenuTrigger>
</Menu>
</MenuTrigger>`;
export const icons = `<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<Menu>
<MenuItem iconStart={<RiFileCopyLine />}>Copy</MenuItem>
<MenuItem iconStart={<RiEdit2Line />}>Rename</MenuItem>
<MenuItem iconStart={<RiChat1Line />}>Send feedback</MenuItem>
</Menu>
</MenuTrigger>`;
export const sections = `<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<Menu>
<MenuSection title="My Account">
<MenuItem iconStart={<RiUserLine />}>Profile</MenuItem>
<MenuItem iconStart={<RiSettingsLine />}>Settings</MenuItem>
</MenuSection>
<MenuSection title="Support">
<MenuItem iconStart={<RiQuestionLine />}>Help Center</MenuItem>
<MenuItem iconStart={<RiCustomerService2Line />}>
Contact Support
</MenuItem>
<MenuItem iconStart={<RiChat1Line />}>Feedback</MenuItem>
</MenuSection>
</Menu>
</MenuTrigger>`;
export const separators = `<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<Menu>
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuItem>Rename</MenuItem>
<MenuSeparator />
<MenuItem>Share</MenuItem>
<MenuItem>Move</MenuItem>
<MenuSeparator />
<MenuItem iconStart={<RiChat1Line />}>Feedback</MenuItem>
</Menu>
</MenuTrigger>`;
export const links = `<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<Menu>
<MenuItem href="/home">Internal link</MenuItem>
<MenuItem href="https://www.google.com" target="_blank">
External link
</MenuItem>
<MenuItem href="mailto:test@test.com">Email link</MenuItem>
</Menu>
</MenuTrigger>`;
export const autocomplete = `<MenuTrigger isOpen>
<Button aria-label="Menu">Menu</Button>
<MenuAutocomplete placeholder="Filter">
<MenuItem>Create new file...</MenuItem>
<MenuItem>Create new folder...</MenuItem>
<MenuItem>Assign to...</MenuItem>
<MenuItem>Assign to me</MenuItem>
<MenuItem>Change status...</MenuItem>
<MenuItem>Change priority...</MenuItem>
<MenuItem>Add label...</MenuItem>
<MenuItem>Remove label...</MenuItem>
</MenuAutocomplete>
</MenuTrigger>`;
export const autocompleteListbox = `const [selected, setSelected] = useState<Selection>(
new Set(['blueberry']),
);
<Flex direction="column" gap="2" align="start">
<Text>Selected: {Array.from(selected).join(', ')}</Text>
<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<MenuAutocompleteListbox
selectedKeys={selected}
onSelectionChange={setSelected}
>
{options.map(option => (
<MenuListBoxItem key={option.value} id={option.value}>
{option.label}
</MenuListBoxItem>
))}
</MenuAutocompleteListbox>
</MenuTrigger>
</Flex>`;
export const autocompleteListboxMultiple = `const [selected, setSelected] = useState<Selection>(
new Set(['blueberry', 'cherry']),
);
<Flex direction="column" gap="2" align="start">
<Text>Selected: {Array.from(selected).join(', ')}</Text>
<MenuTrigger>
<Button aria-label="Menu">Menu</Button>
<MenuAutocompleteListbox
selectionMode="multiple"
selectedKeys={selected}
onSelectionChange={setSelected}
>
{options.map(option => (
<MenuListBoxItem key={option.value} id={option.value}>
{option.label}
</MenuListBoxItem>
))}
</MenuAutocompleteListbox>
</MenuTrigger>
</Flex>`;
+14 -12
View File
@@ -1,7 +1,19 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import { Preview, PreviewSubmenu, PreviewIcons, PreviewLinks, PreviewSections, PreviewSeparators, PreviewAutocompleteMenu, PreviewAutocompleteListbox, PreviewAutocompleteListboxMultiple } from './stories';
import { Preview, PreviewSubmenu, PreviewIcons, PreviewLinks, PreviewSections, PreviewSeparators, PreviewAutocompleteMenu, PreviewAutocompleteListbox, PreviewAutocompleteListboxMultiple } from './components';
import {
menuTriggerPropDefs,
submenuTriggerPropDefs,
menuPropDefs,
menuListBoxPropDefs,
menuAutocompletePropDefs,
menuAutocompleteListboxPropDefs,
menuItemPropDefs,
menuListBoxItemPropDefs,
menuSectionPropDefs,
menuSeparatorPropDefs,
} from './props-definition';
import {
usage,
preview,
@@ -13,17 +25,7 @@ import {
autocomplete,
autocompleteListbox,
autocompleteListboxMultiple,
menuTriggerPropDefs,
submenuTriggerPropDefs,
menuPropDefs,
menuListBoxPropDefs,
menuAutocompletePropDefs,
menuAutocompleteListboxPropDefs,
menuItemPropDefs,
menuListBoxItemPropDefs,
menuSectionPropDefs,
menuSeparatorPropDefs,
} from './menu.props';
} from './snippets';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
@@ -0,0 +1,241 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const menuTriggerPropDefs: Record<string, PropDef> = {
isOpen: {
type: 'boolean',
},
defaultOpen: {
type: 'boolean',
},
onOpenChange: {
type: 'enum',
values: ['(isOpen: boolean) => void'],
},
};
export const submenuTriggerPropDefs: Record<string, PropDef> = {
delay: {
type: 'number',
default: '200',
},
};
export const menuPropDefs: Record<string, PropDef> = {
disabledKeys: {
type: 'enum',
values: ['Iterable<Key>'],
},
selectionMode: {
type: 'enum',
values: ['none', 'single', 'multiple'],
},
selectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
defaultSelectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
placement: {
type: 'enum',
values: [
'top',
'bottom',
'left',
'right',
'top start',
'top end',
'bottom start',
'bottom end',
'left start',
'left end',
'right start',
'right end',
],
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuListBoxPropDefs: Record<string, PropDef> = {
disabledKeys: {
type: 'enum',
values: ['Iterable<Key>'],
},
selectionMode: {
type: 'enum',
values: ['none', 'single', 'multiple'],
},
selectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
defaultSelectedKeys: {
type: 'enum',
values: ['all', 'Iterable<Key>'],
},
placement: {
type: 'enum',
values: [
'top',
'bottom',
'left',
'right',
'top start',
'top end',
'bottom start',
'bottom end',
'left start',
'left end',
'right start',
'right end',
],
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuAutocompletePropDefs: Record<string, PropDef> = {
placement: {
type: 'enum',
values: [
'top',
'bottom',
'left',
'right',
'top start',
'top end',
'bottom start',
'bottom end',
'left start',
'left end',
'right start',
'right end',
],
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuAutocompleteListboxPropDefs: Record<string, PropDef> = {
placement: {
type: 'enum',
values: [
'top',
'bottom',
'left',
'right',
'top start',
'top end',
'bottom start',
'bottom end',
'left start',
'left end',
'right start',
'right end',
],
},
virtualized: {
type: 'boolean',
default: 'false',
},
maxWidth: {
type: 'number',
},
maxHeight: {
type: 'number',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuItemPropDefs: Record<string, PropDef> = {
id: {
type: 'enum',
values: ['Key'],
},
value: {
type: 'string',
},
textValue: {
type: 'string',
},
isDisabled: {
type: 'boolean',
},
href: {
type: 'string',
},
onAction: {
type: 'enum',
values: ['(event) => void'],
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuListBoxItemPropDefs: Record<string, PropDef> = {
id: {
type: 'enum',
values: ['Key'],
},
value: {
type: 'string',
},
textValue: {
type: 'string',
},
isDisabled: {
type: 'boolean',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuSectionPropDefs: Record<string, PropDef> = {
title: {
type: 'string',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuSeparatorPropDefs: Record<string, PropDef> = {
...classNamePropDefs,
...stylePropDefs,
};
+100
View File
@@ -0,0 +1,100 @@
export const usage = `import { MenuTrigger, Menu, MenuItem } from '@backstage/ui';
<MenuTrigger>
<Button>Menu</Button>
<Menu>
<MenuItem>Item 1</MenuItem>
<MenuItem>Item 2</MenuItem>
</Menu>
</MenuTrigger>`;
export const preview = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New File</MenuItem>
<MenuItem>Open File</MenuItem>
<MenuItem>Save</MenuItem>
<MenuItem>Save As...</MenuItem>
</Menu>
</MenuTrigger>`;
export const submenu = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New File</MenuItem>
<SubmenuTrigger>
<MenuItem>Open Recent</MenuItem>
<Menu>
<MenuItem>File 1.txt</MenuItem>
<MenuItem>File 2.txt</MenuItem>
</Menu>
</SubmenuTrigger>
<MenuItem>Save</MenuItem>
</Menu>
</MenuTrigger>`;
export const icons = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem icon={<RiFileLine />}>New File</MenuItem>
<MenuItem icon={<RiFolderLine />}>New Folder</MenuItem>
<MenuItem icon={<RiImageLine />}>New Image</MenuItem>
</Menu>
</MenuTrigger>`;
export const sections = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuSection title="File">
<MenuItem>New</MenuItem>
<MenuItem>Open</MenuItem>
</MenuSection>
<MenuSection title="Edit">
<MenuItem>Cut</MenuItem>
<MenuItem>Copy</MenuItem>
<MenuItem>Paste</MenuItem>
</MenuSection>
</Menu>
</MenuTrigger>`;
export const separators = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem>New</MenuItem>
<MenuItem>Open</MenuItem>
<MenuSeparator />
<MenuItem>Save</MenuItem>
<MenuItem>Save As...</MenuItem>
</Menu>
</MenuTrigger>`;
export const links = `<MenuTrigger>
<Button variant="secondary">Menu</Button>
<Menu>
<MenuItem href="/home">Home</MenuItem>
<MenuItem href="/about">About</MenuItem>
<MenuItem href="/contact">Contact</MenuItem>
</Menu>
</MenuTrigger>`;
export const autocomplete = `<MenuAutocomplete label="Search" placeholder="Type to search...">
<MenuItem>Option 1</MenuItem>
<MenuItem>Option 2</MenuItem>
<MenuItem>Option 3</MenuItem>
</MenuAutocomplete>`;
export const autocompleteListbox = `<MenuAutocomplete label="Select an option" placeholder="Type to filter...">
<MenuListBoxItem>Option 1</MenuListBoxItem>
<MenuListBoxItem>Option 2</MenuListBoxItem>
<MenuListBoxItem>Option 3</MenuListBoxItem>
</MenuAutocomplete>`;
export const autocompleteListboxMultiple = `<MenuAutocomplete
label="Select multiple options"
placeholder="Type to filter..."
selectionMode="multiple"
>
<MenuListBoxItem>Option 1</MenuListBoxItem>
<MenuListBoxItem>Option 2</MenuListBoxItem>
<MenuListBoxItem>Option 3</MenuListBoxItem>
</MenuAutocomplete>`;
@@ -1,31 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/Menu/Menu.stories';
const {
Preview: PreviewStory,
PreviewSubmenu: PreviewSubmenuStory,
PreviewIcons: PreviewIconsStory,
PreviewLinks: PreviewLinksStory,
PreviewSections: PreviewSectionsStory,
PreviewSeparators: PreviewSeparatorsStory,
PreviewAutocompleteMenu: PreviewAutocompleteMenuStory,
PreviewAutocompleteListbox: PreviewAutocompleteListboxStory,
PreviewAutocompleteListboxMultiple: PreviewAutocompleteListboxMultipleStory,
} = stories;
export const Preview = () => <PreviewStory.Component />;
export const PreviewSubmenu = () => <PreviewSubmenuStory.Component />;
export const PreviewIcons = () => <PreviewIconsStory.Component />;
export const PreviewLinks = () => <PreviewLinksStory.Component />;
export const PreviewSections = () => <PreviewSectionsStory.Component />;
export const PreviewSeparators = () => <PreviewSeparatorsStory.Component />;
export const PreviewAutocompleteMenu = () => (
<PreviewAutocompleteMenuStory.Component />
);
export const PreviewAutocompleteListbox = () => (
<PreviewAutocompleteListboxStory.Component />
);
export const PreviewAutocompleteListboxMultiple = () => (
<PreviewAutocompleteListboxMultipleStory.Component />
);
@@ -0,0 +1,34 @@
'use client';
import { PasswordField } from '../../../../../packages/ui/src/components/PasswordField/PasswordField';
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
export const WithLabel = () => {
return (
<PasswordField
name="password"
label="Password"
style={{ maxWidth: '300px' }}
/>
);
};
export const Sizes = () => {
return (
<Flex direction="row" gap="4" style={{ width: '100%', maxWidth: '600px' }}>
<PasswordField name="password" label="Password" size="small" />
<PasswordField name="password" label="Password" size="medium" />
</Flex>
);
};
export const WithDescription = () => {
return (
<PasswordField
name="password"
label="Password"
description="Enter your password"
style={{ maxWidth: '300px' }}
/>
);
};
@@ -1,30 +1,25 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { inputPropDefs } from './props-definition';
import {
inputPropDefs,
passwordFieldUsageSnippet,
passwordFieldDefaultSnippet,
passwordFieldSizesSnippet,
passwordFieldDescriptionSnippet,
} from './password-field.props';
withLabelSnippet,
sizesSnippet,
withDescriptionSnippet,
} from './snippets';
import { WithLabel, Sizes, WithDescription } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { ChangelogComponent } from '@/components/ChangelogComponent';
import { CodeBlock } from '@/components/CodeBlock';
import { PasswordFieldDefinition } from '../../../utils/definitions';
import { WithLabel, Sizes, WithDescription } from './stories';
<PageTitle
title="PasswordField"
description="A password field component for your forms."
/>
<Snippet
align="center"
py={4}
preview={<WithLabel />}
code={passwordFieldDefaultSnippet}
/>
<Snippet align="center" py={4} preview={<WithLabel />} code={withLabelSnippet} />
## Usage
@@ -45,7 +40,7 @@ We support two different sizes: `small`, `medium`.
py={4}
open
preview={<Sizes />}
code={passwordFieldSizesSnippet}
code={sizesSnippet}
/>
### With description
@@ -57,7 +52,7 @@ Here's a simple PasswordField with a description.
py={4}
open
preview={<WithDescription />}
code={passwordFieldDescriptionSnippet}
code={withDescriptionSnippet}
/>
<Theming definition={PasswordFieldDefinition} />
@@ -1,43 +0,0 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const inputPropDefs: Record<string, PropDef> = {
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'small',
responsive: true,
},
label: {
type: 'string',
},
icon: {
type: 'enum',
values: ['ReactNode'],
},
description: {
type: 'string',
},
name: {
type: 'string',
required: true,
},
...classNamePropDefs,
...stylePropDefs,
};
export const passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui';
<PasswordField />`;
export const passwordFieldDefaultSnippet = `<PasswordField label="Label" placeholder="Enter a secret" />`;
export const passwordFieldSizesSnippet = `<Flex direction="row" gap="4">
<PasswordField size="small" placeholder="Small" icon={<Icon name="sparkling" />} />
<PasswordField size="medium" placeholder="Medium" icon={<Icon name="sparkling" />} />
</Flex>`;
export const passwordFieldDescriptionSnippet = `<PasswordField label="Label" description="Description" placeholder="Enter a secret" />`;
@@ -0,0 +1,26 @@
import {
classNamePropDefs,
stylePropDefs,
type PropDef,
} from '@/utils/propDefs';
export const inputPropDefs: Record<string, PropDef> = {
size: {
type: 'enum',
values: ['small', 'medium'],
default: 'small',
responsive: true,
},
label: {
type: 'string',
},
description: {
type: 'string',
},
name: {
type: 'string',
required: true,
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,19 @@
export const passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui';
<PasswordField />`;
export const withLabelSnippet = `<PasswordField
name="password"
label="Password"
/>`;
export const sizesSnippet = `<Flex direction="row" gap="4">
<PasswordField name="password" label="Password" size="small" />
<PasswordField name="password" label="Password" size="medium" />
</Flex>`;
export const withDescriptionSnippet = `<PasswordField
name="password"
label="Password"
description="Enter your password"
/>`;
@@ -1,13 +0,0 @@
'use client';
import * as stories from '@backstage/ui/src/components/PasswordField/PasswordField.stories';
const {
WithLabel: WithLabelStory,
Sizes: SizesStory,
WithDescription: WithDescriptionStory,
} = stories;
export const WithLabel = () => <WithLabelStory.Component />;
export const Sizes = () => <SizesStory.Component />;
export const WithDescription = () => <WithDescriptionStory.Component />;
@@ -0,0 +1,18 @@
'use client';
import { Popover } from '../../../../../packages/ui/src/components/Popover/Popover';
import { DialogTrigger } from '../../../../../packages/ui/src/components/Dialog/Dialog';
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
export const Default = () => {
return (
<DialogTrigger>
<Button>Open Popover</Button>
<Popover>
<div style={{ padding: '16px' }}>
<p>This is a popover</p>
</div>
</Popover>
</DialogTrigger>
);
};
+10 -68
View File
@@ -1,16 +1,9 @@
import { PropsTable } from '@/components/PropsTable';
import { Default } from './stories';
import { Snippet } from '@/components/Snippet';
import { CodeBlock } from '@/components/CodeBlock';
import {
popoverPropDefs,
popoverUsageSnippet,
popoverDefaultSnippet,
popoverPlacementsSnippet,
popoverNoArrowSnippet,
popoverRichContentSnippet,
popoverNonModalSnippet,
} from './popover.props';
import { dialogTriggerPropDefs, popoverPropDefs } from './props-definition';
import { popoverUsageSnippet, defaultSnippet } from './snippets';
import { Default } from './components';
import { PageTitle } from '@/components/PageTitle';
import { Theming } from '@/components/Theming';
import { PopoverDefinition } from '../../../utils/definitions';
@@ -25,7 +18,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
align="center"
py={4}
preview={<Default />}
code={popoverDefaultSnippet}
code={defaultSnippet}
/>
## Usage
@@ -34,69 +27,18 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
## API reference
### DialogTrigger
The trigger component that wraps both the trigger button and the popover content.
<PropsTable data={dialogTriggerPropDefs} />
### 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={<Default />}
code={popoverPlacementsSnippet}
/>
### Without Arrow
You can hide the arrow by setting the `hideArrow` prop.
<Snippet
align="center"
py={4}
preview={<Default />}
code={popoverNoArrowSnippet}
/>
### Rich Content
Popovers can contain complex content including buttons, forms, and other interactive elements.
<Snippet
align="center"
py={4}
preview={<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={<Default />}
code={popoverNonModalSnippet}
/>
### Long Content
Popovers automatically handle scrolling when content exceeds the available viewport space.
<Snippet
align="center"
py={4}
preview={<Default />}
code={popoverDefaultSnippet}
/>
<Theming definition={PopoverDefinition} />
<ChangelogComponent component="popover" />
@@ -1,174 +0,0 @@
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>`;
@@ -0,0 +1,55 @@
import {
childrenPropDefs,
classNamePropDefs,
stylePropDefs,
} from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const dialogTriggerPropDefs: Record<string, PropDef> = {
isDisabled: {
type: 'boolean',
},
delay: {
type: 'number',
default: '0',
},
closeDelay: {
type: 'number',
default: '0',
},
isOpen: {
type: 'boolean',
},
defaultOpen: {
type: 'boolean',
},
...childrenPropDefs,
};
export const popoverPropDefs: Record<string, PropDef> = {
triggerRef: {
type: 'enum',
values: ['RefObject<Element | null>'],
},
isEntering: {
type: 'boolean',
},
isExiting: {
type: 'boolean',
},
placement: {
type: 'enum',
values: ['top', 'right', 'bottom', 'left'],
},
containerPadding: {
type: 'number',
default: '12',
},
offset: {
type: 'number',
default: '8',
},
...childrenPropDefs,
...classNamePropDefs,
...stylePropDefs,
};

Some files were not shown because too many files have changed in this diff Show More