Migrate all mdx files
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
import { components, layoutComponents } from '@/utils/data';
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
|
||||
const { default: Component } = await import(`@/content/${slug}.mdx`);
|
||||
|
||||
return <Component />;
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
const list = [...components, ...layoutComponents];
|
||||
|
||||
return list.map(component => ({
|
||||
slug: component.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
export const dynamicParams = false;
|
||||
@@ -0,0 +1,119 @@
|
||||
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,136 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
Default,
|
||||
WithSubtitle,
|
||||
CustomTrigger,
|
||||
DefaultExpanded,
|
||||
GroupSingleOpen,
|
||||
GroupMultipleOpen,
|
||||
} from './stories';
|
||||
import {
|
||||
accordionPropDefs,
|
||||
accordionTriggerPropDefs,
|
||||
accordionPanelPropDefs,
|
||||
accordionGroupPropDefs,
|
||||
accordionUsageSnippet,
|
||||
accordionWithSubtitleSnippet,
|
||||
accordionCustomTriggerSnippet,
|
||||
accordionDefaultExpandedSnippet,
|
||||
accordionGroupSingleOpenSnippet,
|
||||
accordionGroupMultipleOpenSnippet,
|
||||
} from './accordion.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { AccordionDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Accordion"
|
||||
description="A component for showing and hiding content with animation."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<Default />}
|
||||
code={accordionUsageSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={accordionUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Accordion
|
||||
|
||||
Root container for the accordion. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionPropDefs} />
|
||||
|
||||
### AccordionTrigger
|
||||
|
||||
Trigger component with built-in animated chevron icon. Renders a heading element (defaults to `<h3>`, configurable via `level` prop) wrapping a `<button>`.
|
||||
|
||||
<PropsTable data={accordionTriggerPropDefs} />
|
||||
|
||||
### AccordionPanel
|
||||
|
||||
Panel with the accordion content. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionPanelPropDefs} />
|
||||
|
||||
### AccordionGroup
|
||||
|
||||
Container for managing multiple accordions. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={accordionGroupPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Subtitle
|
||||
|
||||
Here's a view when using both title and subtitle props.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<WithSubtitle />}
|
||||
code={accordionWithSubtitleSnippet}
|
||||
/>
|
||||
|
||||
### Custom Trigger
|
||||
|
||||
Here's a view when providing custom multi-line content as children.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<CustomTrigger />}
|
||||
code={accordionCustomTriggerSnippet}
|
||||
/>
|
||||
|
||||
### Default Expanded
|
||||
|
||||
Here's a view when the panel is expanded by default.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<DefaultExpanded />}
|
||||
code={accordionDefaultExpandedSnippet}
|
||||
/>
|
||||
|
||||
### Group Single Open
|
||||
|
||||
Here's a view when only one accordion can be open at a time.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<GroupSingleOpen />}
|
||||
code={accordionGroupSingleOpenSnippet}
|
||||
/>
|
||||
|
||||
### Group Multiple Open
|
||||
|
||||
Here's a view when multiple accordions can be open simultaneously.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<GroupMultipleOpen />}
|
||||
code={accordionGroupMultipleOpenSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={AccordionDefinition} />
|
||||
|
||||
<ChangelogComponent component="accordion" />
|
||||
@@ -0,0 +1,19 @@
|
||||
'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 />;
|
||||
@@ -0,0 +1,89 @@
|
||||
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,76 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, Sizes, Fallback, Purpose } from './stories';
|
||||
import {
|
||||
avatarPropDefs,
|
||||
snippetUsage,
|
||||
snippetSizes,
|
||||
snippetFallback,
|
||||
snippetPurpose,
|
||||
} from './avatar.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { AvatarDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Avatar"
|
||||
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" />`}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={snippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={avatarPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
Avatar sizes can be set using the `size` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={snippetSizes}
|
||||
/>
|
||||
|
||||
### Fallback
|
||||
|
||||
If the image is not available, the avatar will show the initials of the name.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Fallback />}
|
||||
code={snippetFallback}
|
||||
/>
|
||||
|
||||
### The `purpose` prop
|
||||
|
||||
Control how the avatar is announced to screen readers using the `purpose` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Purpose />}
|
||||
code={snippetPurpose}
|
||||
/>
|
||||
|
||||
<Theming definition={AvatarDefinition} />
|
||||
|
||||
<ChangelogComponent component="avatar" />
|
||||
@@ -0,0 +1,15 @@
|
||||
'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,40 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
displayPropDefs,
|
||||
heightPropDefs,
|
||||
positionPropDefs,
|
||||
stylePropDefs,
|
||||
widthPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const boxPropDefs: Record<string, PropDef> = {
|
||||
as: {
|
||||
type: 'enum',
|
||||
values: ['div', 'span'],
|
||||
default: 'div',
|
||||
responsive: true,
|
||||
},
|
||||
...widthPropDefs,
|
||||
...heightPropDefs,
|
||||
...positionPropDefs,
|
||||
...displayPropDefs,
|
||||
...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,66 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Default } from './stories';
|
||||
import {
|
||||
boxPropDefs,
|
||||
snippetUsage,
|
||||
boxPreviewSnippet,
|
||||
boxSimpleSnippet,
|
||||
boxResponsiveSnippet,
|
||||
} from './box.props';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { BoxDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Layout"
|
||||
title="Box"
|
||||
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"
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={snippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Box
|
||||
|
||||
This is the Box component, our lowest-level component. Here are all the
|
||||
available properties.
|
||||
|
||||
<PropsTable data={boxPropDefs} />
|
||||
|
||||
Padding and margin are used to create space around your component using our
|
||||
predefined spacing tokens. We would recommend to use padding over margin to
|
||||
avoid collapsing margins but both are available.
|
||||
|
||||
<PropsTable data={spacingPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple example
|
||||
|
||||
A simple example of how to use the Box component.
|
||||
|
||||
<CodeBlock code={boxSimpleSnippet} />
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={boxResponsiveSnippet} />
|
||||
|
||||
<Theming definition={BoxDefinition} />
|
||||
|
||||
<ChangelogComponent component="box" />
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Box/Box.stories';
|
||||
|
||||
const { Default: DefaultStory } = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
@@ -0,0 +1,62 @@
|
||||
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,99 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Variants, Sizes, Disabled, Loading } from './stories';
|
||||
import {
|
||||
buttonIconPropDefs,
|
||||
buttonIconUsageSnippet,
|
||||
buttonIconDefaultSnippet,
|
||||
buttonIconVariantsSnippet,
|
||||
buttonIconSizesSnippet,
|
||||
buttonIconDisabledSnippet,
|
||||
buttonIconLoadingSnippet,
|
||||
buttonIconResponsiveSnippet,
|
||||
buttonIconAsLinkSnippet,
|
||||
} from './button-icon.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonIconDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="ButtonIcon"
|
||||
description="A button component with a single icon that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Variants />}
|
||||
code={buttonIconDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={buttonIconUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonIconPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Variants />}
|
||||
code={buttonIconVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={buttonIconSizesSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={buttonIconDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Loading
|
||||
|
||||
Here's a view when buttons are in a loading state.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Loading />}
|
||||
code={buttonIconLoadingSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonIconResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ButtonIconDefinition} />
|
||||
|
||||
<ChangelogComponent component="button-icon" />
|
||||
@@ -0,0 +1,15 @@
|
||||
'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 />;
|
||||
@@ -0,0 +1,73 @@
|
||||
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,97 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Variants, Sizes, WithIcons, Disabled } from './stories';
|
||||
import {
|
||||
buttonLinkPropDefs,
|
||||
buttonLinkSnippetUsage,
|
||||
buttonLinkVariantsSnippet,
|
||||
buttonLinkSizesSnippet,
|
||||
buttonLinkIconsSnippet,
|
||||
buttonLinkDisabledSnippet,
|
||||
buttonLinkResponsiveSnippet,
|
||||
} from './button-link.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonLinkDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="ButtonLink"
|
||||
description="A button component that can be used as a link."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Variants />}
|
||||
code={buttonLinkVariantsSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={buttonLinkSnippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonLinkPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Variants />}
|
||||
code={buttonLinkVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={buttonLinkSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
Here's a view when buttons have icons.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcons />}
|
||||
code={buttonLinkIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={buttonLinkDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonLinkResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ButtonLinkDefinition} />
|
||||
|
||||
<ChangelogComponent component="button-link" />
|
||||
@@ -0,0 +1,15 @@
|
||||
'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 />;
|
||||
@@ -0,0 +1,77 @@
|
||||
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,124 @@
|
||||
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,
|
||||
buttonSnippetUsage,
|
||||
buttonVariantsSnippet,
|
||||
buttonSizesSnippet,
|
||||
buttonIconsSnippet,
|
||||
buttonDisabledSnippet,
|
||||
buttonLoadingSnippet,
|
||||
buttonResponsiveSnippet,
|
||||
buttonAsLinkSnippet,
|
||||
} from './button.props';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Button"
|
||||
description="A button component that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Variants />}
|
||||
code={buttonVariantsSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={buttonSnippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={buttonPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when buttons have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Variants />}
|
||||
code={buttonVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={buttonSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
Here's a view when buttons have icons.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcons />}
|
||||
code={buttonIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={buttonDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Loading
|
||||
|
||||
Here's a view when buttons are in a loading state.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Loading />}
|
||||
code={buttonLoadingSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonResponsiveSnippet} />
|
||||
|
||||
### As Link
|
||||
|
||||
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}
|
||||
/>
|
||||
|
||||
<Theming definition={ButtonDefinition} />
|
||||
|
||||
<ChangelogComponent component="button" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'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 />;
|
||||
@@ -0,0 +1,83 @@
|
||||
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,90 @@
|
||||
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';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { CardDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Card"
|
||||
description="A card component that can be used to display content in a box."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CustomSize />}
|
||||
code={cardDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={cardUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Card
|
||||
|
||||
A card component that can be used to display content in a box.
|
||||
|
||||
<PropsTable data={cardPropDefs} />
|
||||
|
||||
### CardHeader
|
||||
|
||||
To display a header in a card, use the `CardHeader` component. This will be fixed at the top of the card.
|
||||
|
||||
<PropsTable data={cardHeaderPropDefs} />
|
||||
|
||||
### CardBody
|
||||
|
||||
To display content in a card, use the `CardBody` component. This will automatically fill the card.
|
||||
|
||||
<PropsTable data={cardBodyPropDefs} />
|
||||
|
||||
### CardFooter
|
||||
|
||||
To display a footer in a card, use the `CardFooter` component. This will be fixed at the bottom of the card.
|
||||
|
||||
<PropsTable data={cardFooterPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With long body
|
||||
|
||||
Here's a view when card has a long body.
|
||||
|
||||
<Snippet
|
||||
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}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming definition={CardDefinition} />
|
||||
|
||||
<ChangelogComponent component="card" />
|
||||
@@ -0,0 +1,13 @@
|
||||
'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,61 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const checkboxPropDefs: Record<string, PropDef> = {
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['React.ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
isSelected: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
},
|
||||
defaultSelected: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(isSelected: boolean) => void'],
|
||||
responsive: false,
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
},
|
||||
isRequired: {
|
||||
type: 'enum',
|
||||
values: ['boolean'],
|
||||
responsive: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
...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,52 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Default, AllVariants } from './stories';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
checkboxPropDefs,
|
||||
checkboxUsageSnippet,
|
||||
checkboxDefaultSnippet,
|
||||
checkboxVariantsSnippet,
|
||||
} from './checkbox.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { CheckboxDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Checkbox"
|
||||
description="A checkbox component that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={checkboxDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={checkboxUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={checkboxPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### All variants
|
||||
|
||||
Here's a view when checkboxes have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AllVariants />}
|
||||
code={checkboxVariantsSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={CheckboxDefinition} />
|
||||
|
||||
<ChangelogComponent component="checkbox" />
|
||||
@@ -0,0 +1,8 @@
|
||||
'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,32 @@
|
||||
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>
|
||||
<DecorativeBox />
|
||||
</Container>`;
|
||||
|
||||
export const containerSimpleSnippet = `<Container>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
</Container>`;
|
||||
|
||||
export const containerResponsiveSnippet = `<Container paddingY={{ xs: 'sm', md: 'md' }}>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
</Container>`;
|
||||
@@ -0,0 +1,54 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Preview } from './stories';
|
||||
import {
|
||||
containerPropDefs,
|
||||
containerUsageSnippet,
|
||||
containerDefaultSnippet,
|
||||
containerSimpleSnippet,
|
||||
containerResponsiveSnippet,
|
||||
} from './container.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ContainerDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Layout"
|
||||
title="Container"
|
||||
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}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={containerUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={containerPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple
|
||||
|
||||
A simple example of how to use the Container component.
|
||||
|
||||
<CodeBlock code={containerSimpleSnippet} />
|
||||
|
||||
### Responsive padding & margin
|
||||
|
||||
The Container component also supports responsive values, making it easy to
|
||||
create responsive designs.
|
||||
|
||||
<CodeBlock code={containerResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ContainerDefinition} />
|
||||
|
||||
<ChangelogComponent component="container" />
|
||||
@@ -0,0 +1,7 @@
|
||||
'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,172 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const dialogTriggerPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
};
|
||||
|
||||
export const dialogPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
width: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogHeaderPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogBodyPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogFooterPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogClosePropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary'],
|
||||
default: 'secondary',
|
||||
responsive: false,
|
||||
},
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogUsageSnippet = `import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
} from '@backstage/ui';
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Open Dialog</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Title</DialogHeader>
|
||||
<DialogBody>Content</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">Close</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const dialogDefaultSnippet = `<DialogTrigger>
|
||||
<Button variant="secondary">Open Dialog</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Example Dialog</DialogHeader>
|
||||
<DialogBody>
|
||||
<Text>This is a basic dialog example.</Text>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">Close</Button>
|
||||
<Button variant="primary" slot="close">Save</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const dialogFixedWidthAndHeightSnippet = `<DialogTrigger>
|
||||
<Button variant="secondary">Scrollable Dialog</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Long Content Dialog</DialogHeader>
|
||||
<DialogBody width={600} height={400}>
|
||||
...
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">Cancel</Button>
|
||||
<Button variant="primary" slot="close">Accept</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const dialogWithFormSnippet = `<DialogTrigger>
|
||||
<Button variant="secondary">Create User</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Create New User</DialogHeader>
|
||||
<DialogBody>
|
||||
<Flex direction="column" gap="3">
|
||||
<TextField label="Name" placeholder="Enter full name" />
|
||||
<TextField label="Email" placeholder="Enter email address" />
|
||||
<Select label="Role">
|
||||
<SelectItem>Admin</SelectItem>
|
||||
<SelectItem>User</SelectItem>
|
||||
<SelectItem>Viewer</SelectItem>
|
||||
</Select>
|
||||
</Flex>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">Cancel</Button>
|
||||
<Button variant="primary" slot="close">Create User</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const dialogWithNoTriggerSnippet = `const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
<Dialog isOpen={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogHeader>Create New User</DialogHeader>
|
||||
<DialogBody>
|
||||
Your content
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">Cancel</Button>
|
||||
<Button variant="primary" slot="close">Create User</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>`;
|
||||
|
||||
export const dialogCloseSnippet = `<Button slot="close">Close</Button>`;
|
||||
@@ -0,0 +1,108 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, PreviewFixedWidthAndHeight, PreviewWithForm } from './stories';
|
||||
import {
|
||||
dialogPropDefs,
|
||||
dialogTriggerPropDefs,
|
||||
dialogHeaderPropDefs,
|
||||
dialogBodyPropDefs,
|
||||
dialogFooterPropDefs,
|
||||
dialogClosePropDefs,
|
||||
dialogUsageSnippet,
|
||||
dialogDefaultSnippet,
|
||||
dialogFixedWidthAndHeightSnippet,
|
||||
dialogWithFormSnippet,
|
||||
dialogWithNoTriggerSnippet,
|
||||
dialogCloseSnippet,
|
||||
} from './dialog.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { DialogDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Dialog"
|
||||
description="A modal dialog component that displays content in an overlay window."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={dialogDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={dialogUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### DialogTrigger
|
||||
|
||||
Wraps a trigger element and the dialog content to handle open/close state.
|
||||
|
||||
<PropsTable data={dialogTriggerPropDefs} />
|
||||
|
||||
### Dialog
|
||||
|
||||
The main dialog container that renders as a modal overlay.
|
||||
|
||||
<PropsTable data={dialogPropDefs} />
|
||||
|
||||
### DialogHeader
|
||||
|
||||
Displays the dialog title with a built-in close button.
|
||||
|
||||
<PropsTable data={dialogHeaderPropDefs} />
|
||||
|
||||
### DialogBody
|
||||
|
||||
The main content area of the dialog with optional scrolling.
|
||||
|
||||
<PropsTable data={dialogBodyPropDefs} />
|
||||
|
||||
### DialogFooter
|
||||
|
||||
Contains action buttons or other footer content.
|
||||
|
||||
<PropsTable data={dialogFooterPropDefs} />
|
||||
|
||||
If you want to close the dialog while pressing a button, you can use the `slot="close"` prop on the button.
|
||||
|
||||
<CodeBlock code={dialogCloseSnippet} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Fixed Width and Height
|
||||
|
||||
Dialog with a fixed height body that scrolls when content overflows.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PreviewFixedWidthAndHeight />}
|
||||
code={dialogFixedWidthAndHeightSnippet}
|
||||
/>
|
||||
|
||||
### Dialog with Form
|
||||
|
||||
Dialog containing form elements for user input.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PreviewWithForm />}
|
||||
code={dialogWithFormSnippet}
|
||||
/>
|
||||
|
||||
### Dialog with no trigger and controlled by props
|
||||
|
||||
You can also control the dialog using your own states.
|
||||
|
||||
<CodeBlock code={dialogWithNoTriggerSnippet} />
|
||||
|
||||
<Theming definition={DialogDefinition} />
|
||||
|
||||
<ChangelogComponent component="dialog" />
|
||||
@@ -0,0 +1,15 @@
|
||||
'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,55 @@
|
||||
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>`;
|
||||
@@ -0,0 +1,69 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Default } from './stories';
|
||||
import {
|
||||
flexPropDefs,
|
||||
flexUsageSnippet,
|
||||
flexDefaultSnippet,
|
||||
flexFAQ1Snippet,
|
||||
flexSimpleSnippet,
|
||||
flexResponsiveSnippet,
|
||||
flexAlignSnippet,
|
||||
} from './flex.props';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { FlexDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Layout"
|
||||
title="Flex"
|
||||
description="A responsive flex container component for vertical stacking with customizable gaps."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
align="center"
|
||||
preview={<Default />}
|
||||
code={flexDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={flexUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={flexPropDefs} />
|
||||
|
||||
The grid component also accepts all the spacing props from the Box component.
|
||||
|
||||
<PropsTable data={spacingPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple
|
||||
|
||||
A simple example of how to use the Flex component.
|
||||
|
||||
<CodeBlock code={flexSimpleSnippet} />
|
||||
|
||||
### Responsive
|
||||
|
||||
The Flex component also supports responsive values, making it easy to create
|
||||
responsive designs.
|
||||
|
||||
<CodeBlock code={flexResponsiveSnippet} />
|
||||
|
||||
### Align
|
||||
|
||||
The Flex component also supports responsive alignment, making it easy to
|
||||
create responsive designs.
|
||||
|
||||
<CodeBlock code={flexAlignSnippet} />
|
||||
|
||||
<Theming definition={FlexDefinition} />
|
||||
|
||||
<ChangelogComponent component="flex" />
|
||||
@@ -0,0 +1,7 @@
|
||||
'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,111 @@
|
||||
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>`;
|
||||
@@ -0,0 +1,99 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Default } from './stories';
|
||||
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';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Layout"
|
||||
title="Grid"
|
||||
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}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={gridUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Grid.Root
|
||||
|
||||
This is the grid container component. It will help to define the number of
|
||||
columns that will be used in the grid. You can also define the gap between the
|
||||
columns. All values are responsive.
|
||||
|
||||
<PropsTable data={gridPropDefs} />
|
||||
|
||||
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} />
|
||||
|
||||
<ChangelogComponent component="grid" />
|
||||
@@ -0,0 +1,7 @@
|
||||
'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,141 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const propDefs: Record<string, PropDef> = {
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Your plugin',
|
||||
},
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
breadcrumbs: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'Breadcrumb[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the breadcrumb',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'URL for the breadcrumb link',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...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,88 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { WithEverything, WithLongBreadcrumbs, WithTabs, WithCustomActions, WithMenuItems } from './stories';
|
||||
import {
|
||||
propDefs,
|
||||
usage,
|
||||
simple,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withCustomActions,
|
||||
withMenuItems,
|
||||
} from './header-page.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { HeaderPageDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Component"
|
||||
title="HeaderPage"
|
||||
description="A header page component for the plugin that should sit under the Header component."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<WithEverything />}
|
||||
code={defaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={usage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={propDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Breadcrumbs
|
||||
|
||||
You can add breadcrumbs to the header page to help users navigate to the previous page. The `breadcrumbs`
|
||||
prop is an array of objects with a `label` and `href` property. By default we truncate the breadcrumb label to 240px.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<WithLongBreadcrumbs />}
|
||||
code={withBreadcrumbs}
|
||||
/>
|
||||
|
||||
### With Tabs
|
||||
|
||||
You can add tabs to the header page to help users navigate to the different sections of the page. The `tabs`
|
||||
prop is an array of objects with a `label` and `href` property.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<WithTabs />}
|
||||
code={withTabs}
|
||||
/>
|
||||
|
||||
### With Custom Actions
|
||||
|
||||
You can add custom actions to the header page to help users navigate to the different sections of the page. The `customActions`
|
||||
prop is a React node.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<WithCustomActions />}
|
||||
code={withCustomActions}
|
||||
/>
|
||||
|
||||
### With Menu Items
|
||||
|
||||
You can add menu items to the header page to help users navigate to the different sections of the page. The `menuItems`
|
||||
prop is an array of objects with a `label`, `value` and `onClick` property.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<WithMenuItems />}
|
||||
code={withMenuItems}
|
||||
/>
|
||||
|
||||
<Theming definition={HeaderPageDefinition} />
|
||||
|
||||
<ChangelogComponent component="Header" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'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,200 @@
|
||||
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 />`;
|
||||
|
||||
export const defaultSnippet = `<Header
|
||||
title="My plugin"
|
||||
titleLink="/"
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'checks', label: 'Checks' },
|
||||
{ id: 'tracks', label: 'Tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations' },
|
||||
]}
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
{ label: 'Settings', href: '/settings' },
|
||||
]}
|
||||
menuItems={[
|
||||
{ label: 'Settings', value: 'settings' },
|
||||
{ label: 'Invite new members', value: 'invite-new-members' },
|
||||
]}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const simple = `<Header
|
||||
title="My plugin"
|
||||
titleLink="/"
|
||||
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={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>`;
|
||||
|
||||
export const withTabs = `
|
||||
<Header
|
||||
title="My plugin"
|
||||
titleLink="/"
|
||||
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={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'checks', label: 'Checks' },
|
||||
{ id: 'tracks', label: 'Tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations' },
|
||||
]}
|
||||
/>
|
||||
`;
|
||||
|
||||
export const withBreadcrumbs = `<Header
|
||||
title="My plugin"
|
||||
titleLink="/"
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
{ label: 'Settings', href: '/settings' },
|
||||
]}
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'checks', label: 'Checks' },
|
||||
{ id: 'tracks', label: 'Tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withHeaderPage = `<Header
|
||||
title="My plugin"
|
||||
titleLink="/"
|
||||
breadcrumbs={...}
|
||||
tabs={...}
|
||||
/>
|
||||
<HeaderPage
|
||||
title="Page title"
|
||||
menuItems={...}
|
||||
tabs={...}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>`;
|
||||
@@ -0,0 +1,86 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { WithAllOptionsAndTabs, WithAllOptions, WithBreadcrumbs, WithHeaderPage } from './stories';
|
||||
import {
|
||||
propDefs,
|
||||
usage,
|
||||
simple,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withHeaderPage,
|
||||
} from './header.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { HeaderDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
type="Component"
|
||||
title="Header"
|
||||
description="A header component for the plugin."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<WithAllOptionsAndTabs />}
|
||||
code={defaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={usage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={propDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple header
|
||||
|
||||
A simple example of how to use the Header component.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<WithAllOptions />}
|
||||
code={simple}
|
||||
open
|
||||
/>
|
||||
|
||||
### Header with tabs
|
||||
|
||||
A simple example of how to use the Header component with tabs. All links are using React Router
|
||||
under the hood and will be active when you are on the corresponding page.
|
||||
|
||||
<Snippet
|
||||
|
||||
preview={<WithAllOptionsAndTabs />}
|
||||
code={withTabs}
|
||||
open
|
||||
/>
|
||||
|
||||
### Header with breadcrumbs
|
||||
|
||||
Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below).
|
||||
|
||||
<Snippet
|
||||
preview={<WithBreadcrumbs />}
|
||||
code={withBreadcrumbs}
|
||||
open
|
||||
/>
|
||||
|
||||
### Header with HeaderPage
|
||||
|
||||
You can use the `Header` component inside the [HeaderPage](/components/header-page) component to compose your multi-level navigation.
|
||||
|
||||
<Snippet
|
||||
preview={<WithHeaderPage />}
|
||||
code={withHeaderPage}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming definition={HeaderDefinition} />
|
||||
|
||||
<ChangelogComponent component="Header" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'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,81 @@
|
||||
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>`;
|
||||
@@ -0,0 +1,99 @@
|
||||
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,
|
||||
linkUsageSnippet,
|
||||
linkDefaultSnippet,
|
||||
linkVariantsSnippet,
|
||||
linkWeightsSnippet,
|
||||
linkColorsSnippet,
|
||||
linkRouterSnippet,
|
||||
linkTruncateSnippet,
|
||||
} from './link.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { LinkDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Link"
|
||||
description="A link component that renders a `<a>` element."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={linkDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={linkUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<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
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when links have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
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}
|
||||
/>
|
||||
|
||||
### Colors
|
||||
|
||||
Here's a view when links have different colors.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AllColors />}
|
||||
code={linkColorsSnippet}
|
||||
/>
|
||||
|
||||
### Truncate
|
||||
|
||||
The `Link` component has a `truncate` prop that can be used to truncate the text.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<Truncate />}
|
||||
code={linkTruncateSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={LinkDefinition} />
|
||||
|
||||
<ChangelogComponent component="link" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'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,372 @@
|
||||
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>`;
|
||||
@@ -0,0 +1,237 @@
|
||||
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 {
|
||||
usage,
|
||||
preview,
|
||||
submenu,
|
||||
icons,
|
||||
sections,
|
||||
separators,
|
||||
links,
|
||||
autocomplete,
|
||||
autocompleteListbox,
|
||||
autocompleteListboxMultiple,
|
||||
menuTriggerPropDefs,
|
||||
submenuTriggerPropDefs,
|
||||
menuPropDefs,
|
||||
menuListBoxPropDefs,
|
||||
menuAutocompletePropDefs,
|
||||
menuAutocompleteListboxPropDefs,
|
||||
menuItemPropDefs,
|
||||
menuListBoxItemPropDefs,
|
||||
menuSectionPropDefs,
|
||||
menuSeparatorPropDefs,
|
||||
} from './menu.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { MenuDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Menu"
|
||||
description="A list of actions in a dropdown, enhanced with keyboard navigation."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Preview />}
|
||||
code={preview}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={usage} />
|
||||
|
||||
### Triggers
|
||||
|
||||
- `MenuTrigger` is a wrapper component that combines a button or other trigger element with a menu displayed in a popover.
|
||||
- `SubmenuTrigger` is a wrapper component that combines a `MenuItem` with a menu displayed in a popover.
|
||||
|
||||
### Containers
|
||||
|
||||
- `Menu` is a container component that contains a list of menu items or sections.
|
||||
- `MenuListBox` is a container component that contains a list of menu items or sections.
|
||||
- `MenuAutocomplete` is a container component that contains a list of menu items or sections.
|
||||
- `MenuAutocompleteListbox` is a container component that contains a list of menu items or sections.
|
||||
|
||||
### Items
|
||||
|
||||
- `MenuItem` is an individual interactive item in the menu.
|
||||
- `MenuListBoxItem` is an individual interactive item in the menu list box.
|
||||
|
||||
### Separators
|
||||
|
||||
- `MenuSeparator` is a component that renders a horizontal line to separate menu items.
|
||||
- `MenuSection` is a component that renders a section in the menu.
|
||||
|
||||
## API reference
|
||||
|
||||
### MenuTrigger
|
||||
|
||||
`MenuTrigger` accepts exactly two children: the first child should be the trigger element, and second child should be
|
||||
one of the menu containers containing the menu.
|
||||
|
||||
<PropsTable data={menuTriggerPropDefs} />
|
||||
|
||||
### SubmenuTrigger
|
||||
|
||||
The `SubmenuTrigger` accepts exactly two children: the first child should be the `MenuItem` which triggers opening
|
||||
of the submenu, and second child should be one of the menu containers containing the submenu.
|
||||
|
||||
<PropsTable data={submenuTriggerPropDefs} />
|
||||
|
||||
### Menu
|
||||
|
||||
`Menu` is a container component that contains a list of menu items or sections.
|
||||
|
||||
<PropsTable data={menuPropDefs} />
|
||||
|
||||
### MenuListBox
|
||||
|
||||
`MenuListBox` is a container component that contains a list of menu items or sections.
|
||||
|
||||
<PropsTable data={menuListBoxPropDefs} />
|
||||
|
||||
### MenuAutocomplete
|
||||
|
||||
`MenuAutocomplete` is a container component that contains a list of menu items or sections.
|
||||
|
||||
<PropsTable data={menuAutocompletePropDefs} />
|
||||
|
||||
### MenuAutocompleteListbox
|
||||
|
||||
`MenuAutocompleteListbox` is a container component that contains a list of menu items or sections.
|
||||
|
||||
<PropsTable data={menuAutocompleteListboxPropDefs} />
|
||||
|
||||
### MenuItem
|
||||
|
||||
`MenuItem` is an individual interactive item in the menu.
|
||||
|
||||
<PropsTable data={menuItemPropDefs} />
|
||||
|
||||
### MenuListBoxItem
|
||||
|
||||
`MenuListBoxItem` is an individual interactive item in the menu list box.
|
||||
|
||||
<PropsTable data={menuListBoxItemPropDefs} />
|
||||
|
||||
### MenuSection
|
||||
|
||||
`MenuSection` is a component that renders a section in the menu.
|
||||
|
||||
<PropsTable data={menuSectionPropDefs} />
|
||||
|
||||
### MenuSeparator
|
||||
|
||||
`MenuSeparator` is a component that renders a horizontal line to separate menu items.
|
||||
|
||||
<PropsTable data={menuSeparatorPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Nested navigation
|
||||
|
||||
You can nest menus to create a more complex navigation structure. It is important to use the `placement` prop to ensure
|
||||
the submenu is displayed in the correct position. The best practice is to use the `right top` placement for the submenu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewSubmenu />}
|
||||
code={submenu}
|
||||
/>
|
||||
|
||||
### With icons
|
||||
|
||||
You can use the `iconStart` prop to add an icon to the menu item.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewIcons />}
|
||||
code={icons}
|
||||
/>
|
||||
|
||||
### With links
|
||||
|
||||
You can use the `href` prop to add a link to the menu item. This is using our router provider under the hood
|
||||
to work for both internal and external links.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewLinks />}
|
||||
code={links}
|
||||
/>
|
||||
|
||||
### With sections
|
||||
|
||||
You can use the `MenuSection` component to add a section to the menu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewSections />}
|
||||
code={sections}
|
||||
/>
|
||||
|
||||
### With separators
|
||||
|
||||
You can use the `MenuSeparator` component to add a separator to the menu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewSeparators />}
|
||||
code={separators}
|
||||
/>
|
||||
|
||||
### With autocomplete
|
||||
|
||||
You can use the `MenuAutocomplete` component to add a autocomplete to the menu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewAutocompleteMenu />}
|
||||
code={autocomplete}
|
||||
/>
|
||||
|
||||
### With list box
|
||||
|
||||
You can use the `MenuListBox` component to add a list box to the menu.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewAutocompleteListbox />}
|
||||
code={autocompleteListbox}
|
||||
/>
|
||||
|
||||
### With list box with multiple selection
|
||||
|
||||
You can use the `MenuListBox` component to add a list box to the menu. You can also use the `selectionMode` prop to
|
||||
allow multiple selection.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PreviewAutocompleteListboxMultiple />}
|
||||
code={autocompleteListboxMultiple}
|
||||
/>
|
||||
|
||||
<Theming definition={MenuDefinition} />
|
||||
|
||||
<ChangelogComponent component="menu" />
|
||||
@@ -0,0 +1,31 @@
|
||||
'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,65 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import {
|
||||
inputPropDefs,
|
||||
passwordFieldUsageSnippet,
|
||||
passwordFieldDefaultSnippet,
|
||||
passwordFieldSizesSnippet,
|
||||
passwordFieldDescriptionSnippet,
|
||||
} from './password-field.props';
|
||||
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}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={passwordFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={inputPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={passwordFieldSizesSnippet}
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
Here's a simple PasswordField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDescription />}
|
||||
code={passwordFieldDescriptionSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={PasswordFieldDefinition} />
|
||||
|
||||
<ChangelogComponent component="password-field" />
|
||||
@@ -0,0 +1,43 @@
|
||||
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,13 @@
|
||||
'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,102 @@
|
||||
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 { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { PopoverDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Popover"
|
||||
description="A popover displays floating content anchored to a trigger element with automatic positioning and collision detection."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={popoverDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={popoverUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Popover
|
||||
|
||||
The main popover component that displays floating content with automatic positioning.
|
||||
|
||||
<PropsTable data={popoverPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Placements
|
||||
|
||||
Popover supports multiple placement options to position the content relative to the trigger.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<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" />
|
||||
@@ -0,0 +1,174 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const popoverPropDefs: Record<string, PropDef> = {
|
||||
hideArrow: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether to hide the arrow pointing to the trigger element. Arrow is also automatically hidden for MenuTrigger and SubmenuTrigger contexts.',
|
||||
},
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left',
|
||||
'left start',
|
||||
'left end',
|
||||
'right',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
default: 'bottom',
|
||||
description: 'The placement of the popover relative to the trigger.',
|
||||
},
|
||||
containerPadding: {
|
||||
type: 'number',
|
||||
default: '12',
|
||||
description:
|
||||
'The padding between the popover and the edge of the viewport.',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
description: 'The offset from the trigger element.',
|
||||
},
|
||||
crossOffset: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
description: 'The cross-axis offset from the trigger element.',
|
||||
},
|
||||
shouldFlip: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
description:
|
||||
'Whether the popover should flip to the opposite side when there is not enough space.',
|
||||
},
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the popover is open (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the popover is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the popover's open state changes.",
|
||||
},
|
||||
isNonModal: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether the popover is non-modal. Non-modal popovers do not block interaction with the rest of the page.',
|
||||
},
|
||||
isKeyboardDismissDisabled: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
description:
|
||||
'Whether pressing the escape key to close the popover should be disabled.',
|
||||
},
|
||||
shouldCloseOnBlur: {
|
||||
type: 'boolean',
|
||||
default: 'true',
|
||||
description:
|
||||
'Whether to close the popover when the user interacts outside it.',
|
||||
},
|
||||
triggerRef: {
|
||||
type: 'enum',
|
||||
values: ['RefObject<Element | null>'],
|
||||
description: 'A ref for the trigger element.',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const popoverUsageSnippet = `import { Popover, DialogTrigger, Button } from '@backstage/ui';
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Text>This is a popover</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverDefaultSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Text>This is a popover</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverPlacementsSnippet = `<DialogTrigger>
|
||||
<Button>Top</Button>
|
||||
<Popover placement="top">
|
||||
<Text>Top placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Right</Button>
|
||||
<Popover placement="right">
|
||||
<Text>Right placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Bottom</Button>
|
||||
<Popover placement="bottom">
|
||||
<Text>Bottom placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Left</Button>
|
||||
<Popover placement="left">
|
||||
<Text>Left placement</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverNoArrowSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover hideArrow>
|
||||
<Text>Popover without arrow</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverRichContentSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<Flex direction="column" gap="3">
|
||||
<Text style={{ fontWeight: 'bold' }}>Popover Title</Text>
|
||||
<Text>
|
||||
This is a popover with rich content. It can contain multiple
|
||||
elements and formatted text.
|
||||
</Text>
|
||||
<Flex gap="2" justify="end">
|
||||
<Button variant="tertiary" size="small">Cancel</Button>
|
||||
<Button variant="primary" size="small">Confirm</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const popoverNonModalSnippet = `<DialogTrigger>
|
||||
<Button>Open Non-Modal Popover</Button>
|
||||
<Popover isNonModal>
|
||||
<Text>
|
||||
This is a non-modal popover. You can interact with other
|
||||
elements on the page while it's open.
|
||||
</Text>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Popover/Popover.stories';
|
||||
|
||||
const { Default: DefaultStory } = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
@@ -0,0 +1,105 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { Default, Horizontal, Disabled, DisabledSingle, Validation, ReadOnly } from './stories';
|
||||
import {
|
||||
radioGroupPropDefs,
|
||||
radioGroupUsageSnippet,
|
||||
radioGroupDefaultSnippet,
|
||||
radioGroupDescriptionSnippet,
|
||||
radioGroupHorizontalSnippet,
|
||||
radioGroupDisabledSnippet,
|
||||
radioGroupDisabledSingleSnippet,
|
||||
radioGroupValidationSnippet,
|
||||
radioGroupReadOnlySnippet,
|
||||
} from './radio-group.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { RadioGroupDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="RadioGroup"
|
||||
description="A radio group allows a user to select a single item from a list of mutually exclusive options."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={radioGroupDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={radioGroupUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={radioGroupPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Horizontal
|
||||
|
||||
Here's a simple TextField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Horizontal />}
|
||||
code={radioGroupHorizontalSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
You can disable the entire radio group by adding the `isDisabled` prop to the `RadioGroup` component.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={radioGroupDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Disabled Single radio
|
||||
|
||||
You can disable a single radio by adding the `isDisabled` prop to the `Radio` component.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<DisabledSingle />}
|
||||
code={radioGroupDisabledSingleSnippet}
|
||||
/>
|
||||
|
||||
### Validation
|
||||
|
||||
Here's an example of a radio group with errors.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Validation />}
|
||||
code={radioGroupValidationSnippet}
|
||||
/>
|
||||
|
||||
### Read only
|
||||
|
||||
You can make the radio group read only by adding the `isReadOnly` prop to the `RadioGroup` component.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ReadOnly />}
|
||||
code={radioGroupReadOnlySnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={RadioGroupDefinition} />
|
||||
|
||||
<ChangelogComponent component="radio-group" />
|
||||
@@ -0,0 +1,64 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const radioGroupPropDefs: 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 radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/ui';
|
||||
|
||||
<RadioGroup />`;
|
||||
|
||||
export const radioGroupDefaultSnippet = `<RadioGroup label="Label" />`;
|
||||
|
||||
export const radioGroupDescriptionSnippet = `<TextField label="Label" description="Description" placeholder="Enter a URL" />`;
|
||||
|
||||
export const radioGroupHorizontalSnippet = `<RadioGroup label="Label" orientation="horizontal" />`;
|
||||
|
||||
export const radioGroupDisabledSnippet = `<RadioGroup label="Label" isDisabled>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const radioGroupDisabledSingleSnippet = `<RadioGroup label="Label">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander" isDisabled>Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const radioGroupValidationSnippet = `<RadioGroup validate: value => (value === \'charmander\' ? \'Nice try!\' : null)>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const radioGroupReadOnlySnippet = `<RadioGroup label="Label" isReadOnly>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/RadioGroup/RadioGroup.stories';
|
||||
|
||||
const {
|
||||
Default: DefaultStory,
|
||||
Horizontal: HorizontalStory,
|
||||
Disabled: DisabledStory,
|
||||
DisabledSingle: DisabledSingleStory,
|
||||
Validation: ValidationStory,
|
||||
ReadOnly: ReadOnlyStory,
|
||||
} = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const Horizontal = () => <HorizontalStory.Component />;
|
||||
export const Disabled = () => <DisabledStory.Component />;
|
||||
export const DisabledSingle = () => <DisabledSingleStory.Component />;
|
||||
export const Validation = () => <ValidationStory.Component />;
|
||||
export const ReadOnly = () => <ReadOnlyStory.Component />;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { WithLabel, Sizes, WithDescription, StartCollapsed } from './stories';
|
||||
import {
|
||||
searchFieldPropDefs,
|
||||
searchFieldUsageSnippet,
|
||||
searchFieldDefaultSnippet,
|
||||
searchFieldSizesSnippet,
|
||||
searchFieldDescriptionSnippet,
|
||||
searchFieldCollapsibleSnippet,
|
||||
} from './search-field.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SearchFieldDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="SearchField"
|
||||
description="A SearchField is a text field designed for searches."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<WithLabel />}
|
||||
code={searchFieldDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={searchFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={searchFieldPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={searchFieldSizesSnippet}
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
Here's a simple SearchField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDescription />}
|
||||
code={searchFieldDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### Collapsible
|
||||
|
||||
You can make the SearchField collapsible by setting the `startCollapsed` prop to `true`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<StartCollapsed />}
|
||||
code={searchFieldCollapsibleSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={SearchFieldDefinition} />
|
||||
|
||||
<ChangelogComponent component="search-field" />
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const searchFieldPropDefs: 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,
|
||||
},
|
||||
startCollapsed: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const searchFieldUsageSnippet = `import { SearchField } from '@backstage/ui';
|
||||
|
||||
<SearchField />`;
|
||||
|
||||
export const searchFieldDefaultSnippet = `<SearchField label="Label" placeholder="Enter a URL" />`;
|
||||
|
||||
export const searchFieldSizesSnippet = `<Flex direction="row" gap="4">
|
||||
<SearchField size="small" placeholder="Small" icon={<Icon name="sparkling" />} />
|
||||
<SearchField size="medium" placeholder="Medium" icon={<Icon name="sparkling" />} />
|
||||
</Flex>`;
|
||||
|
||||
export const searchFieldDescriptionSnippet = `<SearchField label="Label" description="Description" placeholder="Enter a URL" />`;
|
||||
|
||||
export const searchFieldCollapsibleSnippet = `<SearchField startCollapsed />`;
|
||||
@@ -0,0 +1,15 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/SearchField/SearchField.stories';
|
||||
|
||||
const {
|
||||
WithLabel: WithLabelStory,
|
||||
Sizes: SizesStory,
|
||||
WithDescription: WithDescriptionStory,
|
||||
StartCollapsed: StartCollapsedStory,
|
||||
} = stories;
|
||||
|
||||
export const WithLabel = () => <WithLabelStory.Component />;
|
||||
export const Sizes = () => <SizesStory.Component />;
|
||||
export const WithDescription = () => <WithDescriptionStory.Component />;
|
||||
export const StartCollapsed = () => <StartCollapsedStory.Component />;
|
||||
@@ -0,0 +1,151 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Preview, WithLabelAndDescription, Sizes, WithIcon, Disabled, DisabledOption, Searchable, MultipleSelection, SearchableMultiple } from './stories';
|
||||
import {
|
||||
selectPropDefs,
|
||||
selectUsageSnippet,
|
||||
selectDefaultSnippet,
|
||||
selectDescriptionSnippet,
|
||||
selectSizesSnippet,
|
||||
selectDisabledSnippet,
|
||||
selectResponsiveSnippet,
|
||||
selectIconSnippet,
|
||||
selectSearchableSnippet,
|
||||
selectMultipleSnippet,
|
||||
selectSearchableMultipleSnippet,
|
||||
selectDisabledOptionsSnippet,
|
||||
} from './select.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SelectDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Select"
|
||||
description="A common form component for choosing a predefined value in a dropdown menu."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Preview />}
|
||||
code={selectDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={selectUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={selectPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Label and description
|
||||
|
||||
Select component with label and description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithLabelAndDescription />}
|
||||
code={selectDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when the selects have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={selectSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icon
|
||||
|
||||
Here's a view when the select has an icon.
|
||||
|
||||
<Snippet
|
||||
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcon />}
|
||||
code={selectIconSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when the select is disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={selectDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Disabled options
|
||||
|
||||
You can disable specific options within the Select component using `disabledKeys`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<DisabledOption />}
|
||||
code={selectDisabledOptionsSnippet}
|
||||
/>
|
||||
|
||||
### Searchable
|
||||
|
||||
Here's a view when the select has searchable filtering.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Searchable />}
|
||||
code={selectSearchableSnippet}
|
||||
/>
|
||||
|
||||
### Multiple Selection
|
||||
|
||||
Here's a view when the select allows multiple selections.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MultipleSelection />}
|
||||
code={selectMultipleSnippet}
|
||||
/>
|
||||
|
||||
### Searchable with Multiple Selection
|
||||
|
||||
Here's a view when the select combines search and multiple selection.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SearchableMultiple />}
|
||||
code={selectSearchableMultipleSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when the select is responsive.
|
||||
|
||||
<CodeBlock code={selectResponsiveSnippet} />
|
||||
|
||||
<Theming definition={SelectDefinition} />
|
||||
|
||||
<ChangelogComponent component="select" />
|
||||
@@ -0,0 +1,224 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const selectPropDefs: Record<string, PropDef> = {
|
||||
label: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
responsive: false,
|
||||
required: true,
|
||||
},
|
||||
options: {
|
||||
type: 'enum',
|
||||
values: ['Array<{ value: string, label: string }>'],
|
||||
required: true,
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['single', 'multiple'],
|
||||
default: 'single',
|
||||
responsive: false,
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
default: 'Select an item',
|
||||
responsive: false,
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
value: {
|
||||
type: 'enum',
|
||||
values: ['string', 'string[]'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Selected value (controlled). String for single selection, array for multiple.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'enum',
|
||||
values: ['string', 'string[]'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Initial value (uncontrolled). String for single selection, array for multiple.',
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
},
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
responsive: false,
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
isInvalid: {
|
||||
type: 'boolean',
|
||||
responsive: false,
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
responsive: false,
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(key: Key | null) => void', '(keys: Selection) => void'],
|
||||
responsive: false,
|
||||
description:
|
||||
'Handler called when selection changes. Single mode: receives Key | null. Multiple mode: receives Selection.',
|
||||
},
|
||||
searchable: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
responsive: false,
|
||||
},
|
||||
searchPlaceholder: {
|
||||
type: 'string',
|
||||
default: 'Search...',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const selectUsageSnippet = `import { Select } from '@backstage/ui';
|
||||
|
||||
<Select
|
||||
name="font"
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectDefaultSnippet = `<Select name="font" label="Font Family" options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]} />`;
|
||||
|
||||
export const selectDescriptionSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const selectIconSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
icon={<RiCloudLine />}
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const selectSizesSnippet = `<Flex>
|
||||
<Select
|
||||
size="small"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
<Select
|
||||
size="medium"
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const selectDisabledSnippet = `<Select
|
||||
disabled
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const selectResponsiveSnippet = `<Select
|
||||
size={{ initial: 'small', lg: 'medium' }}
|
||||
label="Font family"
|
||||
options={[ ... ]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableSnippet = `<Select
|
||||
name="country"
|
||||
label="Country"
|
||||
searchable
|
||||
searchPlaceholder="Search countries..."
|
||||
options={[
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectMultipleSnippet = `<Select
|
||||
name="options"
|
||||
label="Select multiple options"
|
||||
selectionMode="multiple"
|
||||
options={[
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
{ value: 'option3', label: 'Option 3' },
|
||||
{ value: 'option4', label: 'Option 4' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectSearchableMultipleSnippet = `<Select
|
||||
name="skills"
|
||||
label="Skills"
|
||||
searchable
|
||||
selectionMode="multiple"
|
||||
searchPlaceholder="Filter skills..."
|
||||
options={[
|
||||
{ value: 'react', label: 'React' },
|
||||
{ value: 'typescript', label: 'TypeScript' },
|
||||
{ value: 'javascript', label: 'JavaScript' },
|
||||
{ value: 'python', label: 'Python' },
|
||||
// ... more options
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const selectDisabledOptionsSnippet = `<Select
|
||||
name="font"
|
||||
label="Font Family"
|
||||
placeholder="Select a font"
|
||||
disabledKeys={['cursive', 'serif']}
|
||||
options={[
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
]}
|
||||
/>`;
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Select/Select.stories';
|
||||
|
||||
const {
|
||||
Preview: PreviewStory,
|
||||
WithLabelAndDescription: WithLabelAndDescriptionStory,
|
||||
Sizes: SizesStory,
|
||||
WithIcon: WithIconStory,
|
||||
Disabled: DisabledStory,
|
||||
DisabledOption: DisabledOptionStory,
|
||||
Searchable: SearchableStory,
|
||||
MultipleSelection: MultipleSelectionStory,
|
||||
SearchableMultiple: SearchableMultipleStory,
|
||||
} = stories;
|
||||
|
||||
export const Preview = () => <PreviewStory.Component />;
|
||||
export const WithLabelAndDescription = () => (
|
||||
<WithLabelAndDescriptionStory.Component />
|
||||
);
|
||||
export const Sizes = () => <SizesStory.Component />;
|
||||
export const WithIcon = () => <WithIconStory.Component />;
|
||||
export const Disabled = () => <DisabledStory.Component />;
|
||||
export const DisabledOption = () => <DisabledOptionStory.Component />;
|
||||
export const Searchable = () => <SearchableStory.Component />;
|
||||
export const MultipleSelection = () => <MultipleSelectionStory.Component />;
|
||||
export const SearchableMultiple = () => <SearchableMultipleStory.Component />;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Demo1, Demo2 } from './stories';
|
||||
import {
|
||||
skeletonPropDefs,
|
||||
skeletonUsageSnippet,
|
||||
skeletonDefaultSnippet,
|
||||
skeletonDemo1Snippet,
|
||||
skeletonDemo2Snippet,
|
||||
} from './skeleton.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SkeletonDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Skeleton"
|
||||
description="Use to show a placeholder while content is loading."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Demo2 />}
|
||||
code={skeletonDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={skeletonUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={skeletonPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Demo 1
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Demo1 />}
|
||||
code={skeletonDemo1Snippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### Demo 2
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Demo2 />}
|
||||
code={skeletonDemo2Snippet}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming definition={SkeletonDefinition} />
|
||||
|
||||
<ChangelogComponent component="skeleton" />
|
||||
@@ -0,0 +1,58 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const skeletonPropDefs: Record<string, PropDef> = {
|
||||
width: {
|
||||
type: 'number',
|
||||
default: '80',
|
||||
responsive: false,
|
||||
},
|
||||
height: {
|
||||
type: 'number',
|
||||
default: '24',
|
||||
responsive: false,
|
||||
},
|
||||
rounded: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const skeletonUsageSnippet = `import { Flex, Skeleton } from '@backstage/ui';
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDefaultSnippet = `<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDemo1Snippet = `<Flex gap="4">
|
||||
<Skeleton rounded width={48} height={48} />
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Skeleton width={200} height={8} />
|
||||
<Flex gap="4">
|
||||
<Skeleton width="100%" height={8} />
|
||||
<Skeleton width="100%" height={8} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
|
||||
export const skeletonDemo2Snippet = `<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Skeleton/Skeleton.stories';
|
||||
|
||||
const { Demo1: Demo1Story, Demo2: Demo2Story } = stories;
|
||||
|
||||
export const Demo1 = () => <Demo1Story.Component />;
|
||||
export const Demo2 = () => <Demo2Story.Component />;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, Disabled } from './stories';
|
||||
import { switchPropDefs, snippetUsage } from './switch.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SwitchDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Switch"
|
||||
description="A control that indicates whether a setting is on or off."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={`<Switch />`}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={snippetUsage} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={switchPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Disabled
|
||||
|
||||
A switch can be disabled using the `isDisabled` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={`<Switch isDisabled />`}
|
||||
/>
|
||||
|
||||
<Theming definition={SwitchDefinition} />
|
||||
|
||||
<ChangelogComponent component="switch" />
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Switch/Switch.stories';
|
||||
|
||||
const { Default: DefaultStory, Disabled: DisabledStory } = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const Disabled = () => <DisabledStory.Component />;
|
||||
@@ -0,0 +1,71 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const switchPropDefs: Record<string, PropDef> = {
|
||||
autoFocus: {
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultSelected: {
|
||||
type: 'boolean',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
isReadOnly: {
|
||||
type: 'boolean',
|
||||
},
|
||||
isSelected: {
|
||||
type: 'boolean',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(isSelected: boolean) => void'],
|
||||
},
|
||||
onFocus: {
|
||||
type: 'enum',
|
||||
values: ['(e: FocusEvent<Target>) => void'],
|
||||
},
|
||||
onBlur: {
|
||||
type: 'enum',
|
||||
values: ['(e: FocusEvent<Target>) => void'],
|
||||
},
|
||||
onFocusChange: {
|
||||
type: 'enum',
|
||||
values: ['(isFocused: boolean) => void'],
|
||||
},
|
||||
onKeyDown: {
|
||||
type: 'enum',
|
||||
values: ['(e: KeyboardEvent) => void'],
|
||||
},
|
||||
onKeyUp: {
|
||||
type: 'enum',
|
||||
values: ['(e: KeyboardEvent) => void'],
|
||||
},
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: ['(isHovered: boolean) => void'],
|
||||
},
|
||||
...stylePropDefs,
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
};
|
||||
|
||||
export const snippetUsage = `import { Switch } from '@backstage/ui';
|
||||
|
||||
<Switch />`;
|
||||
@@ -0,0 +1,165 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { TableRockBand, SelectionModePlayground, SelectionBehaviorPlayground, SelectionToggleWithActions } from './stories';
|
||||
import {
|
||||
tablePropDefs,
|
||||
tableHeaderPropDefs,
|
||||
tableBodyPropDefs,
|
||||
tablePaginationPropDefs,
|
||||
tableUsageSnippet,
|
||||
tableBasicSnippet,
|
||||
tableRowClickSnippet,
|
||||
tableHybridSnippet,
|
||||
tableCellInteractionsSnippet,
|
||||
tablePaginationSnippet,
|
||||
tableSelectionActionsSnippet,
|
||||
tableSelectionModeSnippet,
|
||||
tableSelectionBehaviorSnippet,
|
||||
tableSortingSnippet,
|
||||
columnPropDefs,
|
||||
rowPropDefs,
|
||||
cellPropDefs,
|
||||
} from './table.props';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TableDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Table"
|
||||
description="A flexible table component built on top of TanStack Table with built-in styling, interactions, and pagination support."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<TableRockBand />}
|
||||
code={tableBasicSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={tableUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Table
|
||||
|
||||
The main table component that renders data in a structured grid format.
|
||||
|
||||
<PropsTable data={tablePropDefs} />
|
||||
|
||||
### TableHeader
|
||||
|
||||
The header section of the table that contains the column definitions.
|
||||
|
||||
<PropsTable data={tableHeaderPropDefs} />
|
||||
|
||||
### Column
|
||||
|
||||
A column definition that describes how a column should be rendered.
|
||||
|
||||
<PropsTable data={columnPropDefs} />
|
||||
|
||||
### TableBody
|
||||
|
||||
The body section of the table that contains the rows.
|
||||
|
||||
<PropsTable data={tableBodyPropDefs} />
|
||||
|
||||
### Row
|
||||
|
||||
A row definition that describes how a row should be rendered.
|
||||
|
||||
<PropsTable data={rowPropDefs} />
|
||||
|
||||
### Cell
|
||||
|
||||
A cell definition that describes how a cell should be rendered.
|
||||
|
||||
<PropsTable data={cellPropDefs} />
|
||||
|
||||
### TablePagination
|
||||
|
||||
A pagination component designed to work with the Table component.
|
||||
|
||||
<PropsTable data={tablePaginationPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Table
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Row selection
|
||||
|
||||
Tables support row selection with two configuration options: selection mode and selection behavior.
|
||||
|
||||
#### Selection mode
|
||||
|
||||
Use `selectionMode` to control how many rows can be selected. With `single`, only one row can be selected at a time. With `multiple`, any number of rows can be selected, and a header checkbox provides select-all functionality.
|
||||
|
||||
<Snippet
|
||||
preview={<SelectionModePlayground />}
|
||||
code={tableSelectionModeSnippet}
|
||||
/>
|
||||
|
||||
#### Selection behavior
|
||||
|
||||
Use `selectionBehavior` to control how selection is indicated and interacted with. With `toggle`, checkboxes appear for selection. With `replace`, selection is indicated by row background color—click to select, Cmd/Ctrl+click for multiple.
|
||||
|
||||
<Snippet
|
||||
preview={<SelectionBehaviorPlayground />}
|
||||
code={tableSelectionBehaviorSnippet}
|
||||
/>
|
||||
|
||||
#### With row actions
|
||||
|
||||
With toggle behavior, clicking a row triggers its action when nothing is selected. Once any row is selected, clicking toggles selection instead.
|
||||
|
||||
With replace behavior, clicking selects the row and double-clicking triggers the action.
|
||||
|
||||
<Snippet
|
||||
preview={<SelectionToggleWithActions />}
|
||||
code={tableSelectionActionsSnippet}
|
||||
/>
|
||||
|
||||
### Row Clicks
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Pagination
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Sorting
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Asynchronous loading
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Empty state
|
||||
|
||||
Coming soon.
|
||||
|
||||
### Column resizing
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column reordering
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column pinning
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
### Column visibility
|
||||
|
||||
This feature is not available yet — let us know if you'd like us to explore it!
|
||||
|
||||
<Theming definition={TableDefinition} />
|
||||
|
||||
<ChangelogComponent component="table" />
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Table/stories/Table.docs.stories';
|
||||
|
||||
const {
|
||||
TableRockBand: TableRockBandStory,
|
||||
SelectionModePlayground: SelectionModePlaygroundStory,
|
||||
SelectionBehaviorPlayground: SelectionBehaviorPlaygroundStory,
|
||||
SelectionToggleWithActions: SelectionToggleWithActionsStory,
|
||||
} = stories;
|
||||
|
||||
export const TableRockBand = () => <TableRockBandStory.Component />;
|
||||
export const SelectionModePlayground = () => (
|
||||
<SelectionModePlaygroundStory.Component />
|
||||
);
|
||||
export const SelectionBehaviorPlayground = () => (
|
||||
<SelectionBehaviorPlaygroundStory.Component />
|
||||
);
|
||||
export const SelectionToggleWithActions = () => (
|
||||
<SelectionToggleWithActionsStory.Component />
|
||||
);
|
||||
@@ -0,0 +1,377 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const tablePropDefs: Record<string, PropDef> = {
|
||||
selectionBehavior: {
|
||||
type: 'enum',
|
||||
values: ['toggle', 'replace'],
|
||||
default: 'toggle',
|
||||
description: 'How multiple selection should behave in the collection.',
|
||||
},
|
||||
disabledBehavior: {
|
||||
type: 'enum',
|
||||
values: ['selection', 'all'],
|
||||
default: 'selection',
|
||||
description:
|
||||
'Whether disabledKeys applies to all interactions, or only selection.',
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
description: 'A list of row keys to disable.',
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['single', 'multiple'],
|
||||
description: 'The type of selection that is allowed in the collection.',
|
||||
},
|
||||
selectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The currently selected keys in the collection (controlled).',
|
||||
},
|
||||
defaultSelectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The initial selected keys in the collection (uncontrolled).',
|
||||
},
|
||||
onRowAction: {
|
||||
type: 'enum',
|
||||
values: ['(key: Key) => void'],
|
||||
description:
|
||||
'Handler that is called when a user performs an action on the row.',
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(keys: Selection) => void'],
|
||||
description: 'Handler that is called when the selection changes.',
|
||||
},
|
||||
onSortChange: {
|
||||
type: 'enum',
|
||||
values: ['(descriptor: SortDescriptor) => any'],
|
||||
description:
|
||||
'Handler that is called when the sorted column or direction changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableHeaderPropDefs: Record<string, PropDef> = {
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction starts.',
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction ends.',
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: ['(isHovering: boolean) => void'],
|
||||
description: 'Handler that is called when the hover state changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const columnPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the column.',
|
||||
},
|
||||
allowsSorting: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the column allows sorting.',
|
||||
},
|
||||
isRowHeader: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether a column is a row header and should be announced by assistive technology during row navigation.',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the column's contents, used for accessibility announcements.",
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableBodyPropDefs: Record<string, PropDef> = {
|
||||
renderEmptyState: {
|
||||
type: 'enum',
|
||||
values: ['(props) => ReactNode'],
|
||||
description:
|
||||
'Provides content to display when there are no rows in the table.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const rowPropDefs: Record<string, PropDef> = {
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the row's contents, used for accessibility announcements.",
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the row is disabled.',
|
||||
},
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the row.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
description: 'The URL to navigate to when the row is clicked.',
|
||||
},
|
||||
hrefLang: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The language of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
target: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The target of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
rel: {
|
||||
type: 'string',
|
||||
description:
|
||||
'The relationship of the URL to navigate to when the row is clicked.',
|
||||
},
|
||||
onAction: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description:
|
||||
"Handler that is called when a user performs an action on the row. The exact user event depends on the collection's selectionBehavior prop and the interaction modality.",
|
||||
},
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction starts.',
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: HoverEvent) => void'],
|
||||
description: 'Handler that is called when a hover interaction ends.',
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: ['(isHovering: boolean) => void'],
|
||||
description: 'Handler that is called when the hover state changes.',
|
||||
},
|
||||
onPress: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when the press is released over the target.',
|
||||
},
|
||||
onPressStart: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description: 'Handler that is called when a press interaction starts.',
|
||||
},
|
||||
onPressEnd: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when a press interaction ends, either over the target or when the pointer leaves the target.',
|
||||
},
|
||||
onPressChange: {
|
||||
type: 'enum',
|
||||
values: ['(isPressed: boolean) => void'],
|
||||
description: 'Handler that is called when the press state changes.',
|
||||
},
|
||||
onPressUp: {
|
||||
type: 'enum',
|
||||
values: ['(e: PressEvent) => void'],
|
||||
description:
|
||||
'Handler that is called when a press is released over the target, regardless of whether it started on the target or not.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const cellPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
description: 'The unique id of the cell.',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description:
|
||||
"A string representation of the cell's contents, used for features like typeahead.",
|
||||
},
|
||||
leadingIcon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
description: 'Optional icon to display before the cell content.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tablePaginationPropDefs: Record<string, PropDef> = {
|
||||
offset: {
|
||||
type: 'number',
|
||||
description: 'The current offset (starting index) for pagination.',
|
||||
},
|
||||
pageSize: {
|
||||
type: 'number',
|
||||
description: 'The number of items per page.',
|
||||
},
|
||||
setOffset: {
|
||||
type: 'enum',
|
||||
values: ['(offset: number) => void'],
|
||||
description: 'Handler that is called when the offset changes.',
|
||||
},
|
||||
setPageSize: {
|
||||
type: 'enum',
|
||||
values: ['(pageSize: number) => void'],
|
||||
description: 'Handler that is called when the page size changes.',
|
||||
},
|
||||
rowCount: {
|
||||
type: 'number',
|
||||
description: 'The total number of rows in the table.',
|
||||
},
|
||||
onNextPage: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description: 'Handler that is called when the next page is requested.',
|
||||
},
|
||||
onPreviousPage: {
|
||||
type: 'enum',
|
||||
values: ['() => void'],
|
||||
description: 'Handler that is called when the previous page is requested.',
|
||||
},
|
||||
onPageSizeChange: {
|
||||
type: 'enum',
|
||||
values: ['(pageSize: number) => void'],
|
||||
description: 'Handler that is called when the page size changes.',
|
||||
},
|
||||
showPageSizeOptions: {
|
||||
type: 'boolean',
|
||||
description: 'Whether to show the page size options.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tableUsageSnippet = `import { Cell, CellText, ..., TableHeader, TablePagination } from '@backstage/ui';
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row>
|
||||
<CellText title="Example" />
|
||||
<CellProfile />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination />`;
|
||||
|
||||
export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, CellText, CellProfile, TablePagination, useTable } from '@backstage/ui';
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: 'The Beatles',
|
||||
image: 'https://upload.wikimedia.org/wikipedia/en/thumb/4/42/Beatles_-...jpg',
|
||||
genre: 'Rock, Pop, Psychedelic Rock',
|
||||
yearFormed: 1960,
|
||||
albums: 13
|
||||
},
|
||||
// ... more data
|
||||
];
|
||||
|
||||
// Uncontrolled pagination (easiest)
|
||||
const { data: paginatedData, paginationProps } = useTable({
|
||||
data,
|
||||
pagination: {
|
||||
defaultPageSize: 5,
|
||||
},
|
||||
});
|
||||
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Band name</Column>
|
||||
<Column>Genre</Column>
|
||||
<Column>Year formed</Column>
|
||||
<Column>Albums</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{paginatedData?.map(item => (
|
||||
<Row key={item.name}>
|
||||
<CellProfile
|
||||
name={item.name}
|
||||
src={item.image}
|
||||
href={item.website}
|
||||
/>
|
||||
<CellText title={item.genre} />
|
||||
<CellText title={item.yearFormed.toString()} />
|
||||
<CellText title={item.albums.toString()} />
|
||||
</Row>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination {...paginationProps} />`;
|
||||
|
||||
export const tableSelectionActionsSnippet = `import { Table, TableHeader, TableBody, Column, Row, CellText } from '@backstage/ui';
|
||||
|
||||
function MyTable() {
|
||||
const [selectedKeys, setSelectedKeys] = React.useState(new Set([]));
|
||||
|
||||
return (
|
||||
<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
onRowAction={(key) => console.log('Opening', key)}
|
||||
>
|
||||
<TableHeader>
|
||||
<Column isRowHeader>Name</Column>
|
||||
<Column>Status</Column>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<Row id="1">
|
||||
<CellText title="Component A" />
|
||||
<CellText title="Active" />
|
||||
</Row>
|
||||
<Row id="2">
|
||||
<CellText title="Component B" />
|
||||
<CellText title="Inactive" />
|
||||
</Row>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}`;
|
||||
|
||||
export const tableSelectionModeSnippet = `<Table
|
||||
selectionMode="multiple" // or "single"
|
||||
selectionBehavior="toggle"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
{/* ... */}
|
||||
</Table>`;
|
||||
|
||||
export const tableSelectionBehaviorSnippet = `<Table
|
||||
selectionMode="multiple"
|
||||
selectionBehavior="toggle" // or "replace"
|
||||
selectedKeys={selectedKeys}
|
||||
onSelectionChange={setSelectedKeys}
|
||||
>
|
||||
{/* ... */}
|
||||
</Table>`;
|
||||
@@ -0,0 +1,85 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Default, WithTabPanels, WithMockedURLTab2, PrefixMatchingDeepNesting } from './stories';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
tabsPropDefs,
|
||||
tabPropDefs,
|
||||
tabsUsageSnippet,
|
||||
tabsDefaultSnippet,
|
||||
tabsWithTabPanelsSnippet,
|
||||
tabsWithLinksSnippet,
|
||||
tabsWithDeeplyNestedRoutesSnippet,
|
||||
} from './tabs.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TabsDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Tabs"
|
||||
description="A component for toggling between related panels on the same page."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={tabsDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={tabsUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### Tabs
|
||||
|
||||
Groups the tabs and the corresponding panels. Renders a `<div>` element.
|
||||
|
||||
<PropsTable data={tabsPropDefs} />
|
||||
|
||||
### Tab
|
||||
|
||||
An individual interactive tab button that toggles the corresponding panel. Renders a `<button>` element.
|
||||
|
||||
<PropsTable data={tabPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple tabs
|
||||
|
||||
To connect the tabs with the panels, you need to use the `id` prop on the tab and the tab panel.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<WithTabPanels />}
|
||||
code={tabsWithTabPanelsSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### Tabs as links
|
||||
|
||||
You can use the `href` prop on the tab to make it a link. This will use the `react-router` under the hood to navigate to the tab. We automatically detect if this is an external URL and render a `<a>` element instead of a `<button>`.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<WithMockedURLTab2 />}
|
||||
code={tabsWithLinksSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### With deeply nested routes
|
||||
|
||||
You can use the `matchStrategy` prop on the tab to control how the tab is matched to the current URL.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<PrefixMatchingDeepNesting />}
|
||||
code={tabsWithDeeplyNestedRoutesSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming definition={TabsDefinition} />
|
||||
|
||||
<ChangelogComponent component="tabs" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Tabs/Tabs.stories';
|
||||
|
||||
const {
|
||||
Default: DefaultStory,
|
||||
WithTabPanels: WithTabPanelsStory,
|
||||
WithMockedURLTab2: WithMockedURLTab2Story,
|
||||
PrefixMatchingDeepNesting: PrefixMatchingDeepNestingStory,
|
||||
} = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const WithTabPanels = () => <WithTabPanelsStory.Component />;
|
||||
export const WithMockedURLTab2 = () => <WithMockedURLTab2Story.Component />;
|
||||
export const PrefixMatchingDeepNesting = () => (
|
||||
<PrefixMatchingDeepNestingStory.Component />
|
||||
);
|
||||
@@ -0,0 +1,122 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const tabsPropDefs: Record<string, PropDef> = {
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['string[]'],
|
||||
},
|
||||
selectedKey: {
|
||||
type: 'enum',
|
||||
values: ['string', 'null'],
|
||||
},
|
||||
defaultSelectedKey: {
|
||||
type: 'enum',
|
||||
values: ['string'],
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: [`(key: string) => void`],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tabPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
},
|
||||
hrefLang: {
|
||||
type: 'string',
|
||||
},
|
||||
target: {
|
||||
type: 'enum',
|
||||
values: ['HTMLAttributeAnchorTarget'],
|
||||
},
|
||||
rel: {
|
||||
type: 'string',
|
||||
},
|
||||
matchStrategy: {
|
||||
type: 'enum',
|
||||
values: ['exact', 'prefix'],
|
||||
},
|
||||
onHoverStart: {
|
||||
type: 'enum',
|
||||
values: [`(e: HoverEvent) => void`],
|
||||
},
|
||||
onHoverEnd: {
|
||||
type: 'enum',
|
||||
values: [`(e: HoverEvent) => void`],
|
||||
},
|
||||
onHoverChange: {
|
||||
type: 'enum',
|
||||
values: [`(isHovering: boolean) => void`],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tabsUsageSnippet = `import { Tabs } from '@backstage/ui';
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="tab-1">Tab 1</Tab>
|
||||
<Tab id="tab-2">Tab 2</Tab>
|
||||
<Tab id="tab-3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab-1">Content for Tab 1</TabPanel>
|
||||
<TabPanel id="tab-2">Content for Tab 2</TabPanel>
|
||||
<TabPanel id="tab-3">Content for Tab 3</TabPanel>
|
||||
</Tabs>`;
|
||||
|
||||
export const tabsDefaultSnippet = `import { Tabs } from '@backstage/ui';
|
||||
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="tab-1">Tab 1</Tab>
|
||||
<Tab id="tab-2">Tab 2</Tab>
|
||||
<Tab id="tab-3">Tab 3 With long title</Tab>
|
||||
</TabList>
|
||||
</Tabs>`;
|
||||
|
||||
export const tabsWithTabPanelsSnippet = `<Tabs>
|
||||
<TabList>
|
||||
<Tab id="settings">Settings</Tab>
|
||||
<Tab id="profile">Profile</Tab>
|
||||
<Tab id="preferences">Preferences</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="settings">Settings panel content goes here</TabPanel>
|
||||
<TabPanel id="profile">Profile panel content goes here</TabPanel>
|
||||
<TabPanel id="preferences">Preferences panel content goes here</TabPanel>
|
||||
</Tabs>`;
|
||||
|
||||
export const tabsWithLinksSnippet = `<Tabs>
|
||||
<TabList>
|
||||
<Tab id="tab-1" href="/tab-1">Tab 1</Tab>
|
||||
<Tab id="tab-2" href="/tab-2">Tab 2</Tab>
|
||||
<Tab id="tab-3" href="/tab-3">Tab 3 With long title</Tab>
|
||||
</TabList>
|
||||
</Tabs>`;
|
||||
|
||||
export const tabsWithDeeplyNestedRoutesSnippet = `<Tabs>
|
||||
<TabList>
|
||||
<Tab id="home" href="/home">Home</Tab>
|
||||
<Tab id="catalog" href="/catalog" matchStrategy="prefix">Catalog</Tab>
|
||||
<Tab id="mentorship" href="/mentorship" matchStrategy="prefix">Mentorship</Tab>
|
||||
</TabList>
|
||||
</Tabs>`;
|
||||
@@ -0,0 +1,115 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, WithLink, WithIcon, Sizes, RemovingTags, Disabled } from './stories';
|
||||
import {
|
||||
tagGroupPropDefs,
|
||||
tagPropDefs,
|
||||
usage,
|
||||
preview,
|
||||
withLink,
|
||||
disabled,
|
||||
withIcons,
|
||||
sizes,
|
||||
removingTags,
|
||||
} from './tag-group.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TagGroupDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="TagGroup"
|
||||
description="A tag group is a list of labels, categories, keywords, filters, or other items, that can be used to group and filter content."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={preview}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={usage} />
|
||||
|
||||
## API reference
|
||||
|
||||
### TagGroup
|
||||
|
||||
A tag group is a list of tags.
|
||||
|
||||
<PropsTable data={tagGroupPropDefs} />
|
||||
|
||||
### Tag
|
||||
|
||||
A tag is a single item in a tag group.
|
||||
|
||||
<PropsTable data={tagPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### With Links
|
||||
|
||||
A tag can be a link by passing a `href` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithLink />}
|
||||
code={withLink}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
A tag can have an icon by passing a `icon` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcon />}
|
||||
code={withIcons}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
A tag can have a size by passing a `size` prop. It could be `small` or `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={sizes}
|
||||
/>
|
||||
|
||||
### Removing tags
|
||||
|
||||
A tag can be removed by passing a `onRemove` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RemovingTags />}
|
||||
code={removingTags}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
A switch can be disabled using the `isDisabled` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={disabled}
|
||||
/>
|
||||
|
||||
<Theming definition={TagGroupDefinition} />
|
||||
|
||||
<ChangelogComponent component="switch" />
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/TagGroup/TagGroup.stories';
|
||||
|
||||
const {
|
||||
Default: DefaultStory,
|
||||
WithLink: WithLinkStory,
|
||||
WithIcon: WithIconStory,
|
||||
Sizes: SizesStory,
|
||||
RemovingTags: RemovingTagsStory,
|
||||
Disabled: DisabledStory,
|
||||
} = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const WithLink = () => <WithLinkStory.Component />;
|
||||
export const WithIcon = () => <WithIconStory.Component />;
|
||||
export const Sizes = () => <SizesStory.Component />;
|
||||
export const RemovingTags = () => <RemovingTagsStory.Component />;
|
||||
export const Disabled = () => <DisabledStory.Component />;
|
||||
@@ -0,0 +1,161 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const tagGroupPropDefs: Record<string, PropDef> = {
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
description: 'A list of row keys to disable.',
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['none', 'single', 'multiple'],
|
||||
description: 'The type of selection that is allowed in the collection.',
|
||||
},
|
||||
selectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The currently selected keys in the collection (controlled).',
|
||||
},
|
||||
defaultSelectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
description: 'The initial selected keys in the collection (uncontrolled).',
|
||||
},
|
||||
onRemove: {
|
||||
type: 'enum',
|
||||
values: ['(keys: Set<Key>) => void'],
|
||||
description: 'Handler that is called when a tag is removed.',
|
||||
},
|
||||
onSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(keys: Selection) => void'],
|
||||
description: 'Handler that is called when the selection changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tagPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'The id of the tag.',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
description: 'The text value of the tag.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the tag is disabled.',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
description: 'The href of the tag.',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
description: 'The icon of the tag.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const preview = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag>Banana</Tag>
|
||||
<Tag>Apple</Tag>
|
||||
<Tag>Orange</Tag>
|
||||
<Tag>Pear</Tag>
|
||||
<Tag>Grape</Tag>
|
||||
<Tag>Pineapple</Tag>
|
||||
<Tag>Strawberry</Tag>
|
||||
</TagGroup>
|
||||
`;
|
||||
|
||||
export const usage = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
|
||||
// Basic usage
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag>Tag 1</Tag>
|
||||
<Tag>Tag 2</Tag>
|
||||
<Tag>Tag 3</Tag>
|
||||
</TagGroup>
|
||||
|
||||
// With the items prop
|
||||
const list = [
|
||||
{ id: '1', name: 'Tag 1' },
|
||||
{ id: '2', name: 'Tag 2' },
|
||||
{ id: '3', name: 'Tag 3' },
|
||||
];
|
||||
|
||||
<TagGroup aria-label="Tag Group" items={list}>
|
||||
{item => <Tag>{item.name}</Tag>}
|
||||
</TagGroup>`;
|
||||
|
||||
export const withLink = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag href="/items/banana">Banana</Tag>
|
||||
<Tag href="/items/apple">Apple</Tag>
|
||||
<Tag href="/items/orange">Orange</Tag>
|
||||
...
|
||||
</TagGroup>`;
|
||||
|
||||
export const disabled = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag>Banana</Tag>
|
||||
<Tag isDisabled>Apple</Tag>
|
||||
<Tag isDisabled>Orange</Tag>
|
||||
<Tag>Pear</Tag>
|
||||
...
|
||||
</TagGroup>`;
|
||||
|
||||
export const withIcons = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag icon={<RiBugLine />}>Banana</Tag>
|
||||
<Tag icon={<RiAccountCircleLine />}>Apple</Tag>
|
||||
<Tag icon={<RiEyeLine />}>Orange</Tag>
|
||||
...
|
||||
</TagGroup>`;
|
||||
|
||||
export const sizes = `import { TagGroup, Tag, Flex } from '@backstage/ui';
|
||||
|
||||
<Flex direction="column">
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag size="small">Banana</Tag>
|
||||
<Tag size="small">Apple</Tag>
|
||||
<Tag size="small">Orange</Tag>
|
||||
...
|
||||
</TagGroup>
|
||||
<TagGroup aria-label="Tag Group">
|
||||
<Tag size="medium">Banana</Tag>
|
||||
<Tag size="medium">Apple</Tag>
|
||||
<Tag size="medium">Orange</Tag>
|
||||
...
|
||||
</TagGroup>
|
||||
</Flex>`;
|
||||
|
||||
export const removingTags = `import { TagGroup, Tag } from '@backstage/ui';
|
||||
import type { Selection } from 'react-aria-components';
|
||||
import { useListData } from 'react-stately';
|
||||
|
||||
const [selected, setSelected] = useState<Selection>(new Set(['travel']));
|
||||
|
||||
const list = useListData({
|
||||
initialItems: initialList,
|
||||
});
|
||||
|
||||
<TagGroup
|
||||
items={list.items}
|
||||
onRemove={keys => list.remove(...keys)}
|
||||
selectedKeys={selected}
|
||||
onSelectionChange={setSelected}
|
||||
{...args}
|
||||
>
|
||||
{item => <Tag>{item.name}</Tag>}
|
||||
</TagGroup>`;
|
||||
@@ -0,0 +1,65 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { WithLabel, Sizes, WithDescription } from './stories';
|
||||
import {
|
||||
inputPropDefs,
|
||||
textFieldUsageSnippet,
|
||||
textFieldDefaultSnippet,
|
||||
textFieldSizesSnippet,
|
||||
textFieldDescriptionSnippet,
|
||||
} from './text-field.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TextFieldDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
|
||||
<PageTitle
|
||||
title="TextField"
|
||||
description="A text field component for your forms."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<WithLabel />}
|
||||
code={textFieldDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={textFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={inputPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={textFieldSizesSnippet}
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
Here's a simple TextField with a description.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDescription />}
|
||||
code={textFieldDescriptionSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={TextFieldDefinition} />
|
||||
|
||||
<ChangelogComponent component="text-field" />
|
||||
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/TextField/TextField.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,43 @@
|
||||
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 textFieldUsageSnippet = `import { TextField } from '@backstage/ui';
|
||||
|
||||
<TextField />`;
|
||||
|
||||
export const textFieldDefaultSnippet = `<TextField label="Label" placeholder="Enter a URL" />`;
|
||||
|
||||
export const textFieldSizesSnippet = `<Flex direction="row" gap="4">
|
||||
<TextField size="small" placeholder="Small" icon={<Icon name="sparkling" />} />
|
||||
<TextField size="medium" placeholder="Medium" icon={<Icon name="sparkling" />} />
|
||||
</Flex>`;
|
||||
|
||||
export const textFieldDescriptionSnippet = `<TextField label="Label" description="Description" placeholder="Enter a URL" />`;
|
||||
@@ -0,0 +1,93 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Default, AllVariants, AllWeights, AllColors, Truncate } from './stories';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
textPropDefs,
|
||||
textUsageSnippet,
|
||||
textDefaultSnippet,
|
||||
textVariantsSnippet,
|
||||
textWeightsSnippet,
|
||||
textTruncateSnippet,
|
||||
textResponsiveSnippet,
|
||||
textColorsSnippet,
|
||||
} from './text.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TextDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Text"
|
||||
description="The `Text` component is used to display content on your page."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={textDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={textUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={textPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### All variants
|
||||
|
||||
The `Text` component has a `variant` prop that can be used to change the
|
||||
appearance of the text.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<AllVariants />}
|
||||
code={textVariantsSnippet}
|
||||
/>
|
||||
|
||||
### All weights
|
||||
|
||||
The `Text` component has a `weight` prop that can be used to change the
|
||||
appearance of the text.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<AllWeights />}
|
||||
code={textWeightsSnippet}
|
||||
/>
|
||||
|
||||
### All colors
|
||||
|
||||
The `Text` component has a `color` prop that can be used to change the
|
||||
appearance of the text.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<AllColors />}
|
||||
code={textColorsSnippet}
|
||||
/>
|
||||
|
||||
### Truncate
|
||||
|
||||
The `Text` component has a `truncate` prop that can be used to truncate the text.
|
||||
|
||||
<Snippet
|
||||
open
|
||||
preview={<Truncate />}
|
||||
code={textTruncateSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
You can also use the `variant` prop to change the appearance of the text based
|
||||
on the screen size.
|
||||
|
||||
<CodeBlock code={textResponsiveSnippet} />
|
||||
|
||||
<Theming definition={TextDefinition} />
|
||||
|
||||
<ChangelogComponent component="text" />
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Text/Text.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,131 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
} from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const textPropDefs: Record<string, PropDef> = {
|
||||
as: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'p',
|
||||
'span',
|
||||
'label',
|
||||
'div',
|
||||
'strong',
|
||||
'em',
|
||||
'small',
|
||||
],
|
||||
default: 'span',
|
||||
responsive: true,
|
||||
},
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'title-large',
|
||||
'title-medium',
|
||||
'title-small',
|
||||
'title-x-small',
|
||||
'body-large',
|
||||
'body-medium',
|
||||
'body-small',
|
||||
'body-x-small',
|
||||
],
|
||||
responsive: true,
|
||||
},
|
||||
weight: {
|
||||
type: 'enum',
|
||||
values: ['regular', 'bold'],
|
||||
responsive: true,
|
||||
},
|
||||
color: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'danger', 'warning', 'success'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
truncate: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const textUsageSnippet = `import { Text } from '@backstage/ui';
|
||||
|
||||
<Text />`;
|
||||
|
||||
export const textDefaultSnippet = `<Text style={{ maxWidth: '600px' }}>
|
||||
A man looks at a painting in a museum and says, "Brothers and sisters I
|
||||
have none, but that man's father is my father's son." Who is
|
||||
in the painting?
|
||||
</Text>`;
|
||||
|
||||
export const textVariantsSnippet = `<Flex direction="column" gap="4">
|
||||
<Text variant="title-large">...</Text>
|
||||
<Text variant="title-medium">...</Text>
|
||||
<Text variant="title-small">...</Text>
|
||||
<Text variant="title-x-small">...</Text>
|
||||
<Text variant="body-large">...</Text>
|
||||
<Text variant="body-medium">...</Text>
|
||||
<Text variant="body-small">...</Text>
|
||||
<Text variant="body-x-small">...</Text>
|
||||
</Flex>`;
|
||||
|
||||
export const textWeightsSnippet = `<Flex direction="column" gap="4">
|
||||
<Flex>
|
||||
<Text variant="title-large" weight="regular">A fox</Text>
|
||||
<Text variant="title-large" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="title-medium" weight="regular">A fox</Text>
|
||||
<Text variant="title-medium" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="title-small" weight="regular">A fox</Text>
|
||||
<Text variant="title-small" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="title-x-small" weight="regular">A fox</Text>
|
||||
<Text variant="title-x-small" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="body-large" weight="regular">A fox</Text>
|
||||
<Text variant="body-large" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="body-medium" weight="regular">A fox</Text>
|
||||
<Text variant="body-medium" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="body-small" weight="regular">A fox</Text>
|
||||
<Text variant="body-small" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Text variant="body-x-small" weight="regular">A fox</Text>
|
||||
<Text variant="body-x-small" weight="bold">A turtle</Text>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
|
||||
export const textColorsSnippet = `<Flex direction="column" gap="4">
|
||||
<Text color="primary">I am primary</Text>
|
||||
<Text color="secondary">I am secondary</Text>
|
||||
<Text color="danger">I am danger</Text>
|
||||
<Text color="warning">I am warning</Text>
|
||||
<Text color="success">I am success</Text>
|
||||
</Flex>`;
|
||||
|
||||
export const textTruncateSnippet = `<Text as="p" truncate>...</Text>`;
|
||||
|
||||
export const textResponsiveSnippet = `<Text variant={{ initial: 'body', lg: 'subtitle' }}>
|
||||
Responsive text
|
||||
</Text>`;
|
||||
@@ -0,0 +1,126 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
toggleButtonGroupPropDefs,
|
||||
toggleButtonGroupUsageSnippet,
|
||||
toggleButtonGroupSingleSnippet,
|
||||
toggleButtonGroupMultipleSnippet,
|
||||
toggleButtonGroupVerticalSnippet,
|
||||
toggleButtonGroupDisabledSnippet,
|
||||
toggleButtonGroupDisallowEmptySnippet,
|
||||
toggleButtonGroupIconsSnippet,
|
||||
toggleButtonGroupIconsOnlySnippet,
|
||||
toggleButtonGroupSurfacesSnippet,
|
||||
} from './toggle-button-group.props';
|
||||
import { SingleSelection, Surfaces, MultipleSelection, WithIcons, IconsOnly, DisallowEmptySelection, Orientation, DisabledGroup } from './stories';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { ToggleButtonGroupDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="ToggleButtonGroup"
|
||||
description="A grouping container for related [ToggleButtons](./toggle-button) supporting single or multiple selection."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SingleSelection />}
|
||||
code={toggleButtonGroupSingleSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={toggleButtonGroupUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={toggleButtonGroupPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Surfaces
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Surfaces />}
|
||||
code={toggleButtonGroupSurfacesSnippet}
|
||||
/>
|
||||
|
||||
### Single Selection
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SingleSelection />}
|
||||
code={toggleButtonGroupSingleSnippet}
|
||||
/>
|
||||
|
||||
### Multiple Selection
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MultipleSelection />}
|
||||
code={toggleButtonGroupMultipleSnippet}
|
||||
/>
|
||||
|
||||
### Icons and Text
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcons />}
|
||||
code={toggleButtonGroupIconsSnippet}
|
||||
/>
|
||||
|
||||
### Icons Only
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<IconsOnly />}
|
||||
code={toggleButtonGroupIconsOnlySnippet}
|
||||
/>
|
||||
|
||||
### Disallow Empty Selection
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<DisallowEmptySelection />}
|
||||
code={toggleButtonGroupDisallowEmptySnippet}
|
||||
/>
|
||||
|
||||
### Vertical Orientation
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Orientation />}
|
||||
code={toggleButtonGroupVerticalSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<DisabledGroup />}
|
||||
code={toggleButtonGroupDisabledSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={ToggleButtonGroupDefinition} />
|
||||
|
||||
<ChangelogComponent component="toggle-button-group" />
|
||||
@@ -0,0 +1,25 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories';
|
||||
|
||||
const {
|
||||
SingleSelection: SingleSelectionStory,
|
||||
Surfaces: SurfacesStory,
|
||||
MultipleSelection: MultipleSelectionStory,
|
||||
WithIcons: WithIconsStory,
|
||||
IconsOnly: IconsOnlyStory,
|
||||
DisallowEmptySelection: DisallowEmptySelectionStory,
|
||||
Orientation: OrientationStory,
|
||||
DisabledGroup: DisabledGroupStory,
|
||||
} = stories;
|
||||
|
||||
export const SingleSelection = () => <SingleSelectionStory.Component />;
|
||||
export const Surfaces = () => <SurfacesStory.Component />;
|
||||
export const MultipleSelection = () => <MultipleSelectionStory.Component />;
|
||||
export const WithIcons = () => <WithIconsStory.Component />;
|
||||
export const IconsOnly = () => <IconsOnlyStory.Component />;
|
||||
export const DisallowEmptySelection = () => (
|
||||
<DisallowEmptySelectionStory.Component />
|
||||
);
|
||||
export const Orientation = () => <OrientationStory.Component />;
|
||||
export const DisabledGroup = () => <DisabledGroupStory.Component />;
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const toggleButtonGroupPropDefs: Record<string, PropDef> = {
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['single', 'multiple'],
|
||||
default: 'single',
|
||||
},
|
||||
orientation: {
|
||||
type: 'enum',
|
||||
values: ['horizontal', 'vertical'],
|
||||
default: 'horizontal',
|
||||
responsive: true,
|
||||
},
|
||||
selectedKeys: { type: 'enum', values: ['Iterable<Key>'] },
|
||||
defaultSelectedKeys: { type: 'enum', values: ['Iterable<Key>'] },
|
||||
onSelectionChange: { type: 'enum', values: ['(keys) => void'] },
|
||||
isDisabled: { type: 'boolean', default: 'false' },
|
||||
disallowEmptySelection: { type: 'boolean', default: 'false' },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const toggleButtonGroupUsageSnippet = `import { ToggleButtonGroup, ToggleButton } from '@backstage/ui';
|
||||
|
||||
<ToggleButtonGroup selectionMode="single">
|
||||
<ToggleButton id="dogs">Dogs</ToggleButton>
|
||||
<ToggleButton id="cats">Cats</ToggleButton>
|
||||
<ToggleButton id="birds">Birds</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupSingleSnippet = `<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['dogs']}>
|
||||
<ToggleButton id="dogs">Dogs</ToggleButton>
|
||||
<ToggleButton id="cat">Cats</ToggleButton>
|
||||
<ToggleButton id="bird">Birds</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupMultipleSnippet = `<ToggleButtonGroup selectionMode="multiple" defaultSelectedKeys={['frontend']}>
|
||||
<ToggleButton id="frontend">Frontend</ToggleButton>
|
||||
<ToggleButton id="backend">Backend</ToggleButton>
|
||||
<ToggleButton id="platform">Platform</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupVerticalSnippet = `<ToggleButtonGroup selectionMode="single" orientation="vertical">
|
||||
<ToggleButton id="morning">Morning</ToggleButton>
|
||||
<ToggleButton id="afternoon">Afternoon</ToggleButton>
|
||||
<ToggleButton id="evening">Evening</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupDisabledSnippet = `<ToggleButtonGroup selectionMode="single" isDisabled>
|
||||
<ToggleButton id="cat">Cat</ToggleButton>
|
||||
<ToggleButton id="dog">Dog</ToggleButton>
|
||||
<ToggleButton id="bird">Bird</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupDisallowEmptySnippet = `<ToggleButtonGroup selectionMode="single" disallowEmptySelection defaultSelectedKeys={['one']}>
|
||||
<ToggleButton id="one">One</ToggleButton>
|
||||
<ToggleButton id="two">Two</ToggleButton>
|
||||
<ToggleButton id="three">Three</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupIconsSnippet = `import { RiCloudLine, RiStarFill, RiStarLine, RiArrowRightSLine } from '@remixicon/react';
|
||||
|
||||
<ToggleButtonGroup selectionMode="multiple" defaultSelectedKeys={['cloud']}>
|
||||
<ToggleButton id="cloud" aria-label="Cloud" iconStart={<RiCloudLine />} />
|
||||
<ToggleButton
|
||||
id="starred"
|
||||
aria-label="Starred"
|
||||
iconStart={<RiStarFill />}
|
||||
/>
|
||||
<ToggleButton id="star" iconStart={<RiStarLine />}>
|
||||
Star
|
||||
</ToggleButton>
|
||||
<ToggleButton id="next" iconEnd={<RiArrowRightSLine />}>
|
||||
Next
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupIconsOnlySnippet = `import { RiCloudLine, RiStarLine, RiArrowRightSLine } from '@remixicon/react';
|
||||
|
||||
<ToggleButtonGroup selectionMode="multiple" defaultSelectedKeys={['cloud']}>
|
||||
<ToggleButton id="cloud" iconStart={<RiCloudLine />} />
|
||||
<ToggleButton id="star" iconStart={<RiStarLine />} />
|
||||
<ToggleButton id="next" iconEnd={<RiArrowRightSLine />} />
|
||||
</ToggleButtonGroup>`;
|
||||
|
||||
export const toggleButtonGroupSurfacesSnippet = `<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Default</Text>
|
||||
<Flex align="center" p="4" gap="4">
|
||||
<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['option1']}>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4" gap="4">
|
||||
<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['option1']}>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4" gap="4">
|
||||
<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['option1']}>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4" gap="4">
|
||||
<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['option1']}>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4" gap="4">
|
||||
<ToggleButtonGroup selectionMode="single" defaultSelectedKeys={['option1']}>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, Surfaces, Sizes, WithIcons, Disabled, Controlled, DynamicContent } from './stories';
|
||||
import {
|
||||
toggleButtonPropDefs,
|
||||
toggleButtonUsageSnippet,
|
||||
toggleButtonSurfacesSnippet,
|
||||
toggleButtonSizesSnippet,
|
||||
toggleButtonIconsSnippet,
|
||||
toggleButtonDisabledSnippet,
|
||||
toggleButtonControlledSnippet,
|
||||
toggleButtonFunctionChildrenSnippet,
|
||||
} from './toggle-button.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { ToggleButtonDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="ToggleButton"
|
||||
description="A button that toggles between selected and unselected states. Can be used as a single toggle or as part of a [ToggleButtonGroup](./toggle-button-group)."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={toggleButtonUsageSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={toggleButtonUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={toggleButtonPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Surfaces
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Surfaces />}
|
||||
code={toggleButtonSurfacesSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={toggleButtonSizesSnippet}
|
||||
/>
|
||||
|
||||
### With Icons
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcons />}
|
||||
code={toggleButtonIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={toggleButtonDisabledSnippet}
|
||||
/>
|
||||
|
||||
### Controlled
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Controlled />}
|
||||
code={toggleButtonControlledSnippet}
|
||||
/>
|
||||
|
||||
### Dynamic Content with Function Children
|
||||
|
||||
The `children` prop can be a function that receives render props, allowing you to dynamically customize the button content based on component state (such as `isSelected`, `isDisabled`, `isHovered`, etc.).
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<DynamicContent />}
|
||||
code={toggleButtonFunctionChildrenSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={ToggleButtonDefinition} />
|
||||
|
||||
<ChangelogComponent component="toggle-button" />
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/ToggleButton/ToggleButton.stories';
|
||||
|
||||
const {
|
||||
Default: DefaultStory,
|
||||
Surfaces: SurfacesStory,
|
||||
Sizes: SizesStory,
|
||||
WithIcons: WithIconsStory,
|
||||
Disabled: DisabledStory,
|
||||
Controlled: ControlledStory,
|
||||
DynamicContent: DynamicContentStory,
|
||||
} = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const Surfaces = () => <SurfacesStory.Component />;
|
||||
export const Sizes = () => <SizesStory.Component />;
|
||||
export const WithIcons = () => <WithIconsStory.Component />;
|
||||
export const Disabled = () => <DisabledStory.Component />;
|
||||
export const Controlled = () => <ControlledStory.Component />;
|
||||
export const DynamicContent = () => <DynamicContentStory.Component />;
|
||||
@@ -0,0 +1,195 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const toggleButtonPropDefs: Record<string, PropDef> = {
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
},
|
||||
onSurface: {
|
||||
type: 'enum',
|
||||
values: ['0', '1', '2', '3', 'danger', 'warning', 'success', 'auto'],
|
||||
description: 'Surface level this toggle is placed on',
|
||||
responsive: true,
|
||||
},
|
||||
iconStart: { type: 'enum', values: ['ReactNode'] },
|
||||
iconEnd: { type: 'enum', values: ['ReactNode'] },
|
||||
isSelected: { type: 'boolean' },
|
||||
defaultSelected: { type: 'boolean' },
|
||||
onChange: { type: 'enum', values: ['(isSelected: boolean) => void'] },
|
||||
isDisabled: { type: 'boolean', default: 'false' },
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode', '(values: ToggleButtonRenderProps) => ReactNode'],
|
||||
description:
|
||||
'The children of the component. A function may be provided to alter the children based on component state (such as `isSelected`, `isDisabled`, `isHovered`, etc.).',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const toggleButtonUsageSnippet = `import { ToggleButton } from '@backstage/ui';
|
||||
|
||||
<ToggleButton>Toggle</ToggleButton>`;
|
||||
|
||||
export const toggleButtonSurfacesSnippet = `<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Default</Text>
|
||||
<Flex align="center" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
|
||||
export const toggleButtonSizesSnippet = `<Flex align="center">
|
||||
<ToggleButton size="small">Small</ToggleButton>
|
||||
<ToggleButton size="medium">Medium</ToggleButton>
|
||||
</Flex>`;
|
||||
|
||||
export const toggleButtonIconsSnippet = `import { RiStarLine, RiStarFill, RiCheckLine } from '@remixicon/react';
|
||||
|
||||
<Flex align="center">
|
||||
<ToggleButton iconStart={<RiStarLine />}>Favorite</ToggleButton>
|
||||
<ToggleButton iconStart={<RiStarFill />} defaultSelected>Starred</ToggleButton>
|
||||
<ToggleButton iconEnd={<RiCheckLine />}>Confirm</ToggleButton>
|
||||
</Flex>`;
|
||||
|
||||
export const toggleButtonDisabledSnippet = `<Flex align="center">
|
||||
<ToggleButton isDisabled>Disabled</ToggleButton>
|
||||
<ToggleButton defaultSelected isDisabled>Selected</ToggleButton>
|
||||
</Flex>`;
|
||||
|
||||
export const toggleButtonControlledSnippet = `import { useState } from 'react';
|
||||
import { RiStarFill, RiStarLine } from '@remixicon/react';
|
||||
|
||||
const [selected, setSelected] = useState(false);
|
||||
|
||||
<ToggleButton
|
||||
isSelected={selected}
|
||||
onChange={setSelected}
|
||||
iconStart={selected ? <RiStarFill /> : <RiStarLine />}
|
||||
>
|
||||
{selected ? 'Starred' : 'Not starred'}
|
||||
</ToggleButton>`;
|
||||
|
||||
export const toggleButtonFunctionChildrenSnippet = `import { RiStarFill, RiStarLine } from '@remixicon/react';
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="2">
|
||||
<Text weight="bold">Example 1: Selection State</Text>
|
||||
<Flex align="center" gap="2">
|
||||
<ToggleButton defaultSelected>
|
||||
{({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')}
|
||||
</ToggleButton>
|
||||
<ToggleButton>
|
||||
{({ isSelected }) => (isSelected ? '✓ Selected' : 'Not Selected')}
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Text weight="bold">Example 2: Multiple States</Text>
|
||||
<Flex align="center" gap="2">
|
||||
<ToggleButton defaultSelected>
|
||||
{({ isSelected, isHovered }) => {
|
||||
const states = [];
|
||||
if (isSelected) states.push('on');
|
||||
else states.push('off');
|
||||
if (isHovered) states.push('hovered');
|
||||
return \`Email (\${states.join(', ')})\`;
|
||||
}}
|
||||
</ToggleButton>
|
||||
<ToggleButton>
|
||||
{({ isSelected, isHovered }) => {
|
||||
const states = [];
|
||||
if (isSelected) states.push('on');
|
||||
else states.push('off');
|
||||
if (isHovered) states.push('hovered');
|
||||
return \`Push (\${states.join(', ')})\`;
|
||||
}}
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Text weight="bold">Example 3: Conditional Icons</Text>
|
||||
<Flex align="center" gap="2">
|
||||
<ToggleButton>
|
||||
{({ isSelected }) => (
|
||||
<>
|
||||
{isSelected ? <RiStarFill /> : <RiStarLine />}
|
||||
<span>{isSelected ? 'Starred' : 'Star'}</span>
|
||||
</>
|
||||
)}
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="2">
|
||||
<Text weight="bold">Example 4: Status Indicators</Text>
|
||||
<Flex align="center" gap="2">
|
||||
<ToggleButton defaultSelected>
|
||||
{({ isSelected }) => (
|
||||
<Flex align="center" gap="2">
|
||||
<span
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: isSelected
|
||||
? 'var(--bui-fg-success)'
|
||||
: 'var(--bui-fg-secondary)',
|
||||
}}
|
||||
/>
|
||||
<span>Active</span>
|
||||
</Flex>
|
||||
)}
|
||||
</ToggleButton>
|
||||
<ToggleButton>
|
||||
{({ isSelected }) => (
|
||||
<Flex align="center" gap="2">
|
||||
<span
|
||||
style={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: isSelected
|
||||
? 'var(--bui-fg-danger)'
|
||||
: 'var(--bui-fg-secondary)',
|
||||
}}
|
||||
/>
|
||||
<span>Inactive</span>
|
||||
</Flex>
|
||||
)}
|
||||
</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Default } from './stories';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
tooltipDefaultSnippet,
|
||||
tooltipUsageSnippet,
|
||||
tooltipTriggerPropDefs,
|
||||
tooltipPropDefs,
|
||||
} from './tooltip.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TooltipDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="Tooltip"
|
||||
description="A tooltip displays a description of an element on hover or focus."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={tooltipDefaultSnippet}
|
||||
align="center"
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={tooltipUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### TooltipTrigger
|
||||
|
||||
The trigger will wrap both the trigger and the tooltip.
|
||||
|
||||
<PropsTable data={tooltipTriggerPropDefs} />
|
||||
|
||||
### Tooltip
|
||||
|
||||
The tooltip will wrap the content of the tooltip.
|
||||
|
||||
<PropsTable data={tooltipPropDefs} />
|
||||
|
||||
<Theming definition={TooltipDefinition} />
|
||||
|
||||
<ChangelogComponent component="tooltip" />
|
||||
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/Tooltip/Tooltip.stories';
|
||||
|
||||
const { Default: DefaultStory } = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
} from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const tooltipTriggerPropDefs: Record<string, PropDef> = {
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
delay: {
|
||||
type: 'number',
|
||||
default: '600',
|
||||
},
|
||||
closeDelay: {
|
||||
type: 'number',
|
||||
default: '500',
|
||||
},
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
};
|
||||
|
||||
export const tooltipPropDefs: 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: '0',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const tooltipUsageSnippet = `import { TooltipTrigger, Tooltip, Button } from '@backstage/ui';
|
||||
|
||||
<TooltipTrigger>
|
||||
<Button>Button</Button>
|
||||
<Tooltip>I am a tooltip</Tooltip>
|
||||
</TooltipTrigger>`;
|
||||
|
||||
export const tooltipDefaultSnippet = `<TooltipTrigger>
|
||||
<Button>Button</Button>
|
||||
<Tooltip>I am a tooltip</Tooltip>
|
||||
</TooltipTrigger>`;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Default, ExampleUsage } from './stories';
|
||||
import {
|
||||
visuallyHiddenPropDefs,
|
||||
visuallyHiddenUsageSnippet,
|
||||
visuallyHiddenDefaultSnippet,
|
||||
visuallyHiddenExampleUsageSnippet,
|
||||
} from './visually-hidden.props';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { VisuallyHiddenDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
title="VisuallyHidden"
|
||||
description="Visually hides content while keeping it accessible to screen readers."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="start"
|
||||
py={4}
|
||||
preview={<Default />}
|
||||
code={visuallyHiddenDefaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={visuallyHiddenUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={visuallyHiddenPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Example Usage
|
||||
|
||||
Here's an example of providing screen reader context for a list of links in a footer.
|
||||
|
||||
<Snippet
|
||||
align="start"
|
||||
py={4}
|
||||
preview={<ExampleUsage />}
|
||||
code={visuallyHiddenExampleUsageSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
<Theming definition={VisuallyHiddenDefinition} />
|
||||
|
||||
<ChangelogComponent component="visually-hidden" />
|
||||
@@ -0,0 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import * as stories from '@backstage/ui/src/components/VisuallyHidden/VisuallyHidden.stories';
|
||||
|
||||
const { Default: DefaultStory, ExampleUsage: ExampleUsageStory } = stories;
|
||||
|
||||
export const Default = () => <DefaultStory.Component />;
|
||||
export const ExampleUsage = () => <ExampleUsageStory.Component />;
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const visuallyHiddenPropDefs: Record<string, PropDef> = {
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const visuallyHiddenUsageSnippet = `import { VisuallyHidden } from '@backstage/ui';
|
||||
|
||||
<VisuallyHidden>
|
||||
This content is visually hidden but accessible to screen readers
|
||||
</VisuallyHidden>`;
|
||||
|
||||
export const visuallyHiddenDefaultSnippet = `<Flex direction="column" gap="4">
|
||||
<Text as="p">
|
||||
This text is followed by a paragraph that is visually hidden but
|
||||
accessible to screen readers. Try using a screen reader to hear it, or
|
||||
inspect the DOM to see it's there.
|
||||
</Text>
|
||||
<VisuallyHidden>
|
||||
This content is visually hidden but accessible to screen readers
|
||||
</VisuallyHidden>
|
||||
</Flex>`;
|
||||
|
||||
export const visuallyHiddenExampleUsageSnippet = `<Flex direction="column" gap="4">
|
||||
<VisuallyHidden>
|
||||
<Text as="h2">Footer links</Text>
|
||||
</VisuallyHidden>
|
||||
<Text as="p">
|
||||
<a href="#">About us</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Jobs</a>
|
||||
</Text>
|
||||
<Text as="p">
|
||||
<a href="#">Terms and Conditions</a>
|
||||
</Text>
|
||||
</Flex>`;
|
||||
Reference in New Issue
Block a user