Merge pull request #32339 from backstage/bui-docs-migration
Bui docs migration
This commit is contained in:
@@ -26,7 +26,6 @@
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/mdx": "15.5.7",
|
||||
"@remixicon/react": "^4.6.0",
|
||||
"@storybook/react": "^8.6.12",
|
||||
"@uiw/codemirror-themes": "^4.23.7",
|
||||
"@uiw/react-codemirror": "^4.23.7",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -37,8 +36,7 @@
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"shiki": "^3.13.0",
|
||||
"storybook": "^8.6.15"
|
||||
"shiki": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@octokit/rest": "^22.0.1",
|
||||
|
||||
@@ -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,130 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionTrigger,
|
||||
AccordionPanel,
|
||||
AccordionGroup,
|
||||
} from '../../../../../packages/ui/src/components/Accordion/Accordion';
|
||||
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
|
||||
const Content = () => (
|
||||
<Box>
|
||||
<Text as="p">
|
||||
It's the edge of the world and all of Western civilization
|
||||
</Text>
|
||||
<Text as="p">
|
||||
The sun may rise in the East, at least it settled in a final location
|
||||
</Text>
|
||||
<Text as="p">
|
||||
It's understood that Hollywood sells Californication
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithSubtitle = () => {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionTrigger
|
||||
title="Advanced Settings"
|
||||
subtitle="Configure additional options"
|
||||
/>
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomTrigger = () => {
|
||||
return (
|
||||
<Accordion>
|
||||
<AccordionTrigger>
|
||||
<Box>
|
||||
<Text as="div" variant="body-large" weight="bold">
|
||||
Custom Multi-line Trigger
|
||||
</Text>
|
||||
<Text as="div" variant="body-medium" color="secondary">
|
||||
Click to expand additional details and configuration options
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionTrigger>
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultExpanded = () => {
|
||||
return (
|
||||
<Accordion defaultExpanded>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
export const GroupSingleOpen = () => {
|
||||
return (
|
||||
<AccordionGroup>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Third Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const GroupMultipleOpen = () => {
|
||||
return (
|
||||
<AccordionGroup allowsMultiple>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Third Panel" />
|
||||
<AccordionPanel>
|
||||
<Content />
|
||||
</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +1,32 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { AccordionSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
accordionPropDefs,
|
||||
accordionTriggerPropDefs,
|
||||
accordionPanelPropDefs,
|
||||
accordionGroupPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
accordionUsageSnippet,
|
||||
accordionWithSubtitleSnippet,
|
||||
accordionCustomTriggerSnippet,
|
||||
accordionDefaultExpandedSnippet,
|
||||
accordionGroupSingleOpenSnippet,
|
||||
accordionGroupMultipleOpenSnippet,
|
||||
} from './accordion.props';
|
||||
defaultSnippet,
|
||||
withSubtitleSnippet,
|
||||
customTriggerSnippet,
|
||||
defaultExpandedSnippet,
|
||||
groupSingleOpenSnippet,
|
||||
groupMultipleOpenSnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
Default,
|
||||
WithSubtitle,
|
||||
CustomTrigger,
|
||||
DefaultExpanded,
|
||||
GroupSingleOpen,
|
||||
GroupMultipleOpen,
|
||||
} from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { AccordionDefinition } from '../utils/definitions';
|
||||
import { AccordionDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -28,8 +38,8 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<AccordionSnippet story="Default" />}
|
||||
code={accordionUsageSnippet}
|
||||
preview={<Default />}
|
||||
code={defaultSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
@@ -72,8 +82,8 @@ Here's a view when using both title and subtitle props.
|
||||
align="center"
|
||||
py={4}
|
||||
height={240}
|
||||
preview={<AccordionSnippet story="WithSubtitle" />}
|
||||
code={accordionWithSubtitleSnippet}
|
||||
preview={<WithSubtitle />}
|
||||
code={withSubtitleSnippet}
|
||||
/>
|
||||
|
||||
### Custom Trigger
|
||||
@@ -84,8 +94,8 @@ Here's a view when providing custom multi-line content as children.
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="CustomTrigger" />}
|
||||
code={accordionCustomTriggerSnippet}
|
||||
preview={<CustomTrigger />}
|
||||
code={customTriggerSnippet}
|
||||
/>
|
||||
|
||||
### Default Expanded
|
||||
@@ -96,8 +106,8 @@ Here's a view when the panel is expanded by default.
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="DefaultExpanded" />}
|
||||
code={accordionDefaultExpandedSnippet}
|
||||
preview={<DefaultExpanded />}
|
||||
code={defaultExpandedSnippet}
|
||||
/>
|
||||
|
||||
### Group Single Open
|
||||
@@ -108,8 +118,8 @@ Here's a view when only one accordion can be open at a time.
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="GroupSingleOpen" />}
|
||||
code={accordionGroupSingleOpenSnippet}
|
||||
preview={<GroupSingleOpen />}
|
||||
code={groupSingleOpenSnippet}
|
||||
/>
|
||||
|
||||
### Group Multiple Open
|
||||
@@ -120,8 +130,8 @@ Here's a view when multiple accordions can be open simultaneously.
|
||||
align="center"
|
||||
py={4}
|
||||
height={280}
|
||||
preview={<AccordionSnippet story="GroupMultipleOpen" />}
|
||||
code={accordionGroupMultipleOpenSnippet}
|
||||
preview={<GroupMultipleOpen />}
|
||||
code={groupMultipleOpenSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={AccordionDefinition} />
|
||||
@@ -0,0 +1,59 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const accordionPropDefs: Record<string, PropDef> = {
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode', '(state: { isExpanded: boolean }) => ReactNode'],
|
||||
},
|
||||
defaultExpanded: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
isExpanded: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onExpandedChange: {
|
||||
type: 'enum',
|
||||
values: ['(isExpanded: boolean) => void'],
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionTriggerPropDefs: Record<string, PropDef> = {
|
||||
level: {
|
||||
type: 'enum',
|
||||
values: ['1', '2', '3', '4', '5', '6'],
|
||||
default: '3',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
subtitle: {
|
||||
type: 'string',
|
||||
},
|
||||
children: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionPanelPropDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const accordionGroupPropDefs: Record<string, PropDef> = {
|
||||
allowsMultiple: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
export const accordionUsageSnippet = `import { Accordion, AccordionTrigger, AccordionPanel } from '@backstage/ui';
|
||||
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>Your content</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const defaultSnippet = `<Accordion>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const withSubtitleSnippet = `<Accordion>
|
||||
<AccordionTrigger
|
||||
title="Advanced Settings"
|
||||
subtitle="Configure additional options"
|
||||
/>
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const customTriggerSnippet = `<Accordion>
|
||||
<AccordionTrigger>
|
||||
<Box>
|
||||
<Text as="div" variant="body-large" weight="bold">
|
||||
Custom Multi-line Trigger
|
||||
</Text>
|
||||
<Text as="div" variant="body-medium" color="secondary">
|
||||
Click to expand additional details and configuration options
|
||||
</Text>
|
||||
</Box>
|
||||
</AccordionTrigger>
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const defaultExpandedSnippet = `<Accordion defaultExpanded>
|
||||
<AccordionTrigger title="Toggle Panel" />
|
||||
<AccordionPanel>
|
||||
<Text>Your content here</Text>
|
||||
</AccordionPanel>
|
||||
</Accordion>`;
|
||||
|
||||
export const groupSingleOpenSnippet = `<AccordionGroup>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>Content 1</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>Content 2</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>`;
|
||||
|
||||
export const groupMultipleOpenSnippet = `<AccordionGroup allowsMultiple>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="First Panel" />
|
||||
<AccordionPanel>Content 1</AccordionPanel>
|
||||
</Accordion>
|
||||
<Accordion>
|
||||
<AccordionTrigger title="Second Panel" />
|
||||
<AccordionPanel>Content 2</AccordionPanel>
|
||||
</Accordion>
|
||||
</AccordionGroup>`;
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { Avatar } from '../../../../../packages/ui/src/components/Avatar/Avatar';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Fallback = () => {
|
||||
return (
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
|
||||
name="Charles de Dreuille"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="6">
|
||||
<Flex>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
size="x-small"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
size="small"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
size="medium"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
size="large"
|
||||
/>
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
size="x-large"
|
||||
/>
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Avatar name="Charles de Dreuille" size="x-small" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="small" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="medium" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="large" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="x-large" src="" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Purpose = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="1">
|
||||
<Text variant="title-x-small">Informative (default)</Text>
|
||||
<Text variant="body-medium">
|
||||
Use when avatar appears alone. Announced as "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 avatar appears with adjacent text. Hidden from screen
|
||||
readers:
|
||||
</Text>
|
||||
<Flex gap="2" align="center">
|
||||
<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
purpose="decoration"
|
||||
/>
|
||||
<Text>Charles de Dreuille</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,18 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { AvatarSnippet } from '@/snippets/stories-snippets';
|
||||
import { avatarPropDefs } from './props-definition';
|
||||
import {
|
||||
avatarPropDefs,
|
||||
snippetUsage,
|
||||
snippetSizes,
|
||||
snippetFallback,
|
||||
snippetPurpose,
|
||||
} from './avatar.props';
|
||||
avatarUsageSnippet,
|
||||
defaultSnippet,
|
||||
sizesSnippet,
|
||||
fallbackSnippet,
|
||||
purposeSnippet,
|
||||
} from './snippets';
|
||||
import { Default, Sizes, Fallback, Purpose } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { AvatarDefinition } from '../utils/definitions';
|
||||
import { AvatarDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -19,16 +20,11 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="An avatar component with a fallback for initials."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<AvatarSnippet story="Default" />}
|
||||
code={`<Avatar src="https://avatars.githubusercontent.com/u/1540635?v=4" name="Charles de Dreuille" />`}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={snippetUsage} />
|
||||
<CodeBlock code={avatarUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
@@ -40,13 +36,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
Avatar sizes can be set using the `size` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AvatarSnippet story="Sizes" />}
|
||||
code={snippetSizes}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### Fallback
|
||||
|
||||
@@ -56,20 +46,15 @@ If the image is not available, the avatar will show the initials of the name.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AvatarSnippet story="Fallback" />}
|
||||
code={snippetFallback}
|
||||
preview={<Fallback />}
|
||||
code={fallbackSnippet}
|
||||
/>
|
||||
|
||||
### The `purpose` prop
|
||||
|
||||
Control how the avatar is announced to screen readers using the `purpose` prop.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<AvatarSnippet story="Purpose" />}
|
||||
code={snippetPurpose}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Purpose />} code={purposeSnippet} />
|
||||
|
||||
<Theming definition={AvatarDefinition} />
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const avatarPropDefs: Record<string, PropDef> = {
|
||||
src: {
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['x-small', 'small', 'medium', 'large', 'x-large'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
purpose: {
|
||||
type: 'enum',
|
||||
values: ['informative', 'decoration'],
|
||||
default: 'informative',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
export const avatarUsageSnippet = `import { Avatar } from '@backstage/ui';
|
||||
|
||||
<Avatar />`;
|
||||
|
||||
export const defaultSnippet = `<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/1540635?v=4"
|
||||
name="Charles de Dreuille"
|
||||
/>`;
|
||||
|
||||
export const fallbackSnippet = `<Avatar
|
||||
src="https://avatars.githubusercontent.com/u/15406AAAAAAAAA"
|
||||
name="Charles de Dreuille"
|
||||
/>`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="column" gap="6">
|
||||
<Flex>
|
||||
<Avatar src="..." name="Charles de Dreuille" size="x-small" />
|
||||
<Avatar src="..." name="Charles de Dreuille" size="small" />
|
||||
<Avatar src="..." name="Charles de Dreuille" size="medium" />
|
||||
<Avatar src="..." name="Charles de Dreuille" size="large" />
|
||||
<Avatar src="..." name="Charles de Dreuille" size="x-large" />
|
||||
</Flex>
|
||||
<Flex>
|
||||
<Avatar name="Charles de Dreuille" size="x-small" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="small" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="medium" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="large" src="" />
|
||||
<Avatar name="Charles de Dreuille" size="x-large" src="" />
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
|
||||
export const purposeSnippet = `<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="1">
|
||||
<Text variant="title-x-small">Informative (default)</Text>
|
||||
<Text variant="body-medium">
|
||||
Use when avatar appears alone. Announced as "Charles de Dreuille" to screen readers:
|
||||
</Text>
|
||||
<Flex gap="2" align="center">
|
||||
<Avatar src="..." name="Charles de Dreuille" purpose="informative" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="1">
|
||||
<Text variant="title-x-small">Decoration</Text>
|
||||
<Text variant="body-medium">
|
||||
Use when avatar appears with adjacent text. Hidden from screen readers:
|
||||
</Text>
|
||||
<Flex gap="2" align="center">
|
||||
<Avatar src="..." name="Charles de Dreuille" purpose="decoration" />
|
||||
<Text>Charles de Dreuille</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
|
||||
|
||||
const diagonalStripePattern = (() => {
|
||||
const svg = `
|
||||
<svg width="6" height="6" viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2563eb" fill-opacity="0.6" fill-rule="evenodd">
|
||||
<path d="M5 0h1L0 6V5zM6 5v1H5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
||||
})();
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Box
|
||||
width="64px"
|
||||
height="64px"
|
||||
style={{
|
||||
background: '#eaf2fd',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #2563eb',
|
||||
backgroundImage: `url("${diagonalStripePattern}")`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold',
|
||||
color: '#2563eb',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,18 +1,18 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { BoxSnippet } from '@/snippets/stories-snippets';
|
||||
import { boxPropDefs } from './props-definition';
|
||||
import {
|
||||
boxPropDefs,
|
||||
snippetUsage,
|
||||
boxPreviewSnippet,
|
||||
defaultSnippet,
|
||||
boxSimpleSnippet,
|
||||
boxResponsiveSnippet,
|
||||
} from './box.props';
|
||||
} from './snippets';
|
||||
import { Default } from './components';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { BoxDefinition } from '../utils/definitions';
|
||||
import { BoxDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -21,12 +21,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="Box is the lowest-level component in Backstage UI. It provides a consistent API for styling and layout."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<BoxSnippet story="Default" />}
|
||||
code={boxPreviewSnippet}
|
||||
align="center"
|
||||
/>
|
||||
<Snippet py={4} preview={<Default />} code={defaultSnippet} align="center" />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -22,19 +22,3 @@ export const boxPropDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const snippetUsage = `import { Box } from '@backstage/ui';
|
||||
|
||||
<Box />`;
|
||||
|
||||
export const boxPreviewSnippet = `<Box>
|
||||
<DecorativeBox />
|
||||
</Box>`;
|
||||
|
||||
export const boxSimpleSnippet = `<Box padding="md" borderRadius="md">Hello World</Box>`;
|
||||
|
||||
export const boxResponsiveSnippet = `<Box
|
||||
padding={{ xs: 'sm', md: 'md' }}
|
||||
borderRadius={{ xs: 'sm', md: 'md' }}>
|
||||
Hello World
|
||||
</Box>`;
|
||||
@@ -0,0 +1,21 @@
|
||||
export const snippetUsage = `import { Box } from '@backstage/ui';
|
||||
|
||||
<Box />`;
|
||||
|
||||
export const defaultSnippet = `<Box
|
||||
width="64px"
|
||||
height="64px"
|
||||
style={{
|
||||
background: '#eaf2fd',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #2563eb',
|
||||
}}
|
||||
/>`;
|
||||
|
||||
export const boxSimpleSnippet = `<Box padding="md" borderRadius="md">Hello World</Box>`;
|
||||
|
||||
export const boxResponsiveSnippet = `<Box
|
||||
padding={{ xs: 'sm', md: 'md' }}
|
||||
borderRadius={{ xs: 'sm', md: 'md' }}>
|
||||
Hello World
|
||||
</Box>`;
|
||||
@@ -0,0 +1,68 @@
|
||||
'use client';
|
||||
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
export const Variants = () => {
|
||||
return (
|
||||
<Flex align="center" gap="2">
|
||||
<ButtonIcon icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
|
||||
<ButtonIcon
|
||||
icon={<RiCloudLine />}
|
||||
variant="secondary"
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
<ButtonIcon
|
||||
icon={<RiCloudLine />}
|
||||
variant="tertiary"
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex align="center" gap="2">
|
||||
<ButtonIcon icon={<RiCloudLine />} size="small" aria-label="Cloud" />
|
||||
<ButtonIcon icon={<RiCloudLine />} size="medium" aria-label="Cloud" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return (
|
||||
<Flex direction="row" gap="2">
|
||||
<ButtonIcon
|
||||
isDisabled
|
||||
icon={<RiCloudLine />}
|
||||
variant="primary"
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
<ButtonIcon
|
||||
isDisabled
|
||||
icon={<RiCloudLine />}
|
||||
variant="secondary"
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
<ButtonIcon
|
||||
isDisabled
|
||||
icon={<RiCloudLine />}
|
||||
variant="tertiary"
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
return (
|
||||
<ButtonIcon
|
||||
icon={<RiCloudLine />}
|
||||
variant="primary"
|
||||
loading
|
||||
aria-label="Cloud"
|
||||
/>
|
||||
);
|
||||
};
|
||||
+16
-36
@@ -1,21 +1,18 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ButtonIconSnippet } from '@/snippets/stories-snippets';
|
||||
import { buttonIconPropDefs } from './props-definition';
|
||||
import {
|
||||
buttonIconPropDefs,
|
||||
buttonIconUsageSnippet,
|
||||
buttonIconDefaultSnippet,
|
||||
buttonIconVariantsSnippet,
|
||||
buttonIconSizesSnippet,
|
||||
buttonIconDisabledSnippet,
|
||||
buttonIconLoadingSnippet,
|
||||
buttonIconResponsiveSnippet,
|
||||
buttonIconAsLinkSnippet,
|
||||
} from './button-icon.props';
|
||||
variantsSnippet,
|
||||
sizesSnippet,
|
||||
disabledSnippet,
|
||||
loadingSnippet,
|
||||
} from './snippets';
|
||||
import { Variants, Sizes, Disabled, Loading } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonIconDefinition } from '../utils/definitions';
|
||||
import { ButtonIconDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -23,12 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A button component with a single icon that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<ButtonIconSnippet story="Variants" />}
|
||||
code={buttonIconDefaultSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Variants />} code={variantsSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -48,21 +40,15 @@ Here's a view when buttons have different variants.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Variants" />}
|
||||
code={buttonIconVariantsSnippet}
|
||||
preview={<Variants />}
|
||||
code={variantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Sizes" />}
|
||||
code={buttonIconSizesSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### Disabled
|
||||
|
||||
@@ -72,8 +58,8 @@ Here's a view when buttons are disabled.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Disabled" />}
|
||||
code={buttonIconDisabledSnippet}
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
### Loading
|
||||
@@ -84,16 +70,10 @@ Here's a view when buttons are in a loading state.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonIconSnippet story="Loading" />}
|
||||
code={buttonIconLoadingSnippet}
|
||||
preview={<Loading />}
|
||||
code={loadingSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
|
||||
Here's a view when buttons are responsive.
|
||||
|
||||
<CodeBlock code={buttonIconResponsiveSnippet} />
|
||||
|
||||
<Theming definition={ButtonIconDefinition} />
|
||||
|
||||
<ChangelogComponent component="button-icon" />
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const buttonIconPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
icon: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
loading: { type: 'boolean', default: 'false', responsive: false },
|
||||
type: {
|
||||
type: 'enum',
|
||||
values: ['button', 'submit', 'reset'],
|
||||
default: 'button',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export const buttonIconUsageSnippet = `import { ButtonIcon } from '@backstage/ui';
|
||||
|
||||
<ButtonIcon />`;
|
||||
|
||||
export const variantsSnippet = `<Flex align="center" gap="2">
|
||||
<ButtonIcon icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
|
||||
<ButtonIcon icon={<RiCloudLine />} variant="secondary" aria-label="Cloud" />
|
||||
<ButtonIcon icon={<RiCloudLine />} variant="tertiary" aria-label="Cloud" />
|
||||
</Flex>`;
|
||||
|
||||
export const sizesSnippet = `<Flex align="center" gap="2">
|
||||
<ButtonIcon icon={<RiCloudLine />} size="small" aria-label="Cloud" />
|
||||
<ButtonIcon icon={<RiCloudLine />} size="medium" aria-label="Cloud" />
|
||||
</Flex>`;
|
||||
|
||||
export const disabledSnippet = `<Flex direction="row" gap="2">
|
||||
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="primary" aria-label="Cloud" />
|
||||
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="secondary" aria-label="Cloud" />
|
||||
<ButtonIcon isDisabled icon={<RiCloudLine />} variant="tertiary" aria-label="Cloud" />
|
||||
</Flex>`;
|
||||
|
||||
export const loadingSnippet = `<ButtonIcon icon={<RiCloudLine />} variant="primary" loading aria-label="Cloud" />`;
|
||||
@@ -0,0 +1,122 @@
|
||||
'use client';
|
||||
|
||||
import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react';
|
||||
|
||||
export const Variants = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex align="center">
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="primary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="secondary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="tertiary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex align="center">
|
||||
<ButtonLink size="small" href="https://ui.backstage.io" target="_blank">
|
||||
Small
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
size="medium"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Medium
|
||||
</ButtonLink>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithIcons = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex align="center">
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconEnd={<RiArrowRightSLine />}
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
iconEnd={<RiArrowRightSLine />}
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex gap="4">
|
||||
<ButtonLink
|
||||
variant="primary"
|
||||
isDisabled
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Primary
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
variant="secondary"
|
||||
isDisabled
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Secondary
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
variant="tertiary"
|
||||
isDisabled
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Tertiary
|
||||
</ButtonLink>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { buttonLinkPropDefs } from './props-definition';
|
||||
import {
|
||||
buttonLinkUsageSnippet,
|
||||
variantsSnippet,
|
||||
sizesSnippet,
|
||||
withIconsSnippet,
|
||||
disabledSnippet,
|
||||
} from './snippets';
|
||||
import { Variants, Sizes, WithIcons, Disabled } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonLinkDefinition } from '../../../utils/definitions';
|
||||
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={variantsSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={buttonLinkUsageSnippet} />
|
||||
|
||||
## 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={variantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### With Icons
|
||||
|
||||
Here's a view when buttons have icons.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithIcons />}
|
||||
code={withIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
|
||||
Here's a view when buttons are disabled.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={ButtonLinkDefinition} />
|
||||
|
||||
<ChangelogComponent component="button-link" />
|
||||
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
childrenPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const buttonLinkPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
href: { type: 'string', required: true },
|
||||
target: {
|
||||
type: 'enum',
|
||||
values: ['_self', '_blank', '_parent', '_top'],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
export const buttonLinkUsageSnippet = `import { ButtonLink } from '@backstage/ui';
|
||||
|
||||
<ButtonLink href="https://ui.backstage.io">Button</ButtonLink>`;
|
||||
|
||||
export const variantsSnippet = `<Flex align="center">
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="primary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="secondary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="tertiary"
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const sizesSnippet = `<Flex align="center">
|
||||
<ButtonLink size="small" href="https://ui.backstage.io" target="_blank">
|
||||
Small
|
||||
</ButtonLink>
|
||||
<ButtonLink size="medium" href="https://ui.backstage.io" target="_blank">
|
||||
Medium
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const withIconsSnippet = `<Flex align="center">
|
||||
<ButtonLink iconStart={<RiCloudLine />} href="https://ui.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink iconEnd={<RiArrowRightSLine />} href="https://ui.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
iconStart={<RiCloudLine />}
|
||||
iconEnd={<RiArrowRightSLine />}
|
||||
href="https://ui.backstage.io"
|
||||
target="_blank"
|
||||
>
|
||||
Button
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
|
||||
export const disabledSnippet = `<Flex gap="4">
|
||||
<ButtonLink variant="primary" isDisabled href="https://ui.backstage.io" target="_blank">
|
||||
Primary
|
||||
</ButtonLink>
|
||||
<ButtonLink variant="secondary" isDisabled href="https://ui.backstage.io" target="_blank">
|
||||
Secondary
|
||||
</ButtonLink>
|
||||
<ButtonLink variant="tertiary" isDisabled href="https://ui.backstage.io" target="_blank">
|
||||
Tertiary
|
||||
</ButtonLink>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { ButtonLink } from '../../../../../packages/ui/src/components/ButtonLink/ButtonLink';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiArrowRightSLine, RiCloudLine } from '@remixicon/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
export const Variants = () => {
|
||||
return (
|
||||
<Flex>
|
||||
<Button variant="primary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="secondary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="tertiary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex align="center">
|
||||
<Button size="small">Small</Button>
|
||||
<Button size="medium">Medium</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithIcons = () => {
|
||||
return (
|
||||
<Flex align="center">
|
||||
<Button iconStart={<RiCloudLine />}>Button</Button>
|
||||
<Button iconEnd={<RiArrowRightSLine />}>Button</Button>
|
||||
<Button iconStart={<RiCloudLine />} iconEnd={<RiArrowRightSLine />}>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return (
|
||||
<Flex gap="4">
|
||||
<Button variant="primary" isDisabled>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary" isDisabled>
|
||||
Tertiary
|
||||
</Button>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const Loading = () => {
|
||||
return (
|
||||
<Button variant="primary" loading={true}>
|
||||
Load more items
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const AsLink = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<ButtonLink href="https://ui.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@@ -1,34 +1,36 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ButtonSnippet, ButtonLinkSnippet } from '@/snippets/stories-snippets';
|
||||
import { buttonPropDefs } from './props-definition';
|
||||
import {
|
||||
buttonPropDefs,
|
||||
variantsSnippet,
|
||||
sizesSnippet,
|
||||
withIconsSnippet,
|
||||
disabledSnippet,
|
||||
loadingSnippet,
|
||||
asLinkSnippet,
|
||||
buttonSnippetUsage,
|
||||
buttonVariantsSnippet,
|
||||
buttonSizesSnippet,
|
||||
buttonIconsSnippet,
|
||||
buttonDisabledSnippet,
|
||||
buttonLoadingSnippet,
|
||||
buttonResponsiveSnippet,
|
||||
buttonAsLinkSnippet,
|
||||
} from './button.props';
|
||||
} from './snippets';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ButtonDefinition } from '../utils/definitions';
|
||||
import { ButtonDefinition } from '../../../utils/definitions';
|
||||
import {
|
||||
Variants,
|
||||
Sizes,
|
||||
WithIcons,
|
||||
Disabled,
|
||||
Loading,
|
||||
AsLink,
|
||||
} from './components';
|
||||
|
||||
<PageTitle
|
||||
title="Button"
|
||||
description="A button component that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<ButtonSnippet story="Variants" />}
|
||||
code={buttonVariantsSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Variants />} code={variantsSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -48,21 +50,15 @@ Here's a view when buttons have different variants.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Variants" />}
|
||||
code={buttonVariantsSnippet}
|
||||
preview={<Variants />}
|
||||
code={variantsSnippet}
|
||||
/>
|
||||
|
||||
### Sizes
|
||||
|
||||
Here's a view when buttons have different sizes.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Sizes" />}
|
||||
code={buttonSizesSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### With Icons
|
||||
|
||||
@@ -72,8 +68,8 @@ Here's a view when buttons have icons.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="WithIcons" />}
|
||||
code={buttonIconsSnippet}
|
||||
preview={<WithIcons />}
|
||||
code={withIconsSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
@@ -84,8 +80,8 @@ Here's a view when buttons are disabled.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Disabled" />}
|
||||
code={buttonDisabledSnippet}
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
### Loading
|
||||
@@ -96,8 +92,8 @@ Here's a view when buttons are in a loading state.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonSnippet story="Loading" />}
|
||||
code={buttonLoadingSnippet}
|
||||
preview={<Loading />}
|
||||
code={loadingSnippet}
|
||||
/>
|
||||
|
||||
### Responsive
|
||||
@@ -110,13 +106,7 @@ Here's a view when buttons are responsive.
|
||||
|
||||
If you want to use a button as a link, please use the `ButtonLink` component.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ButtonLinkSnippet story="Variants" />}
|
||||
code={buttonAsLinkSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<AsLink />} code={asLinkSnippet} />
|
||||
|
||||
<Theming definition={ButtonDefinition} />
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const buttonPropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary'],
|
||||
default: 'primary',
|
||||
responsive: true,
|
||||
},
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'medium',
|
||||
responsive: true,
|
||||
},
|
||||
iconStart: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
iconEnd: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isDisabled: { type: 'boolean', default: 'false', responsive: false },
|
||||
loading: { type: 'boolean', default: 'false', responsive: false },
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
type: {
|
||||
type: 'enum',
|
||||
values: ['button', 'submit', 'reset'],
|
||||
default: 'button',
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
export const buttonSnippetUsage = `import { Button } from '@backstage/ui';
|
||||
|
||||
<Button />`;
|
||||
|
||||
export const buttonResponsiveSnippet = `<Button variant={{ initial: 'primary', lg: 'secondary' }}>
|
||||
Responsive Button
|
||||
</Button>`;
|
||||
|
||||
export const variantsSnippet = `<Flex>
|
||||
<Button variant="primary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="secondary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
<Button variant="tertiary" iconStart={<RiCloudLine />}>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const sizesSnippet = `<Flex align="center">
|
||||
<Button size="small">Small</Button>
|
||||
<Button size="medium">Medium</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const withIconsSnippet = `<Flex align="center">
|
||||
<Button iconStart={<RiCloudLine />}>Button</Button>
|
||||
<Button iconEnd={<RiArrowRightSLine />}>Button</Button>
|
||||
<Button iconStart={<RiCloudLine />} iconEnd={<RiArrowRightSLine />}>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const disabledSnippet = `<Flex gap="4">
|
||||
<Button variant="primary" isDisabled>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary" isDisabled>
|
||||
Tertiary
|
||||
</Button>
|
||||
</Flex>`;
|
||||
|
||||
export const loadingSnippet = `<Button variant="primary" loading={true}>
|
||||
Load more items
|
||||
</Button>`;
|
||||
|
||||
export const asLinkSnippet = `<MemoryRouter>
|
||||
<ButtonLink href="https://ui.backstage.io" target="_blank">
|
||||
Button
|
||||
</ButtonLink>
|
||||
</MemoryRouter>`;
|
||||
@@ -0,0 +1,66 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
CardFooter,
|
||||
} from '../../../../../packages/ui/src/components/Card/Card';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Card style={{ width: '300px', height: '200px' }}>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomSize = () => {
|
||||
return (
|
||||
<Card
|
||||
style={{
|
||||
width: '300px',
|
||||
height: '200px',
|
||||
}}
|
||||
>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithLongBody = () => {
|
||||
return (
|
||||
<Card style={{ width: '300px', height: '200px' }}>
|
||||
<CardHeader>
|
||||
<Text>Header</Text>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Text>
|
||||
This is the first paragraph of a long body text that demonstrates how
|
||||
the Card component handles extensive content. The card should adjust
|
||||
accordingly to display all the text properly while maintaining its
|
||||
structure.
|
||||
</Text>
|
||||
<Text>
|
||||
Here'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>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,22 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CardSnippet } from '@/snippets/stories-snippets';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
cardPropDefs,
|
||||
cardUsageSnippet,
|
||||
cardDefaultSnippet,
|
||||
cardHeaderPropDefs,
|
||||
cardBodyPropDefs,
|
||||
cardFooterPropDefs,
|
||||
cardLongBodySnippet,
|
||||
cardListRowSnippet,
|
||||
} from './card.props';
|
||||
} from './props-definition';
|
||||
import {
|
||||
cardUsageSnippet,
|
||||
defaultSnippet,
|
||||
customSizeSnippet,
|
||||
withLongBodySnippet,
|
||||
} from './snippets';
|
||||
import { Default, CustomSize, WithLongBody } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { CardDefinition } from '../utils/definitions';
|
||||
import { CardDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -22,12 +24,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A card component that can be used to display content in a box."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CardSnippet story="CustomSize" />}
|
||||
code={cardDefaultSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -61,6 +58,18 @@ To display a footer in a card, use the `CardFooter` component. This will be fixe
|
||||
|
||||
## Examples
|
||||
|
||||
### Custom size
|
||||
|
||||
Here's a view when card has a custom size.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CustomSize />}
|
||||
code={customSizeSnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### With long body
|
||||
|
||||
Here's a view when card has a long body.
|
||||
@@ -68,20 +77,8 @@ Here's a view when card has a long body.
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CardSnippet story="WithLongBody" />}
|
||||
code={cardLongBodySnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### With list
|
||||
|
||||
Here's a view when card has a list.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CardSnippet story="WithListRow" />}
|
||||
code={cardListRowSnippet}
|
||||
preview={<WithLongBody />}
|
||||
code={withLongBodySnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
childrenPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const cardPropDefs: Record<string, PropDef> = {
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const cardHeaderPropDefs: Record<string, PropDef> = {
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const cardBodyPropDefs: Record<string, PropDef> = {
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const cardFooterPropDefs: Record<string, PropDef> = {
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
export const cardUsageSnippet = `import { Card, CardHeader, CardBody, CardFooter } from '@backstage/ui';
|
||||
|
||||
<Card>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>`;
|
||||
|
||||
export const defaultSnippet = `<Card>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>`;
|
||||
|
||||
export const customSizeSnippet = `<Card style={{ width: '300px', height: '200px' }}>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>`;
|
||||
|
||||
export const withLongBodySnippet = `<Card style={{ width: '300px', height: '200px' }}>
|
||||
<CardHeader>
|
||||
<Text>Header</Text>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Text>
|
||||
This is the first paragraph of a long body text that demonstrates how
|
||||
the Card component handles extensive content.
|
||||
</Text>
|
||||
<Text>
|
||||
Here's a second paragraph that adds more content to our card body.
|
||||
</Text>
|
||||
<Text>
|
||||
This third paragraph continues to add more text to ensure we have a
|
||||
proper demonstration of a card with significant content.
|
||||
</Text>
|
||||
</CardBody>
|
||||
<CardFooter>
|
||||
<Text>Footer</Text>
|
||||
</CardFooter>
|
||||
</Card>`;
|
||||
@@ -0,0 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { Checkbox } from '../../../../../packages/ui/src/components/Checkbox/Checkbox';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
|
||||
export const Default = () => {
|
||||
return <Checkbox>Accept terms and conditions</Checkbox>;
|
||||
};
|
||||
|
||||
export const AllVariants = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
<Checkbox>Unchecked</Checkbox>
|
||||
<Checkbox isSelected>Checked</Checkbox>
|
||||
<Checkbox isDisabled>Disabled</Checkbox>
|
||||
<Checkbox isSelected isDisabled>
|
||||
Checked & Disabled
|
||||
</Checkbox>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,16 +1,16 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CheckboxSnippet } from '@/snippets/stories-snippets';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { checkboxPropDefs } from './props-definition';
|
||||
import {
|
||||
checkboxPropDefs,
|
||||
checkboxUsageSnippet,
|
||||
checkboxDefaultSnippet,
|
||||
checkboxVariantsSnippet,
|
||||
} from './checkbox.props';
|
||||
defaultSnippet,
|
||||
allVariantsSnippet,
|
||||
} from './snippets';
|
||||
import { Default, AllVariants } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { CheckboxDefinition } from '../utils/definitions';
|
||||
import { CheckboxDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -18,12 +18,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A checkbox component that can be used to trigger actions."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<CheckboxSnippet story="Default" />}
|
||||
code={checkboxDefaultSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -43,8 +38,8 @@ Here's a view when checkboxes have different variants.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<CheckboxSnippet story="AllVariants" />}
|
||||
code={checkboxVariantsSnippet}
|
||||
preview={<AllVariants />}
|
||||
code={allVariantsSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={CheckboxDefinition} />
|
||||
-13
@@ -46,16 +46,3 @@ export const checkboxPropDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui';
|
||||
|
||||
<Checkbox>Accept terms</Checkbox>`;
|
||||
|
||||
export const checkboxDefaultSnippet = `<Checkbox>Accept terms and conditions</Checkbox>`;
|
||||
|
||||
export const checkboxVariantsSnippet = `<Flex direction="column" gap="2">
|
||||
<Checkbox>Unchecked</Checkbox>
|
||||
<Checkbox isSelected>Checked</Checkbox>
|
||||
<Checkbox isDisabled>Disabled</Checkbox>
|
||||
<Checkbox isSelected isDisabled>Checked & Disabled</Checkbox>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,12 @@
|
||||
export const checkboxUsageSnippet = `import { Checkbox } from '@backstage/ui';
|
||||
|
||||
<Checkbox>Accept terms</Checkbox>`;
|
||||
|
||||
export const defaultSnippet = `<Checkbox>Accept terms and conditions</Checkbox>`;
|
||||
|
||||
export const allVariantsSnippet = `<Flex direction="column" gap="2">
|
||||
<Checkbox>Unchecked</Checkbox>
|
||||
<Checkbox isSelected>Checked</Checkbox>
|
||||
<Checkbox isDisabled>Disabled</Checkbox>
|
||||
<Checkbox isSelected isDisabled>Checked & Disabled</Checkbox>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
|
||||
|
||||
const DecorativeBox = () => (
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
borderRadius: '4px',
|
||||
border: '1px solid #2563eb',
|
||||
backgroundImage:
|
||||
'url("data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E")',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Preview = () => {
|
||||
return (
|
||||
<div style={{ maxWidth: '600px', margin: '0 auto' }}>
|
||||
<DecorativeBox />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,17 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { ContainerSnippet } from '@/snippets/stories-snippets';
|
||||
import { containerPropDefs } from './props-definition';
|
||||
import {
|
||||
containerPropDefs,
|
||||
containerUsageSnippet,
|
||||
containerDefaultSnippet,
|
||||
previewSnippet,
|
||||
containerSimpleSnippet,
|
||||
containerResponsiveSnippet,
|
||||
} from './container.props';
|
||||
} from './snippets';
|
||||
import { Preview } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ContainerDefinition } from '../utils/definitions';
|
||||
import { ContainerDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -20,11 +20,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="The container component let you use our default max-width and center the content on the page."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<ContainerSnippet story="Preview" />}
|
||||
code={containerDefaultSnippet}
|
||||
/>
|
||||
<Snippet py={4} preview={<Preview />} code={previewSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
gapPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const containerPropDefs: Record<string, PropDef> = {
|
||||
...gapPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
+2
-15
@@ -1,23 +1,10 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
gapPropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const containerPropDefs: Record<string, PropDef> = {
|
||||
...gapPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const containerUsageSnippet = `import { Container } from "@backstage/ui";
|
||||
|
||||
<Container>Hello World!</Container>`;
|
||||
|
||||
export const containerDefaultSnippet = `<Container>
|
||||
export const previewSnippet = `<div style={{ maxWidth: '600px', margin: '0 auto' }}>
|
||||
<DecorativeBox />
|
||||
</Container>`;
|
||||
</div>`;
|
||||
|
||||
export const containerSimpleSnippet = `<Container>
|
||||
<Box>Hello World</Box>
|
||||
@@ -0,0 +1,104 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogHeader,
|
||||
DialogBody,
|
||||
DialogFooter,
|
||||
} from '../../../../../packages/ui/src/components/Dialog/Dialog';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
import { TextField } from '../../../../../packages/ui/src/components/TextField/TextField';
|
||||
import { Select } from '../../../../../packages/ui/src/components/Select/Select';
|
||||
|
||||
export const Default = () => (
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">Open Dialog</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Example Dialog</DialogHeader>
|
||||
<DialogBody>
|
||||
<Text>This is a basic dialog example.</Text>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">
|
||||
Close
|
||||
</Button>
|
||||
<Button variant="primary" slot="close">
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>
|
||||
);
|
||||
|
||||
export const PreviewFixedWidthAndHeight = () => (
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">Scrollable Dialog</Button>
|
||||
<Dialog width={600} height={400}>
|
||||
<DialogHeader>Long Content Dialog</DialogHeader>
|
||||
<DialogBody>
|
||||
<Flex direction="column" gap="3">
|
||||
<Text>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
|
||||
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
|
||||
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
||||
aliquip ex ea commodo consequat.
|
||||
</Text>
|
||||
<Text>
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse
|
||||
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
|
||||
cupidatat non proident, sunt in culpa qui officia deserunt mollit
|
||||
anim id est laborum.
|
||||
</Text>
|
||||
<Text>
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem
|
||||
accusantium doloremque laudantium, totam rem aperiam, eaque ipsa
|
||||
quae ab illo inventore veritatis et quasi architecto beatae vitae
|
||||
dicta sunt explicabo.
|
||||
</Text>
|
||||
</Flex>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" slot="close">
|
||||
Accept
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>
|
||||
);
|
||||
|
||||
export const PreviewWithForm = () => (
|
||||
<DialogTrigger>
|
||||
<Button variant="secondary">Create User</Button>
|
||||
<Dialog>
|
||||
<DialogHeader>Create New User</DialogHeader>
|
||||
<DialogBody>
|
||||
<Flex direction="column" gap="3">
|
||||
<TextField label="Name" placeholder="Enter full name" />
|
||||
<TextField label="Email" placeholder="Enter email address" />
|
||||
<Select
|
||||
label="Role"
|
||||
options={[
|
||||
{ value: 'admin', label: 'Admin' },
|
||||
{ value: 'user', label: 'User' },
|
||||
{ value: 'viewer', label: 'Viewer' },
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
<Button variant="secondary" slot="close">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="primary" slot="close">
|
||||
Create User
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
</DialogTrigger>
|
||||
);
|
||||
@@ -1,7 +1,11 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { DialogSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
Default,
|
||||
PreviewFixedWidthAndHeight,
|
||||
PreviewWithForm,
|
||||
} from './components';
|
||||
import {
|
||||
dialogPropDefs,
|
||||
dialogTriggerPropDefs,
|
||||
@@ -9,16 +13,18 @@ import {
|
||||
dialogBodyPropDefs,
|
||||
dialogFooterPropDefs,
|
||||
dialogClosePropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
dialogUsageSnippet,
|
||||
dialogDefaultSnippet,
|
||||
dialogFixedWidthAndHeightSnippet,
|
||||
dialogWithFormSnippet,
|
||||
dialogWithNoTriggerSnippet,
|
||||
dialogCloseSnippet,
|
||||
} from './dialog.props';
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { DialogDefinition } from '../utils/definitions';
|
||||
import { DialogDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -29,7 +35,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<DialogSnippet story="Default" />}
|
||||
preview={<Default />}
|
||||
code={dialogDefaultSnippet}
|
||||
/>
|
||||
|
||||
@@ -82,7 +88,7 @@ Dialog with a fixed height body that scrolls when content overflows.
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<DialogSnippet story="PreviewFixedWidthAndHeight" />}
|
||||
preview={<PreviewFixedWidthAndHeight />}
|
||||
code={dialogFixedWidthAndHeightSnippet}
|
||||
/>
|
||||
|
||||
@@ -93,7 +99,7 @@ Dialog containing form elements for user input.
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<DialogSnippet story="PreviewWithForm" />}
|
||||
preview={<PreviewWithForm />}
|
||||
code={dialogWithFormSnippet}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const dialogTriggerPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
};
|
||||
|
||||
export const dialogPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
width: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogHeaderPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogBodyPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogFooterPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogClosePropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary'],
|
||||
default: 'secondary',
|
||||
responsive: false,
|
||||
},
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
+2
-91
@@ -1,92 +1,3 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const dialogTriggerPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
};
|
||||
|
||||
export const dialogPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (controlled).',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the overlay is open by default (uncontrolled).',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
description:
|
||||
"Handler that is called when the overlay's open state changes.",
|
||||
},
|
||||
width: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogHeaderPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogBodyPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
height: {
|
||||
type: 'enum',
|
||||
values: ['number', 'string'],
|
||||
responsive: false,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogFooterPropDefs: Record<string, PropDef> = {
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogClosePropDefs: Record<string, PropDef> = {
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary'],
|
||||
default: 'secondary',
|
||||
responsive: false,
|
||||
},
|
||||
children: { type: 'enum', values: ['ReactNode'], responsive: false },
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const dialogUsageSnippet = `import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
@@ -122,9 +33,9 @@ export const dialogDefaultSnippet = `<DialogTrigger>
|
||||
|
||||
export const dialogFixedWidthAndHeightSnippet = `<DialogTrigger>
|
||||
<Button variant="secondary">Scrollable Dialog</Button>
|
||||
<Dialog>
|
||||
<Dialog width={600} height={400}>
|
||||
<DialogHeader>Long Content Dialog</DialogHeader>
|
||||
<DialogBody width={600} height={400}>
|
||||
<DialogBody>
|
||||
...
|
||||
</DialogBody>
|
||||
<DialogFooter>
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Flex gap="4">
|
||||
<Box
|
||||
style={{
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,20 +1,19 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { FlexSnippet } from '@/snippets/stories-snippets';
|
||||
import { flexPropDefs } from './props-definition';
|
||||
import {
|
||||
flexPropDefs,
|
||||
flexUsageSnippet,
|
||||
flexDefaultSnippet,
|
||||
flexFAQ1Snippet,
|
||||
defaultSnippet,
|
||||
flexSimpleSnippet,
|
||||
flexResponsiveSnippet,
|
||||
flexAlignSnippet,
|
||||
} from './flex.props';
|
||||
} from './snippets';
|
||||
import { Default } from './components';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { FlexDefinition } from '../utils/definitions';
|
||||
import { FlexDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -23,12 +22,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A responsive flex container component for vertical stacking with customizable gaps."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
align="center"
|
||||
preview={<FlexSnippet story="Default" />}
|
||||
code={flexDefaultSnippet}
|
||||
/>
|
||||
<Snippet py={4} align="center" preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const flexPropDefs: Record<string, PropDef> = {
|
||||
align: {
|
||||
type: 'enum',
|
||||
values: ['start', 'center', 'end', 'baseline', 'stretch'],
|
||||
responsive: true,
|
||||
},
|
||||
direction: {
|
||||
type: 'enum',
|
||||
values: ['row', 'column', 'row-reverse', 'column-reverse'],
|
||||
responsive: true,
|
||||
},
|
||||
justify: {
|
||||
type: 'enum',
|
||||
values: ['start', 'center', 'end', 'between'],
|
||||
responsive: true,
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
export const flexUsageSnippet = `import { Flex } from '@backstage/ui';
|
||||
|
||||
<Flex />`;
|
||||
|
||||
export const defaultSnippet = `<Flex gap="4">
|
||||
<Box style={{ width: '64px', height: '64px' }} />
|
||||
<Box style={{ width: '64px', height: '64px' }} />
|
||||
<Box style={{ width: '64px', height: '64px' }} />
|
||||
</Flex>`;
|
||||
|
||||
export const flexSimpleSnippet = `<Flex gap="4">
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
</Flex>`;
|
||||
|
||||
export const flexResponsiveSnippet = `<Flex gap={{ initial: '2', md: '4' }}>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
</Flex>`;
|
||||
|
||||
export const flexAlignSnippet = `<Flex align={{ initial: 'start', md: 'center' }}>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
<Box>Hello World</Box>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,59 @@
|
||||
'use client';
|
||||
|
||||
import { Grid } from '../../../../../packages/ui/src/components/Grid/Grid';
|
||||
import { Box } from '../../../../../packages/ui/src/components/Box/Box';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<Grid.Root columns="3" gap="4">
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
style={{
|
||||
height: '64px',
|
||||
background: '#eaf2fd',
|
||||
border: '1px solid #2563eb',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</Grid.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { gridPropDefs } from './props-definition';
|
||||
import { gridUsageSnippet, defaultSnippet } from './snippets';
|
||||
import { Default } from './components';
|
||||
import { spacingPropDefs } from '@/utils/propDefs';
|
||||
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={defaultSnippet} />
|
||||
|
||||
## 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} />
|
||||
|
||||
<Theming definition={GridDefinition} />
|
||||
|
||||
<ChangelogComponent component="grid" />
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
gapPropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const gridPropDefs: Record<string, PropDef> = {
|
||||
columns: {
|
||||
type: 'string',
|
||||
responsive: true,
|
||||
},
|
||||
rows: {
|
||||
type: 'string',
|
||||
responsive: true,
|
||||
},
|
||||
...gapPropDefs,
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
export const gridUsageSnippet = `import { Grid } from '@backstage/ui';
|
||||
|
||||
<Grid />`;
|
||||
|
||||
export const defaultSnippet = `<Grid.Root columns="3" gap="4">
|
||||
<Box style={{ height: '64px' }} />
|
||||
<Box style={{ height: '64px' }} />
|
||||
<Box style={{ height: '64px' }} />
|
||||
<Box style={{ height: '64px' }} />
|
||||
<Box style={{ height: '64px' }} />
|
||||
<Box style={{ height: '64px' }} />
|
||||
</Grid.Root>`;
|
||||
@@ -0,0 +1,107 @@
|
||||
'use client';
|
||||
|
||||
import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import {
|
||||
MenuTrigger,
|
||||
Menu,
|
||||
MenuItem,
|
||||
} from '../../../../../packages/ui/src/components/Menu/Menu';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { RiMore2Line } from '@remixicon/react';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
|
||||
];
|
||||
|
||||
const menuItems = [
|
||||
{ label: 'Settings', value: 'settings', href: '/settings' },
|
||||
{
|
||||
label: 'Invite new members',
|
||||
value: 'invite-new-members',
|
||||
href: '/invite-new-members',
|
||||
},
|
||||
{
|
||||
label: 'Logout',
|
||||
value: 'logout',
|
||||
onClick: () => {
|
||||
alert('logout');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const breadcrumbs = [
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
|
||||
{ label: 'Another Long Breadcrumb', href: '/another-long-breadcrumb' },
|
||||
{
|
||||
label: 'Yet Another Long Breadcrumb',
|
||||
href: '/yet-another-long-breadcrumb',
|
||||
},
|
||||
];
|
||||
|
||||
export const WithEverything = () => (
|
||||
<MemoryRouter>
|
||||
<HeaderPage
|
||||
title="Page Title"
|
||||
tabs={tabs}
|
||||
breadcrumbs={breadcrumbs}
|
||||
customActions={
|
||||
<>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="primary">Primary</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithLongBreadcrumbs = () => (
|
||||
<MemoryRouter>
|
||||
<HeaderPage title="Page Title" breadcrumbs={breadcrumbs} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithTabs = () => (
|
||||
<MemoryRouter>
|
||||
<HeaderPage title="Page Title" tabs={tabs} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithCustomActions = () => (
|
||||
<MemoryRouter>
|
||||
<HeaderPage
|
||||
title="Page Title"
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithMenuItems = () => (
|
||||
<MemoryRouter>
|
||||
<HeaderPage
|
||||
title="Page Title"
|
||||
customActions={
|
||||
<MenuTrigger>
|
||||
<ButtonIcon variant="tertiary" icon={<RiMore2Line />} />
|
||||
<Menu placement="bottom end">
|
||||
{menuItems.map(option => (
|
||||
<MenuItem
|
||||
key={option.value}
|
||||
onAction={option.onClick}
|
||||
href={option.href}
|
||||
>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
+15
-30
@@ -1,20 +1,25 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { HeaderPageSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
propDefs,
|
||||
WithEverything,
|
||||
WithLongBreadcrumbs,
|
||||
WithTabs,
|
||||
WithCustomActions,
|
||||
WithMenuItems,
|
||||
} from './components';
|
||||
import { propDefs } from './props-definition';
|
||||
import {
|
||||
usage,
|
||||
simple,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withCustomActions,
|
||||
withMenuItems,
|
||||
} from './header-page.props';
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { HeaderPageDefinition } from '../utils/definitions';
|
||||
import { HeaderPageDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -23,11 +28,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A header page component for the plugin that should sit under the Header component."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<HeaderPageSnippet story="WithEverything" />}
|
||||
code={defaultSnippet}
|
||||
/>
|
||||
<Snippet py={4} preview={<WithEverything />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -44,44 +45,28 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
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={<HeaderPageSnippet story="WithLongBreadcrumbs" />}
|
||||
code={withBreadcrumbs}
|
||||
/>
|
||||
<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={<HeaderPageSnippet story="WithTabs" />}
|
||||
code={withTabs}
|
||||
/>
|
||||
<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={<HeaderPageSnippet story="WithCustomActions" />}
|
||||
code={withCustomActions}
|
||||
/>
|
||||
<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={<HeaderPageSnippet story="WithMenuItems" />}
|
||||
code={withMenuItems}
|
||||
/>
|
||||
<Snippet open preview={<WithMenuItems />} code={withMenuItems} />
|
||||
|
||||
<Theming definition={HeaderPageDefinition} />
|
||||
|
||||
-52
@@ -87,55 +87,3 @@ export const propDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const usage = `import { HeaderPage } from '@backstage/ui';
|
||||
|
||||
<HeaderPage />`;
|
||||
|
||||
export const defaultSnippet = `<HeaderPage
|
||||
title="Page Title"
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'checks', label: 'Checks' },
|
||||
{ id: 'tracks', label: 'Tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations' },
|
||||
]}
|
||||
menuItems={[
|
||||
{ label: 'Settings', value: 'settings' },
|
||||
{ label: 'Invite new members', value: 'invite-new-members' },
|
||||
]}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>`;
|
||||
|
||||
export const withBreadcrumbs = `<HeaderPage
|
||||
title="Page Title"
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withTabs = `<HeaderPage
|
||||
title="Page Title"
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withCustomActions = `<HeaderPage
|
||||
title="Page Title"
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>`;
|
||||
|
||||
export const withMenuItems = `<HeaderPage
|
||||
title="Page Title"
|
||||
menuItems={[
|
||||
{ label: 'Settings', value: 'settings', onClick: () => {} },
|
||||
{ label: 'Invite new members', value: 'invite-new-members', onClick: () => {} },
|
||||
]}
|
||||
/>`;
|
||||
@@ -0,0 +1,51 @@
|
||||
export const usage = `import { HeaderPage } from '@backstage/ui';
|
||||
|
||||
<HeaderPage />`;
|
||||
|
||||
export const defaultSnippet = `<HeaderPage
|
||||
title="Page Title"
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
{ id: 'checks', label: 'Checks' },
|
||||
{ id: 'tracks', label: 'Tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations' },
|
||||
]}
|
||||
menuItems={[
|
||||
{ label: 'Settings', value: 'settings' },
|
||||
{ label: 'Invite new members', value: 'invite-new-members' },
|
||||
]}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>`;
|
||||
|
||||
export const withBreadcrumbs = `<HeaderPage
|
||||
title="Page Title"
|
||||
breadcrumbs={[
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Long Breadcrumb Name', href: '/long-breadcrumb' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withTabs = `<HeaderPage
|
||||
title="Page Title"
|
||||
tabs={[
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
|
||||
]}
|
||||
/>`;
|
||||
|
||||
export const withCustomActions = `<HeaderPage
|
||||
title="Page Title"
|
||||
customActions={<Button>Custom action</Button>}
|
||||
/>`;
|
||||
|
||||
export const withMenuItems = `<HeaderPage
|
||||
title="Page Title"
|
||||
menuItems={[
|
||||
{ label: 'Settings', value: 'settings', onClick: () => {} },
|
||||
{ label: 'Invite new members', value: 'invite-new-members', onClick: () => {} },
|
||||
]}
|
||||
/>`;
|
||||
@@ -0,0 +1,90 @@
|
||||
'use client';
|
||||
|
||||
import { Header } from '../../../../../packages/ui/src/components/Header/Header';
|
||||
import { HeaderPage } from '../../../../../packages/ui/src/components/HeaderPage/HeaderPage';
|
||||
import { ButtonIcon } from '../../../../../packages/ui/src/components/ButtonIcon/ButtonIcon';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
RiHeartLine,
|
||||
RiEmotionHappyLine,
|
||||
RiCloudy2Line,
|
||||
} from '@remixicon/react';
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', label: 'Overview', href: '/overview' },
|
||||
{ id: 'checks', label: 'Checks', href: '/checks' },
|
||||
{ id: 'tracks', label: 'Tracks', href: '/tracks' },
|
||||
{ id: 'campaigns', label: 'Campaigns', href: '/campaigns' },
|
||||
{ id: 'integrations', label: 'Integrations', href: '/integrations' },
|
||||
];
|
||||
|
||||
const tabs2 = [
|
||||
{ id: 'Banana', label: 'Banana', href: '/banana' },
|
||||
{ id: 'Apple', label: 'Apple', href: '/apple' },
|
||||
{ id: 'Orange', label: 'Orange', href: '/orange' },
|
||||
];
|
||||
|
||||
const breadcrumbs = [
|
||||
{ label: 'Home', href: '/' },
|
||||
{ label: 'Dashboard', href: '/dashboard' },
|
||||
{ label: 'Settings', href: '/settings' },
|
||||
];
|
||||
|
||||
export const WithAllOptionsAndTabs = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
tabs={tabs}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithAllOptions = () => (
|
||||
<MemoryRouter>
|
||||
<Header
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithBreadcrumbs = () => (
|
||||
<MemoryRouter>
|
||||
<Header tabs={tabs} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const WithHeaderPage = () => (
|
||||
<MemoryRouter>
|
||||
<>
|
||||
<Header
|
||||
tabs={tabs}
|
||||
customActions={
|
||||
<>
|
||||
<ButtonIcon variant="tertiary" icon={<RiCloudy2Line />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiEmotionHappyLine />} />
|
||||
<ButtonIcon variant="tertiary" icon={<RiHeartLine />} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<HeaderPage
|
||||
title="Page title"
|
||||
tabs={tabs2}
|
||||
customActions={<Button>Custom action</Button>}
|
||||
breadcrumbs={breadcrumbs}
|
||||
/>
|
||||
</>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -1,19 +1,24 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { HeaderSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
propDefs,
|
||||
WithAllOptionsAndTabs,
|
||||
WithAllOptions,
|
||||
WithBreadcrumbs,
|
||||
WithHeaderPage,
|
||||
} from './components';
|
||||
import { propDefs } from './props-definition';
|
||||
import {
|
||||
usage,
|
||||
simple,
|
||||
defaultSnippet,
|
||||
withTabs,
|
||||
withBreadcrumbs,
|
||||
withHeaderPage,
|
||||
} from './header.props';
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { HeaderDefinition } from '../utils/definitions';
|
||||
import { HeaderDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
<PageTitle
|
||||
@@ -22,11 +27,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
description="A header component for the plugin."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<HeaderSnippet story="WithAllOptionsAndTabs" />}
|
||||
code={defaultSnippet}
|
||||
/>
|
||||
<Snippet py={4} preview={<WithAllOptionsAndTabs />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -42,12 +43,7 @@ import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
|
||||
A simple example of how to use the Header component.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<HeaderSnippet story="WithAllOptions" />}
|
||||
code={simple}
|
||||
open
|
||||
/>
|
||||
<Snippet py={4} preview={<WithAllOptions />} code={simple} open />
|
||||
|
||||
### Header with tabs
|
||||
|
||||
@@ -56,7 +52,7 @@ under the hood and will be active when you are on the corresponding page.
|
||||
|
||||
<Snippet
|
||||
|
||||
preview={<HeaderSnippet story="WithAllOptionsAndTabs" />}
|
||||
preview={<WithAllOptionsAndTabs />}
|
||||
code={withTabs}
|
||||
open
|
||||
/>
|
||||
@@ -65,21 +61,13 @@ open
|
||||
|
||||
Breacrumbs should appear when you scroll down (and not directly visible as it is in the demo below).
|
||||
|
||||
<Snippet
|
||||
preview={<HeaderSnippet story="WithBreadcrumbs" />}
|
||||
code={withBreadcrumbs}
|
||||
open
|
||||
/>
|
||||
<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={<HeaderSnippet story="WithHeaderPage" />}
|
||||
code={withHeaderPage}
|
||||
open
|
||||
/>
|
||||
<Snippet preview={<WithHeaderPage />} code={withHeaderPage} open />
|
||||
|
||||
<Theming definition={HeaderDefinition} />
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const propDefs: Record<string, PropDef> = {
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Your plugin',
|
||||
},
|
||||
titleLink: {
|
||||
type: 'string',
|
||||
default: '/',
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
menuItems: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'MenuItem[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the menu item',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the menu item',
|
||||
},
|
||||
onClick: {
|
||||
type: '() => void',
|
||||
required: false,
|
||||
description: 'Callback function when menu item is clicked',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'HeaderTab[]',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique identifier for the tab',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the tab',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL to navigate to when tab is clicked',
|
||||
},
|
||||
matchStrategy: {
|
||||
type: "'exact' | 'prefix'",
|
||||
required: false,
|
||||
description: 'How to match the current route to highlight the tab',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
onTabSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(key: string) => void'],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -1,87 +1,3 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const propDefs: Record<string, PropDef> = {
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
default: 'Your plugin',
|
||||
},
|
||||
titleLink: {
|
||||
type: 'string',
|
||||
default: '/',
|
||||
},
|
||||
customActions: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
},
|
||||
menuItems: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'MenuItem[]',
|
||||
properties: {
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the menu item',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique value for the menu item',
|
||||
},
|
||||
onClick: {
|
||||
type: '() => void',
|
||||
required: false,
|
||||
description: 'Callback function when menu item is clicked',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tabs: {
|
||||
type: 'complex',
|
||||
complexType: {
|
||||
name: 'HeaderTab[]',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Unique identifier for the tab',
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'Display text for the tab',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
description: 'URL to navigate to when tab is clicked',
|
||||
},
|
||||
matchStrategy: {
|
||||
type: "'exact' | 'prefix'",
|
||||
required: false,
|
||||
description: 'How to match the current route to highlight the tab',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
onTabSelectionChange: {
|
||||
type: 'enum',
|
||||
values: ['(key: string) => void'],
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const usage = `import { Header } from '@backstage/ui';
|
||||
|
||||
<Header />`;
|
||||
@@ -0,0 +1,111 @@
|
||||
'use client';
|
||||
|
||||
import { Link } from '../../../../../packages/ui/src/components/Link/Link';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Link href="/">Sign up for Backstage</Link>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExternalLink = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Link href="https://backstage.io" target="_blank">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllVariants = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex gap="4" direction="column">
|
||||
<Link href="https://ui.backstage.io" variant="title-large">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-medium">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-small">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-x-small">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-large">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-medium">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-small">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-x-small">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllColors = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex gap="4" direction="column">
|
||||
<Link
|
||||
href="https://ui.backstage.io"
|
||||
variant="title-small"
|
||||
color="primary"
|
||||
>
|
||||
I am primary
|
||||
</Link>
|
||||
<Link
|
||||
href="https://ui.backstage.io"
|
||||
variant="title-small"
|
||||
color="secondary"
|
||||
>
|
||||
I am secondary
|
||||
</Link>
|
||||
<Link
|
||||
href="https://ui.backstage.io"
|
||||
variant="title-small"
|
||||
color="tertiary"
|
||||
>
|
||||
I am tertiary
|
||||
</Link>
|
||||
<Link
|
||||
href="https://ui.backstage.io"
|
||||
variant="title-small"
|
||||
color="inherit"
|
||||
>
|
||||
I am inherit
|
||||
</Link>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const Underline = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex gap="4" direction="column">
|
||||
<Link href="https://ui.backstage.io" underline="always">
|
||||
Always underlined
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" underline="hover">
|
||||
Underlined on hover
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" underline="none">
|
||||
Never underlined
|
||||
</Link>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { linkPropDefs } from './props-definition';
|
||||
import {
|
||||
linkUsageSnippet,
|
||||
defaultSnippet,
|
||||
externalLinkSnippet,
|
||||
allVariantsSnippet,
|
||||
allColorsSnippet,
|
||||
underlineSnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
Default,
|
||||
ExternalLink,
|
||||
AllVariants,
|
||||
AllColors,
|
||||
Underline,
|
||||
} from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
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={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={linkUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={linkPropDefs} />
|
||||
|
||||
## Examples
|
||||
|
||||
### External Link
|
||||
|
||||
Here's how to create an external link that opens in a new tab.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<ExternalLink />}
|
||||
code={externalLinkSnippet}
|
||||
/>
|
||||
|
||||
### Variants
|
||||
|
||||
Here's a view when links have different variants.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AllVariants />}
|
||||
code={allVariantsSnippet}
|
||||
/>
|
||||
|
||||
### Colors
|
||||
|
||||
Here's a view when links have different colors.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<AllColors />}
|
||||
code={allColorsSnippet}
|
||||
/>
|
||||
|
||||
### Underline
|
||||
|
||||
Here's a view when links have different underline styles.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Underline />}
|
||||
code={underlineSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={LinkDefinition} />
|
||||
|
||||
<ChangelogComponent component="link" />
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const linkPropDefs: Record<string, PropDef> = {
|
||||
href: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
target: {
|
||||
type: 'enum',
|
||||
values: ['_self', '_blank', '_parent', '_top'],
|
||||
},
|
||||
variant: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'title-large',
|
||||
'title-medium',
|
||||
'title-small',
|
||||
'title-x-small',
|
||||
'body-large',
|
||||
'body-medium',
|
||||
'body-small',
|
||||
'body-x-small',
|
||||
],
|
||||
default: 'body-medium',
|
||||
responsive: true,
|
||||
},
|
||||
color: {
|
||||
type: 'enum',
|
||||
values: ['primary', 'secondary', 'tertiary', 'inherit'],
|
||||
default: 'primary',
|
||||
},
|
||||
underline: {
|
||||
type: 'enum',
|
||||
values: ['always', 'hover', 'none'],
|
||||
default: 'hover',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
export const linkUsageSnippet = `import { Link } from '@backstage/ui';
|
||||
|
||||
<Link href="/">Sign up for Backstage</Link>`;
|
||||
|
||||
export const defaultSnippet = `<Link href="/">Sign up for Backstage</Link>`;
|
||||
|
||||
export const externalLinkSnippet = `<Link href="https://backstage.io" target="_blank">
|
||||
Sign up for Backstage
|
||||
</Link>`;
|
||||
|
||||
export const allVariantsSnippet = `<Flex gap="4" direction="column">
|
||||
<Link href="https://ui.backstage.io" variant="title-large">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-medium">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-small">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-large">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="body-medium">
|
||||
Sign up for Backstage
|
||||
</Link>
|
||||
</Flex>`;
|
||||
|
||||
export const allColorsSnippet = `<Flex gap="4" direction="column">
|
||||
<Link href="https://ui.backstage.io" variant="title-small" color="primary">
|
||||
I am primary
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-small" color="secondary">
|
||||
I am secondary
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-small" color="tertiary">
|
||||
I am tertiary
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" variant="title-small" color="inherit">
|
||||
I am inherit
|
||||
</Link>
|
||||
</Flex>`;
|
||||
|
||||
export const underlineSnippet = `<Flex gap="4" direction="column">
|
||||
<Link href="https://ui.backstage.io" underline="always">
|
||||
Always underlined
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" underline="hover">
|
||||
Underlined on hover
|
||||
</Link>
|
||||
<Link href="https://ui.backstage.io" underline="none">
|
||||
Never underlined
|
||||
</Link>
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,157 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
MenuTrigger,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuSection,
|
||||
MenuSeparator,
|
||||
SubmenuTrigger,
|
||||
MenuAutocomplete,
|
||||
MenuListBoxItem,
|
||||
} from '../../../../../packages/ui/src/components/Menu/Menu';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import {
|
||||
RiChat1Line,
|
||||
RiFileLine,
|
||||
RiFolderLine,
|
||||
RiImageLine,
|
||||
RiSettingsLine,
|
||||
RiShareBoxLine,
|
||||
} from '@remixicon/react';
|
||||
|
||||
export const Preview = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button aria-label="Menu">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>Edit</MenuItem>
|
||||
<MenuItem>Duplicate</MenuItem>
|
||||
<MenuItem>Rename</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem iconStart={<RiShareBoxLine />}>Share</MenuItem>
|
||||
<MenuItem iconStart={<RiChat1Line />}>Feedback</MenuItem>
|
||||
<MenuSeparator />
|
||||
<SubmenuTrigger>
|
||||
<MenuItem iconStart={<RiSettingsLine />}>Settings</MenuItem>
|
||||
<Menu placement="right top">
|
||||
<MenuItem>Edit</MenuItem>
|
||||
<MenuItem>Duplicate</MenuItem>
|
||||
<MenuItem>Rename</MenuItem>
|
||||
</Menu>
|
||||
</SubmenuTrigger>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewSubmenu = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>New File</MenuItem>
|
||||
<SubmenuTrigger>
|
||||
<MenuItem>Open Recent</MenuItem>
|
||||
<Menu>
|
||||
<MenuItem>File 1.txt</MenuItem>
|
||||
<MenuItem>File 2.txt</MenuItem>
|
||||
<MenuItem>File 3.txt</MenuItem>
|
||||
</Menu>
|
||||
</SubmenuTrigger>
|
||||
<MenuItem>Save</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewIcons = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem iconStart={<RiFileLine />}>New File</MenuItem>
|
||||
<MenuItem iconStart={<RiFolderLine />}>New Folder</MenuItem>
|
||||
<MenuItem iconStart={<RiImageLine />}>New Image</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewLinks = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem href="/home">Home</MenuItem>
|
||||
<MenuItem href="/about">About</MenuItem>
|
||||
<MenuItem href="/contact">Contact</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewSections = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuSection title="File">
|
||||
<MenuItem>New</MenuItem>
|
||||
<MenuItem>Open</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSection title="Edit">
|
||||
<MenuItem>Cut</MenuItem>
|
||||
<MenuItem>Copy</MenuItem>
|
||||
<MenuItem>Paste</MenuItem>
|
||||
</MenuSection>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewSeparators = () => (
|
||||
<MemoryRouter>
|
||||
<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>New</MenuItem>
|
||||
<MenuItem>Open</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem>Save</MenuItem>
|
||||
<MenuItem>Save As...</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewAutocompleteMenu = () => (
|
||||
<MemoryRouter>
|
||||
<MenuAutocomplete placeholder="Type to search...">
|
||||
<MenuItem>Option 1</MenuItem>
|
||||
<MenuItem>Option 2</MenuItem>
|
||||
<MenuItem>Option 3</MenuItem>
|
||||
</MenuAutocomplete>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewAutocompleteListbox = () => (
|
||||
<MemoryRouter>
|
||||
<MenuAutocomplete placeholder="Type to filter...">
|
||||
<MenuListBoxItem>Option 1</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 2</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 3</MenuListBoxItem>
|
||||
</MenuAutocomplete>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
export const PreviewAutocompleteListboxMultiple = () => (
|
||||
<MemoryRouter>
|
||||
<MenuAutocomplete placeholder="Type to filter..." selectionMode="multiple">
|
||||
<MenuListBoxItem>Option 1</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 2</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 3</MenuListBoxItem>
|
||||
</MenuAutocomplete>
|
||||
</MemoryRouter>
|
||||
);
|
||||
@@ -1,7 +1,29 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { MenuSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
Preview,
|
||||
PreviewSubmenu,
|
||||
PreviewIcons,
|
||||
PreviewLinks,
|
||||
PreviewSections,
|
||||
PreviewSeparators,
|
||||
PreviewAutocompleteMenu,
|
||||
PreviewAutocompleteListbox,
|
||||
PreviewAutocompleteListboxMultiple,
|
||||
} from './components';
|
||||
import {
|
||||
menuTriggerPropDefs,
|
||||
submenuTriggerPropDefs,
|
||||
menuPropDefs,
|
||||
menuListBoxPropDefs,
|
||||
menuAutocompletePropDefs,
|
||||
menuAutocompleteListboxPropDefs,
|
||||
menuItemPropDefs,
|
||||
menuListBoxItemPropDefs,
|
||||
menuSectionPropDefs,
|
||||
menuSeparatorPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
usage,
|
||||
preview,
|
||||
@@ -13,33 +35,18 @@ import {
|
||||
autocomplete,
|
||||
autocompleteListbox,
|
||||
autocompleteListboxMultiple,
|
||||
menuTriggerPropDefs,
|
||||
submenuTriggerPropDefs,
|
||||
menuPropDefs,
|
||||
menuListBoxPropDefs,
|
||||
menuAutocompletePropDefs,
|
||||
menuAutocompleteListboxPropDefs,
|
||||
menuItemPropDefs,
|
||||
menuListBoxItemPropDefs,
|
||||
menuSectionPropDefs,
|
||||
menuSeparatorPropDefs,
|
||||
} from './menu.props';
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { MenuDefinition } from '../utils/definitions';
|
||||
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={<MenuSnippet story="Preview" />}
|
||||
code={preview}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Preview />} code={preview} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -142,7 +149,7 @@ the submenu is displayed in the correct position. The best practice is to use th
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewSubmenu" />}
|
||||
preview={<PreviewSubmenu />}
|
||||
code={submenu}
|
||||
/>
|
||||
|
||||
@@ -150,26 +157,14 @@ the submenu is displayed in the correct position. The best practice is to use th
|
||||
|
||||
You can use the `iconStart` prop to add an icon to the menu item.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewIcons" />}
|
||||
code={icons}
|
||||
/>
|
||||
<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={<MenuSnippet story="PreviewLinks" />}
|
||||
code={links}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<PreviewLinks />} code={links} />
|
||||
|
||||
### With sections
|
||||
|
||||
@@ -179,7 +174,7 @@ You can use the `MenuSection` component to add a section to the menu.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewSections" />}
|
||||
preview={<PreviewSections />}
|
||||
code={sections}
|
||||
/>
|
||||
|
||||
@@ -191,7 +186,7 @@ You can use the `MenuSeparator` component to add a separator to the menu.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewSeparators" />}
|
||||
preview={<PreviewSeparators />}
|
||||
code={separators}
|
||||
/>
|
||||
|
||||
@@ -203,7 +198,7 @@ You can use the `MenuAutocomplete` component to add a autocomplete to the menu.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewAutocompleteMenu" />}
|
||||
preview={<PreviewAutocompleteMenu />}
|
||||
code={autocomplete}
|
||||
/>
|
||||
|
||||
@@ -215,7 +210,7 @@ You can use the `MenuListBox` component to add a list box to the menu.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewAutocompleteListbox" />}
|
||||
preview={<PreviewAutocompleteListbox />}
|
||||
code={autocompleteListbox}
|
||||
/>
|
||||
|
||||
@@ -228,7 +223,7 @@ allow multiple selection.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<MenuSnippet story="PreviewAutocompleteListboxMultiple" />}
|
||||
preview={<PreviewAutocompleteListboxMultiple />}
|
||||
code={autocompleteListboxMultiple}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const menuTriggerPropDefs: Record<string, PropDef> = {
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onOpenChange: {
|
||||
type: 'enum',
|
||||
values: ['(isOpen: boolean) => void'],
|
||||
},
|
||||
};
|
||||
|
||||
export const submenuTriggerPropDefs: Record<string, PropDef> = {
|
||||
delay: {
|
||||
type: 'number',
|
||||
default: '200',
|
||||
},
|
||||
};
|
||||
|
||||
export const menuPropDefs: Record<string, PropDef> = {
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['none', 'single', 'multiple'],
|
||||
},
|
||||
selectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
},
|
||||
defaultSelectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
},
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left start',
|
||||
'left end',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
},
|
||||
virtualized: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
maxWidth: {
|
||||
type: 'number',
|
||||
},
|
||||
maxHeight: {
|
||||
type: 'number',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuListBoxPropDefs: Record<string, PropDef> = {
|
||||
disabledKeys: {
|
||||
type: 'enum',
|
||||
values: ['Iterable<Key>'],
|
||||
},
|
||||
selectionMode: {
|
||||
type: 'enum',
|
||||
values: ['none', 'single', 'multiple'],
|
||||
},
|
||||
selectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
},
|
||||
defaultSelectedKeys: {
|
||||
type: 'enum',
|
||||
values: ['all', 'Iterable<Key>'],
|
||||
},
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left start',
|
||||
'left end',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
},
|
||||
virtualized: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
maxWidth: {
|
||||
type: 'number',
|
||||
},
|
||||
maxHeight: {
|
||||
type: 'number',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuAutocompletePropDefs: Record<string, PropDef> = {
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left start',
|
||||
'left end',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
},
|
||||
virtualized: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
maxWidth: {
|
||||
type: 'number',
|
||||
},
|
||||
maxHeight: {
|
||||
type: 'number',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuAutocompleteListboxPropDefs: Record<string, PropDef> = {
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right',
|
||||
'top start',
|
||||
'top end',
|
||||
'bottom start',
|
||||
'bottom end',
|
||||
'left start',
|
||||
'left end',
|
||||
'right start',
|
||||
'right end',
|
||||
],
|
||||
},
|
||||
virtualized: {
|
||||
type: 'boolean',
|
||||
default: 'false',
|
||||
},
|
||||
maxWidth: {
|
||||
type: 'number',
|
||||
},
|
||||
maxHeight: {
|
||||
type: 'number',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuItemPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
href: {
|
||||
type: 'string',
|
||||
},
|
||||
onAction: {
|
||||
type: 'enum',
|
||||
values: ['(event) => void'],
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuListBoxItemPropDefs: Record<string, PropDef> = {
|
||||
id: {
|
||||
type: 'enum',
|
||||
values: ['Key'],
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
},
|
||||
textValue: {
|
||||
type: 'string',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuSectionPropDefs: Record<string, PropDef> = {
|
||||
title: {
|
||||
type: 'string',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
|
||||
export const menuSeparatorPropDefs: Record<string, PropDef> = {
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
export const usage = `import { MenuTrigger, Menu, MenuItem } from '@backstage/ui';
|
||||
|
||||
<MenuTrigger>
|
||||
<Button>Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>Item 1</MenuItem>
|
||||
<MenuItem>Item 2</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const preview = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>New File</MenuItem>
|
||||
<MenuItem>Open File</MenuItem>
|
||||
<MenuItem>Save</MenuItem>
|
||||
<MenuItem>Save As...</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const submenu = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>New File</MenuItem>
|
||||
<SubmenuTrigger>
|
||||
<MenuItem>Open Recent</MenuItem>
|
||||
<Menu>
|
||||
<MenuItem>File 1.txt</MenuItem>
|
||||
<MenuItem>File 2.txt</MenuItem>
|
||||
</Menu>
|
||||
</SubmenuTrigger>
|
||||
<MenuItem>Save</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const icons = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem icon={<RiFileLine />}>New File</MenuItem>
|
||||
<MenuItem icon={<RiFolderLine />}>New Folder</MenuItem>
|
||||
<MenuItem icon={<RiImageLine />}>New Image</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const sections = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuSection title="File">
|
||||
<MenuItem>New</MenuItem>
|
||||
<MenuItem>Open</MenuItem>
|
||||
</MenuSection>
|
||||
<MenuSection title="Edit">
|
||||
<MenuItem>Cut</MenuItem>
|
||||
<MenuItem>Copy</MenuItem>
|
||||
<MenuItem>Paste</MenuItem>
|
||||
</MenuSection>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const separators = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem>New</MenuItem>
|
||||
<MenuItem>Open</MenuItem>
|
||||
<MenuSeparator />
|
||||
<MenuItem>Save</MenuItem>
|
||||
<MenuItem>Save As...</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const links = `<MenuTrigger>
|
||||
<Button variant="secondary">Menu</Button>
|
||||
<Menu>
|
||||
<MenuItem href="/home">Home</MenuItem>
|
||||
<MenuItem href="/about">About</MenuItem>
|
||||
<MenuItem href="/contact">Contact</MenuItem>
|
||||
</Menu>
|
||||
</MenuTrigger>`;
|
||||
|
||||
export const autocomplete = `<MenuAutocomplete label="Search" placeholder="Type to search...">
|
||||
<MenuItem>Option 1</MenuItem>
|
||||
<MenuItem>Option 2</MenuItem>
|
||||
<MenuItem>Option 3</MenuItem>
|
||||
</MenuAutocomplete>`;
|
||||
|
||||
export const autocompleteListbox = `<MenuAutocomplete label="Select an option" placeholder="Type to filter...">
|
||||
<MenuListBoxItem>Option 1</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 2</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 3</MenuListBoxItem>
|
||||
</MenuAutocomplete>`;
|
||||
|
||||
export const autocompleteListboxMultiple = `<MenuAutocomplete
|
||||
label="Select multiple options"
|
||||
placeholder="Type to filter..."
|
||||
selectionMode="multiple"
|
||||
>
|
||||
<MenuListBoxItem>Option 1</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 2</MenuListBoxItem>
|
||||
<MenuListBoxItem>Option 3</MenuListBoxItem>
|
||||
</MenuAutocomplete>`;
|
||||
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { PasswordField } from '../../../../../packages/ui/src/components/PasswordField/PasswordField';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
|
||||
export const WithLabel = () => {
|
||||
return (
|
||||
<PasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
style={{ maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex direction="row" gap="4" style={{ width: '100%', maxWidth: '600px' }}>
|
||||
<PasswordField name="password" label="Password" size="small" />
|
||||
<PasswordField name="password" label="Password" size="medium" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDescription = () => {
|
||||
return (
|
||||
<PasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
description="Enter your password"
|
||||
style={{ maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+12
-18
@@ -1,18 +1,18 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { PasswordFieldSnippet } from '@/snippets/stories-snippets';
|
||||
import { inputPropDefs } from './props-definition';
|
||||
import {
|
||||
inputPropDefs,
|
||||
passwordFieldUsageSnippet,
|
||||
passwordFieldDefaultSnippet,
|
||||
passwordFieldSizesSnippet,
|
||||
passwordFieldDescriptionSnippet,
|
||||
} from './password-field.props';
|
||||
withLabelSnippet,
|
||||
sizesSnippet,
|
||||
withDescriptionSnippet,
|
||||
} from './snippets';
|
||||
import { WithLabel, Sizes, WithDescription } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { PasswordFieldDefinition } from '../utils/definitions';
|
||||
import { PasswordFieldDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="PasswordField"
|
||||
@@ -22,8 +22,8 @@ import { PasswordFieldDefinition } from '../utils/definitions';
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<PasswordFieldSnippet story="WithLabel" />}
|
||||
code={passwordFieldDefaultSnippet}
|
||||
preview={<WithLabel />}
|
||||
code={withLabelSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
@@ -40,13 +40,7 @@ import { PasswordFieldDefinition } from '../utils/definitions';
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="Sizes" />}
|
||||
code={passwordFieldSizesSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### With description
|
||||
|
||||
@@ -56,8 +50,8 @@ Here's a simple PasswordField with a description.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<PasswordFieldSnippet story="WithDescription" />}
|
||||
code={passwordFieldDescriptionSnippet}
|
||||
preview={<WithDescription />}
|
||||
code={withDescriptionSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={PasswordFieldDefinition} />
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
|
||||
export const inputPropDefs: Record<string, PropDef> = {
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export const passwordFieldUsageSnippet = `import { PasswordField } from '@backstage/ui';
|
||||
|
||||
<PasswordField />`;
|
||||
|
||||
export const withLabelSnippet = `<PasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
/>`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="row" gap="4">
|
||||
<PasswordField name="password" label="Password" size="small" />
|
||||
<PasswordField name="password" label="Password" size="medium" />
|
||||
</Flex>`;
|
||||
|
||||
export const withDescriptionSnippet = `<PasswordField
|
||||
name="password"
|
||||
label="Password"
|
||||
description="Enter your password"
|
||||
/>`;
|
||||
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import { Popover } from '../../../../../packages/ui/src/components/Popover/Popover';
|
||||
import { DialogTrigger } from '../../../../../packages/ui/src/components/Dialog/Dialog';
|
||||
import { Button } from '../../../../../packages/ui/src/components/Button/Button';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<div style={{ padding: '16px' }}>
|
||||
<p>This is a popover</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { dialogTriggerPropDefs, popoverPropDefs } from './props-definition';
|
||||
import { popoverUsageSnippet, defaultSnippet } from './snippets';
|
||||
import { Default } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { PopoverDefinition } from '../../../utils/definitions';
|
||||
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={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={popoverUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
### DialogTrigger
|
||||
|
||||
The trigger component that wraps both the trigger button and the popover content.
|
||||
|
||||
<PropsTable data={dialogTriggerPropDefs} />
|
||||
|
||||
### Popover
|
||||
|
||||
The main popover component that displays floating content with automatic positioning.
|
||||
|
||||
<PropsTable data={popoverPropDefs} />
|
||||
|
||||
<Theming definition={PopoverDefinition} />
|
||||
|
||||
<ChangelogComponent component="popover" />
|
||||
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
childrenPropDefs,
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
} from '@/utils/propDefs';
|
||||
import type { PropDef } from '@/utils/propDefs';
|
||||
|
||||
export const dialogTriggerPropDefs: Record<string, PropDef> = {
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
},
|
||||
delay: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
},
|
||||
closeDelay: {
|
||||
type: 'number',
|
||||
default: '0',
|
||||
},
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'boolean',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
};
|
||||
|
||||
export const popoverPropDefs: Record<string, PropDef> = {
|
||||
triggerRef: {
|
||||
type: 'enum',
|
||||
values: ['RefObject<Element | null>'],
|
||||
},
|
||||
isEntering: {
|
||||
type: 'boolean',
|
||||
},
|
||||
isExiting: {
|
||||
type: 'boolean',
|
||||
},
|
||||
placement: {
|
||||
type: 'enum',
|
||||
values: ['top', 'right', 'bottom', 'left'],
|
||||
},
|
||||
containerPadding: {
|
||||
type: 'number',
|
||||
default: '12',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
default: '8',
|
||||
},
|
||||
...childrenPropDefs,
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
export const popoverUsageSnippet = `import { DialogTrigger, Popover, Button } from '@backstage/ui';
|
||||
|
||||
<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<div style={{ padding: '16px' }}>
|
||||
<p>This is a popover</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
|
||||
export const defaultSnippet = `<DialogTrigger>
|
||||
<Button>Open Popover</Button>
|
||||
<Popover>
|
||||
<div style={{ padding: '16px' }}>
|
||||
<p>This is a popover</p>
|
||||
</div>
|
||||
</Popover>
|
||||
</DialogTrigger>`;
|
||||
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
RadioGroup,
|
||||
Radio,
|
||||
} from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<RadioGroup label="What is your favorite pokemon?">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const Horizontal = () => {
|
||||
return (
|
||||
<RadioGroup label="What is your favorite pokemon?" orientation="horizontal">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return (
|
||||
<RadioGroup label="What is your favorite pokemon?" isDisabled>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const DisabledSingle = () => {
|
||||
return (
|
||||
<RadioGroup label="What is your favorite pokemon?">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander" isDisabled>
|
||||
Charmander
|
||||
</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const Validation = () => {
|
||||
return (
|
||||
<RadioGroup
|
||||
label="What is your favorite pokemon?"
|
||||
name="pokemon"
|
||||
defaultValue="charmander"
|
||||
validationBehavior="aria"
|
||||
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 ReadOnly = () => {
|
||||
return (
|
||||
<RadioGroup
|
||||
label="What is your favorite pokemon?"
|
||||
isReadOnly
|
||||
defaultValue="charmander"
|
||||
>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
||||
+28
-27
@@ -1,34 +1,35 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { RadioGroupSnippet } from '@/snippets/stories-snippets';
|
||||
import { radioGroupPropDefs } from './props-definition';
|
||||
import {
|
||||
radioGroupPropDefs,
|
||||
radioGroupUsageSnippet,
|
||||
radioGroupDefaultSnippet,
|
||||
radioGroupDescriptionSnippet,
|
||||
radioGroupHorizontalSnippet,
|
||||
radioGroupDisabledSnippet,
|
||||
radioGroupDisabledSingleSnippet,
|
||||
radioGroupValidationSnippet,
|
||||
radioGroupReadOnlySnippet,
|
||||
} from './radio-group.props';
|
||||
defaultSnippet,
|
||||
horizontalSnippet,
|
||||
disabledSnippet,
|
||||
disabledSingleSnippet,
|
||||
validationSnippet,
|
||||
readOnlySnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
Default,
|
||||
Horizontal,
|
||||
Disabled,
|
||||
DisabledSingle,
|
||||
Validation,
|
||||
ReadOnly,
|
||||
} from './components';
|
||||
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';
|
||||
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={<RadioGroupSnippet story="Default" />}
|
||||
code={radioGroupDefaultSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -48,8 +49,8 @@ Here's a simple TextField with a description.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RadioGroupSnippet story="Horizontal" />}
|
||||
code={radioGroupHorizontalSnippet}
|
||||
preview={<Horizontal />}
|
||||
code={horizontalSnippet}
|
||||
/>
|
||||
|
||||
### Disabled
|
||||
@@ -60,8 +61,8 @@ You can disable the entire radio group by adding the `isDisabled` prop to the `R
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RadioGroupSnippet story="Disabled" />}
|
||||
code={radioGroupDisabledSnippet}
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
### Disabled Single radio
|
||||
@@ -72,8 +73,8 @@ You can disable a single radio by adding the `isDisabled` prop to the `Radio` co
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RadioGroupSnippet story="DisabledSingle" />}
|
||||
code={radioGroupDisabledSingleSnippet}
|
||||
preview={<DisabledSingle />}
|
||||
code={disabledSingleSnippet}
|
||||
/>
|
||||
|
||||
### Validation
|
||||
@@ -84,8 +85,8 @@ Here's an example of a radio group with errors.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RadioGroupSnippet story="Validation" />}
|
||||
code={radioGroupValidationSnippet}
|
||||
preview={<Validation />}
|
||||
code={validationSnippet}
|
||||
/>
|
||||
|
||||
### Read only
|
||||
@@ -96,8 +97,8 @@ You can make the radio group read only by adding the `isReadOnly` prop to the `R
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<RadioGroupSnippet story="ReadOnly" />}
|
||||
code={radioGroupReadOnlySnippet}
|
||||
preview={<ReadOnly />}
|
||||
code={readOnlySnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={RadioGroupDefinition} />
|
||||
@@ -0,0 +1,30 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
export const radioGroupUsageSnippet = `import { RadioGroup } from '@backstage/ui';
|
||||
|
||||
<RadioGroup />`;
|
||||
|
||||
export const defaultSnippet = `<RadioGroup label="What is your favorite pokemon?">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const horizontalSnippet = `<RadioGroup
|
||||
label="What is your favorite pokemon?"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const disabledSnippet = `<RadioGroup label="What is your favorite pokemon?" isDisabled>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const disabledSingleSnippet = `<RadioGroup label="What is your favorite pokemon?">
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander" isDisabled>
|
||||
Charmander
|
||||
</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
|
||||
export const validationSnippet = `<RadioGroup
|
||||
label="What is your favorite pokemon?"
|
||||
name="pokemon"
|
||||
defaultValue="charmander"
|
||||
validationBehavior="aria"
|
||||
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 readOnlySnippet = `<RadioGroup
|
||||
label="What is your favorite pokemon?"
|
||||
isReadOnly
|
||||
defaultValue="charmander"
|
||||
>
|
||||
<Radio value="bulbasaur">Bulbasaur</Radio>
|
||||
<Radio value="charmander">Charmander</Radio>
|
||||
<Radio value="squirtle">Squirtle</Radio>
|
||||
</RadioGroup>`;
|
||||
@@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import { SearchField } from '../../../../../packages/ui/src/components/SearchField/SearchField';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
|
||||
export const WithLabel = () => {
|
||||
return (
|
||||
<SearchField
|
||||
name="url"
|
||||
label="Label"
|
||||
aria-label="Search"
|
||||
style={{ width: '100%', maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex direction="row" gap="4" style={{ width: '100%', maxWidth: '600px' }}>
|
||||
<SearchField name="url" aria-label="Search" size="small" />
|
||||
<SearchField name="url" aria-label="Search" size="medium" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDescription = () => {
|
||||
return (
|
||||
<SearchField
|
||||
name="url"
|
||||
label="Label"
|
||||
description="Description"
|
||||
aria-label="Search"
|
||||
style={{ width: '100%', maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const StartCollapsed = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="row" gap="4">
|
||||
<SearchField
|
||||
name="url"
|
||||
aria-label="Search"
|
||||
size="small"
|
||||
startCollapsed
|
||||
/>
|
||||
<SearchField
|
||||
name="url"
|
||||
aria-label="Search"
|
||||
size="medium"
|
||||
startCollapsed
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
+20
-21
@@ -1,19 +1,24 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { SearchFieldSnippet } from '@/snippets/stories-snippets';
|
||||
import { searchFieldPropDefs } from './props-definition';
|
||||
import {
|
||||
searchFieldPropDefs,
|
||||
searchFieldUsageSnippet,
|
||||
searchFieldDefaultSnippet,
|
||||
searchFieldSizesSnippet,
|
||||
searchFieldDescriptionSnippet,
|
||||
searchFieldCollapsibleSnippet,
|
||||
} from './search-field.props';
|
||||
withLabelSnippet,
|
||||
sizesSnippet,
|
||||
withDescriptionSnippet,
|
||||
startCollapsedSnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
WithLabel,
|
||||
Sizes,
|
||||
WithDescription,
|
||||
StartCollapsed,
|
||||
} from './components';
|
||||
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';
|
||||
import { SearchFieldDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="SearchField"
|
||||
@@ -23,8 +28,8 @@ import { SearchFieldDefinition } from '../utils/definitions';
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SearchFieldSnippet story="WithLabel" />}
|
||||
code={searchFieldDefaultSnippet}
|
||||
preview={<WithLabel />}
|
||||
code={withLabelSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
@@ -41,13 +46,7 @@ import { SearchFieldDefinition } from '../utils/definitions';
|
||||
|
||||
We support two different sizes: `small`, `medium`.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SearchFieldSnippet story="Sizes" />}
|
||||
code={searchFieldSizesSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} open preview={<Sizes />} code={sizesSnippet} />
|
||||
|
||||
### With description
|
||||
|
||||
@@ -57,8 +56,8 @@ Here's a simple SearchField with a description.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SearchFieldSnippet story="WithDescription" />}
|
||||
code={searchFieldDescriptionSnippet}
|
||||
preview={<WithDescription />}
|
||||
code={withDescriptionSnippet}
|
||||
/>
|
||||
|
||||
### Collapsible
|
||||
@@ -69,8 +68,8 @@ You can make the SearchField collapsible by setting the `startCollapsed` prop to
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SearchFieldSnippet story="StartCollapsed" />}
|
||||
code={searchFieldCollapsibleSnippet}
|
||||
preview={<StartCollapsed />}
|
||||
code={startCollapsedSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={SearchFieldDefinition} />
|
||||
@@ -0,0 +1,34 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
export const searchFieldUsageSnippet = `import { SearchField } from '@backstage/ui';
|
||||
|
||||
<SearchField />`;
|
||||
|
||||
export const withLabelSnippet = `<SearchField name="url" label="Label" aria-label="Search" />`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="row" gap="4">
|
||||
<SearchField name="url" aria-label="Search" size="small" />
|
||||
<SearchField name="url" aria-label="Search" size="medium" />
|
||||
</Flex>`;
|
||||
|
||||
export const withDescriptionSnippet = `<SearchField
|
||||
name="url"
|
||||
label="Label"
|
||||
description="Description"
|
||||
aria-label="Search"
|
||||
/>`;
|
||||
|
||||
export const startCollapsedSnippet = `<Flex direction="column" gap="4">
|
||||
<Flex direction="row" gap="4">
|
||||
<SearchField
|
||||
name="url"
|
||||
aria-label="Search"
|
||||
size="small"
|
||||
startCollapsed
|
||||
/>
|
||||
<SearchField
|
||||
name="url"
|
||||
aria-label="Search"
|
||||
size="medium"
|
||||
startCollapsed
|
||||
/>
|
||||
</Flex>
|
||||
<SearchField name="url" aria-label="Search" size="small" startCollapsed />
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,150 @@
|
||||
'use client';
|
||||
|
||||
import { Select } from '../../../../../packages/ui/src/components/Select/Select';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiCloudLine } from '@remixicon/react';
|
||||
|
||||
const fontOptions = [
|
||||
{ value: 'sans', label: 'Sans-serif' },
|
||||
{ value: 'serif', label: 'Serif' },
|
||||
{ value: 'mono', label: 'Monospace' },
|
||||
{ value: 'cursive', label: 'Cursive' },
|
||||
];
|
||||
|
||||
const countries = [
|
||||
{ value: 'us', label: 'United States' },
|
||||
{ value: 'ca', label: 'Canada' },
|
||||
{ value: 'mx', label: 'Mexico' },
|
||||
{ value: 'uk', label: 'United Kingdom' },
|
||||
{ value: 'fr', label: 'France' },
|
||||
{ value: 'de', label: 'Germany' },
|
||||
{ value: 'it', label: 'Italy' },
|
||||
{ value: 'es', label: 'Spain' },
|
||||
{ value: 'jp', label: 'Japan' },
|
||||
{ value: 'cn', label: 'China' },
|
||||
{ value: 'in', label: 'India' },
|
||||
{ value: 'br', label: 'Brazil' },
|
||||
{ value: 'au', label: 'Australia' },
|
||||
];
|
||||
|
||||
const skills = [
|
||||
{ value: 'react', label: 'React' },
|
||||
{ value: 'typescript', label: 'TypeScript' },
|
||||
{ value: 'javascript', label: 'JavaScript' },
|
||||
{ value: 'python', label: 'Python' },
|
||||
{ value: 'java', label: 'Java' },
|
||||
{ value: 'csharp', label: 'C#' },
|
||||
{ value: 'go', label: 'Go' },
|
||||
{ value: 'rust', label: 'Rust' },
|
||||
{ value: 'kotlin', label: 'Kotlin' },
|
||||
{ value: 'swift', label: 'Swift' },
|
||||
];
|
||||
|
||||
export const Preview = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
placeholder="Select a font"
|
||||
name="font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const WithLabelAndDescription = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
description="Choose a font family for your document"
|
||||
options={fontOptions}
|
||||
name="font"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Sizes = () => (
|
||||
<Flex direction="row" gap="2">
|
||||
<Select
|
||||
label="Small"
|
||||
size="small"
|
||||
options={fontOptions}
|
||||
name="font-small"
|
||||
placeholder="Select a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
<Select
|
||||
label="Medium"
|
||||
size="medium"
|
||||
options={fontOptions}
|
||||
name="font-medium"
|
||||
placeholder="Select a font"
|
||||
style={{ maxWidth: 260 }}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export const WithIcon = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
name="font"
|
||||
icon={<RiCloudLine />}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Disabled = () => (
|
||||
<Select
|
||||
label="Font Family"
|
||||
options={fontOptions}
|
||||
name="font"
|
||||
isDisabled
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const DisabledOption = () => (
|
||||
<Select
|
||||
label="Select an option"
|
||||
options={fontOptions}
|
||||
name="font"
|
||||
disabledKeys={['serif']}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Searchable = () => (
|
||||
<Select
|
||||
label="Country"
|
||||
searchable
|
||||
searchPlaceholder="Search countries..."
|
||||
options={countries}
|
||||
name="country"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const MultipleSelection = () => (
|
||||
<Select
|
||||
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' },
|
||||
]}
|
||||
name="multiple"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
|
||||
export const SearchableMultiple = () => (
|
||||
<Select
|
||||
label="Skills"
|
||||
searchable
|
||||
selectionMode="multiple"
|
||||
searchPlaceholder="Filter skills..."
|
||||
options={skills}
|
||||
name="skills"
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
);
|
||||
@@ -1,9 +1,19 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SelectSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
selectPropDefs,
|
||||
Preview,
|
||||
WithLabelAndDescription,
|
||||
Sizes,
|
||||
WithIcon,
|
||||
Disabled,
|
||||
DisabledOption,
|
||||
Searchable,
|
||||
MultipleSelection,
|
||||
SearchableMultiple,
|
||||
} from './components';
|
||||
import { selectPropDefs } from './props-definition';
|
||||
import {
|
||||
selectUsageSnippet,
|
||||
selectDefaultSnippet,
|
||||
selectDescriptionSnippet,
|
||||
@@ -15,11 +25,11 @@ import {
|
||||
selectMultipleSnippet,
|
||||
selectSearchableMultipleSnippet,
|
||||
selectDisabledOptionsSnippet,
|
||||
} from './select.props';
|
||||
} from './snippets';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SelectDefinition } from '../utils/definitions';
|
||||
import { SelectDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Select"
|
||||
@@ -29,7 +39,7 @@ import { SelectDefinition } from '../utils/definitions';
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SelectSnippet story="Preview" />}
|
||||
preview={<Preview />}
|
||||
code={selectDefaultSnippet}
|
||||
/>
|
||||
|
||||
@@ -51,7 +61,7 @@ Select component with label and description.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="WithLabelAndDescription" />}
|
||||
preview={<WithLabelAndDescription />}
|
||||
code={selectDescriptionSnippet}
|
||||
/>
|
||||
|
||||
@@ -63,7 +73,7 @@ Here's a view when the selects have different sizes.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Sizes" />}
|
||||
preview={<Sizes />}
|
||||
code={selectSizesSnippet}
|
||||
/>
|
||||
|
||||
@@ -76,7 +86,7 @@ Here's a view when the select has an icon.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="WithIcon" />}
|
||||
preview={<WithIcon />}
|
||||
code={selectIconSnippet}
|
||||
/>
|
||||
|
||||
@@ -88,7 +98,7 @@ Here's a view when the select is disabled.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Disabled" />}
|
||||
preview={<Disabled />}
|
||||
code={selectDisabledSnippet}
|
||||
/>
|
||||
|
||||
@@ -100,7 +110,7 @@ You can disable specific options within the Select component using `disabledKeys
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="DisabledOption" />}
|
||||
preview={<DisabledOption />}
|
||||
code={selectDisabledOptionsSnippet}
|
||||
/>
|
||||
|
||||
@@ -112,7 +122,7 @@ Here's a view when the select has searchable filtering.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="Searchable" />}
|
||||
preview={<Searchable />}
|
||||
code={selectSearchableSnippet}
|
||||
/>
|
||||
|
||||
@@ -124,7 +134,7 @@ Here's a view when the select allows multiple selections.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="MultipleSelection" />}
|
||||
preview={<MultipleSelection />}
|
||||
code={selectMultipleSnippet}
|
||||
/>
|
||||
|
||||
@@ -136,7 +146,7 @@ Here's a view when the select combines search and multiple selection.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SelectSnippet story="SearchableMultiple" />}
|
||||
preview={<SearchableMultiple />}
|
||||
code={selectSearchableMultipleSnippet}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
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,
|
||||
};
|
||||
@@ -1,115 +1,3 @@
|
||||
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
|
||||
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { Skeleton } from '../../../../../packages/ui/src/components/Skeleton/Skeleton';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
|
||||
export const Demo1 = () => {
|
||||
return (
|
||||
<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 Demo2 = () => {
|
||||
return (
|
||||
<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -1,30 +1,20 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SkeletonSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
skeletonPropDefs,
|
||||
skeletonUsageSnippet,
|
||||
skeletonDefaultSnippet,
|
||||
skeletonDemo1Snippet,
|
||||
skeletonDemo2Snippet,
|
||||
} from './skeleton.props';
|
||||
import { skeletonPropDefs } from './props-definition';
|
||||
import { skeletonUsageSnippet, demo1Snippet, demo2Snippet } from './snippets';
|
||||
import { Demo1, Demo2 } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SkeletonDefinition } from '../utils/definitions';
|
||||
import { SkeletonDefinition } from '../../../utils/definitions';
|
||||
|
||||
<PageTitle
|
||||
title="Skeleton"
|
||||
description="Use to show a placeholder while content is loading."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo2" />}
|
||||
code={skeletonDefaultSnippet}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Demo2 />} code={demo2Snippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -40,25 +30,13 @@ import { SkeletonDefinition } from '../utils/definitions';
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo1" />}
|
||||
code={skeletonDemo1Snippet}
|
||||
open
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Demo1 />} code={demo1Snippet} open />
|
||||
|
||||
### Demo 2
|
||||
|
||||
You can use a mix of different sizes to create a more complex skeleton.
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<SkeletonSnippet story="Demo2" />}
|
||||
code={skeletonDemo2Snippet}
|
||||
open
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Demo2 />} code={demo2Snippet} open />
|
||||
|
||||
<Theming definition={SkeletonDefinition} />
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
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 demo1Snippet = `<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 demo2Snippet = `<Flex direction="column" gap="4">
|
||||
<Skeleton width={400} height={160} />
|
||||
<Skeleton width={400} height={12} />
|
||||
<Skeleton width={240} height={12} />
|
||||
</Flex>`;
|
||||
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { Switch } from '../../../../../packages/ui/src/components/Switch/Switch';
|
||||
|
||||
export const Default = () => {
|
||||
return <Switch label="Switch" />;
|
||||
};
|
||||
|
||||
export const Disabled = () => {
|
||||
return <Switch label="Switch" isDisabled />;
|
||||
};
|
||||
@@ -1,24 +1,20 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { SwitchSnippet } from '@/snippets/stories-snippets';
|
||||
import { switchPropDefs, snippetUsage } from './switch.props';
|
||||
import { switchPropDefs } from './props-definition';
|
||||
import { snippetUsage, defaultSnippet, disabledSnippet } from './snippets';
|
||||
import { Default, Disabled } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { SwitchDefinition } from '../utils/definitions';
|
||||
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={<SwitchSnippet story="Default" />}
|
||||
code={`<Switch />`}
|
||||
/>
|
||||
<Snippet align="center" py={4} preview={<Default />} code={defaultSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -38,8 +34,8 @@ A switch can be disabled using the `isDisabled` prop.
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<SwitchSnippet story="Disabled" />}
|
||||
code={`<Switch isDisabled />`}
|
||||
preview={<Disabled />}
|
||||
code={disabledSnippet}
|
||||
/>
|
||||
|
||||
<Theming definition={SwitchDefinition} />
|
||||
-4
@@ -65,7 +65,3 @@ export const switchPropDefs: Record<string, PropDef> = {
|
||||
type: 'string',
|
||||
},
|
||||
};
|
||||
|
||||
export const snippetUsage = `import { Switch } from '@backstage/ui';
|
||||
|
||||
<Switch />`;
|
||||
@@ -0,0 +1,7 @@
|
||||
export const snippetUsage = `import { Switch } from '@backstage/ui';
|
||||
|
||||
<Switch />`;
|
||||
|
||||
export const defaultSnippet = `<Switch label="Switch" />`;
|
||||
|
||||
export const disabledSnippet = `<Switch label="Switch" isDisabled />`;
|
||||
@@ -0,0 +1,188 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Table,
|
||||
CellProfile,
|
||||
CellText,
|
||||
type ColumnConfig,
|
||||
useTable,
|
||||
} from '../../../../../packages/ui/src/components/Table';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
import { RadioGroup } from '../../../../../packages/ui/src/components/RadioGroup/RadioGroup';
|
||||
import { Radio } from '../../../../../packages/ui/src/components/RadioGroup';
|
||||
import { data as data4 } from '../../../../../packages/ui/src/components/Table/stories/mocked-data4';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
selectionData,
|
||||
selectionColumns,
|
||||
} from '../../../../../packages/ui/src/components/Table/stories/utils';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
type Data4Item = (typeof data4)[0];
|
||||
|
||||
const columns: ColumnConfig<Data4Item>[] = [
|
||||
{
|
||||
id: 'name',
|
||||
label: 'Band name',
|
||||
isRowHeader: true,
|
||||
cell: item => (
|
||||
<CellProfile name={item.name} src={item.image} href={item.website} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'genre',
|
||||
label: 'Genre',
|
||||
cell: item => <CellText title={item.genre} />,
|
||||
},
|
||||
{
|
||||
id: 'yearFormed',
|
||||
label: 'Year formed',
|
||||
cell: item => <CellText title={item.yearFormed.toString()} />,
|
||||
},
|
||||
{
|
||||
id: 'albums',
|
||||
label: 'Albums',
|
||||
cell: item => <CellText title={item.albums.toString()} />,
|
||||
},
|
||||
];
|
||||
|
||||
export const TableRockBand = () => {
|
||||
const { tableProps } = useTable({
|
||||
mode: 'complete',
|
||||
getData: () => data4,
|
||||
paginationOptions: { pageSize: 5 },
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Table columnConfig={columns} {...tableProps} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectionToggleWithActions = () => {
|
||||
const [selected, setSelected] = useState<Set<string | number> | 'all'>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const { tableProps } = useTable({
|
||||
mode: 'complete',
|
||||
getData: () => selectionData,
|
||||
paginationOptions: { pageSize: 10 },
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Table
|
||||
{...tableProps}
|
||||
columnConfig={selectionColumns}
|
||||
selection={{
|
||||
mode: 'multiple',
|
||||
behavior: 'toggle',
|
||||
selected,
|
||||
onSelectionChange: setSelected,
|
||||
}}
|
||||
rowConfig={{ onClick: item => alert(`Clicked: ${item.name}`) }}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectionModePlayground = () => {
|
||||
const [selectionMode, setSelectionMode] = useState<'single' | 'multiple'>(
|
||||
'multiple',
|
||||
);
|
||||
const [selected, setSelected] = useState<Set<string | number> | 'all'>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const { tableProps } = useTable({
|
||||
mode: 'complete',
|
||||
getData: () => selectionData,
|
||||
paginationOptions: { pageSize: 10 },
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex direction="column" gap="8">
|
||||
<Table
|
||||
{...tableProps}
|
||||
columnConfig={selectionColumns}
|
||||
selection={{
|
||||
mode: selectionMode,
|
||||
behavior: 'toggle',
|
||||
selected,
|
||||
onSelectionChange: setSelected,
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Text as="h4" style={{ marginBottom: 'var(--bui-space-2)' }}>
|
||||
Selection mode:
|
||||
</Text>
|
||||
<RadioGroup
|
||||
aria-label="Selection mode"
|
||||
orientation="horizontal"
|
||||
value={selectionMode}
|
||||
onChange={value => {
|
||||
setSelectionMode(value as 'single' | 'multiple');
|
||||
setSelected(new Set());
|
||||
}}
|
||||
>
|
||||
<Radio value="single">single</Radio>
|
||||
<Radio value="multiple">multiple</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectionBehaviorPlayground = () => {
|
||||
const [selectionBehavior, setSelectionBehavior] = useState<
|
||||
'toggle' | 'replace'
|
||||
>('toggle');
|
||||
const [selected, setSelected] = useState<Set<string | number> | 'all'>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
const { tableProps } = useTable({
|
||||
mode: 'complete',
|
||||
getData: () => selectionData,
|
||||
paginationOptions: { pageSize: 10 },
|
||||
});
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Flex direction="column" gap="8">
|
||||
<Table
|
||||
{...tableProps}
|
||||
columnConfig={selectionColumns}
|
||||
selection={{
|
||||
mode: 'multiple',
|
||||
behavior: selectionBehavior,
|
||||
selected,
|
||||
onSelectionChange: setSelected,
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<Text as="h4" style={{ marginBottom: 'var(--bui-space-2)' }}>
|
||||
Selection behavior:
|
||||
</Text>
|
||||
<RadioGroup
|
||||
aria-label="Selection behavior"
|
||||
orientation="horizontal"
|
||||
value={selectionBehavior}
|
||||
onChange={value => {
|
||||
setSelectionBehavior(value as 'toggle' | 'replace');
|
||||
setSelected(new Set());
|
||||
}}
|
||||
>
|
||||
<Radio value="toggle">toggle</Radio>
|
||||
<Radio value="replace">replace</Radio>
|
||||
</RadioGroup>
|
||||
</div>
|
||||
</Flex>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@@ -1,41 +1,39 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { TableSnippet } from '@/snippets/stories-snippets';
|
||||
import {
|
||||
TableRockBand,
|
||||
SelectionModePlayground,
|
||||
SelectionBehaviorPlayground,
|
||||
SelectionToggleWithActions,
|
||||
} from './components';
|
||||
import {
|
||||
tablePropDefs,
|
||||
tableHeaderPropDefs,
|
||||
tableBodyPropDefs,
|
||||
tablePaginationPropDefs,
|
||||
tableUsageSnippet,
|
||||
tableBasicSnippet,
|
||||
tableRowClickSnippet,
|
||||
tableHybridSnippet,
|
||||
tableCellInteractionsSnippet,
|
||||
tablePaginationSnippet,
|
||||
tableSelectionActionsSnippet,
|
||||
tableSelectionModeSnippet,
|
||||
tableSelectionBehaviorSnippet,
|
||||
tableSortingSnippet,
|
||||
columnPropDefs,
|
||||
rowPropDefs,
|
||||
cellPropDefs,
|
||||
} from './table.props';
|
||||
} from './props-definition';
|
||||
import {
|
||||
tableUsageSnippet,
|
||||
tableBasicSnippet,
|
||||
tableSelectionActionsSnippet,
|
||||
tableSelectionModeSnippet,
|
||||
tableSelectionBehaviorSnippet,
|
||||
} from './snippets';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { TableDefinition } from '../utils/definitions';
|
||||
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={<TableSnippet story="TableRockBand" />}
|
||||
code={tableBasicSnippet}
|
||||
/>
|
||||
<Snippet py={4} preview={<TableRockBand />} code={tableBasicSnippet} />
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -100,7 +98,7 @@ Tables support row selection with two configuration options: selection mode and
|
||||
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={<TableSnippet story="SelectionModePlayground" />}
|
||||
preview={<SelectionModePlayground />}
|
||||
code={tableSelectionModeSnippet}
|
||||
/>
|
||||
|
||||
@@ -109,7 +107,7 @@ Use `selectionMode` to control how many rows can be selected. With `single`, onl
|
||||
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={<TableSnippet story="SelectionBehaviorPlayground" />}
|
||||
preview={<SelectionBehaviorPlayground />}
|
||||
code={tableSelectionBehaviorSnippet}
|
||||
/>
|
||||
|
||||
@@ -120,7 +118,7 @@ With toggle behavior, clicking a row triggers its action when nothing is selecte
|
||||
With replace behavior, clicking selects the row and double-clicking triggers the action.
|
||||
|
||||
<Snippet
|
||||
preview={<TableSnippet story="SelectionToggleWithActions" />}
|
||||
preview={<SelectionToggleWithActions />}
|
||||
code={tableSelectionActionsSnippet}
|
||||
/>
|
||||
|
||||
-109
@@ -266,112 +266,3 @@ export const tablePaginationPropDefs: Record<string, PropDef> = {
|
||||
...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,108 @@
|
||||
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,104 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
Tabs,
|
||||
TabList,
|
||||
Tab,
|
||||
TabPanel,
|
||||
} from '../../../../../packages/ui/src/components/Tabs';
|
||||
import { Text } from '../../../../../packages/ui/src/components/Text/Text';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="tab1">Tab 1</Tab>
|
||||
<Tab id="tab2">Tab 2</Tab>
|
||||
<Tab id="tab3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab1">
|
||||
<Text>Content for Tab 1</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab2">
|
||||
<Text>Content for Tab 2</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab3">
|
||||
<Text>Content for Tab 3</Text>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const DefaultSelectedKey = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Tabs defaultSelectedKey="tab2">
|
||||
<TabList>
|
||||
<Tab id="tab1">Tab 1</Tab>
|
||||
<Tab id="tab2">Tab 2</Tab>
|
||||
<Tab id="tab3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab1">
|
||||
<Text>Content for Tab 1</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab2">
|
||||
<Text>Content for Tab 2</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab3">
|
||||
<Text>Content for Tab 3</Text>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const DisabledTabs = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
<Tab id="tab1">Tab 1</Tab>
|
||||
<Tab id="tab2" isDisabled>
|
||||
Tab 2 (Disabled)
|
||||
</Tab>
|
||||
<Tab id="tab3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab1">
|
||||
<Text>Content for Tab 1</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab2">
|
||||
<Text>Content for Tab 2</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab3">
|
||||
<Text>Content for Tab 3</Text>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
export const Orientation = () => {
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<Tabs orientation="vertical">
|
||||
<TabList>
|
||||
<Tab id="tab1">Tab 1</Tab>
|
||||
<Tab id="tab2">Tab 2</Tab>
|
||||
<Tab id="tab3">Tab 3</Tab>
|
||||
</TabList>
|
||||
<TabPanel id="tab1">
|
||||
<Text>Content for Tab 1</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab2">
|
||||
<Text>Content for Tab 2</Text>
|
||||
</TabPanel>
|
||||
<TabPanel id="tab3">
|
||||
<Text>Content for Tab 3</Text>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import {
|
||||
tabsPropDefs,
|
||||
tabListPropDefs,
|
||||
tabPropDefs,
|
||||
tabPanelPropDefs,
|
||||
} from './props-definition';
|
||||
import {
|
||||
tabsUsageSnippet,
|
||||
defaultSnippet,
|
||||
defaultSelectedKeySnippet,
|
||||
disabledTabsSnippet,
|
||||
orientationSnippet,
|
||||
} from './snippets';
|
||||
import {
|
||||
Default,
|
||||
DefaultSelectedKey,
|
||||
DisabledTabs,
|
||||
Orientation,
|
||||
} from './components';
|
||||
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={defaultSnippet} />
|
||||
|
||||
## 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
|
||||
|
||||
### Default Selected Key
|
||||
|
||||
Here's how to set which tab is selected by default.
|
||||
|
||||
<Snippet
|
||||
py={4}
|
||||
preview={<DefaultSelectedKey />}
|
||||
code={defaultSelectedKeySnippet}
|
||||
open
|
||||
/>
|
||||
|
||||
### Disabled Tabs
|
||||
|
||||
Here's how to disable specific tabs.
|
||||
|
||||
<Snippet py={4} preview={<DisabledTabs />} code={disabledTabsSnippet} open />
|
||||
|
||||
### Orientation
|
||||
|
||||
Here's how to display tabs vertically.
|
||||
|
||||
<Snippet py={4} preview={<Orientation />} code={orientationSnippet} open />
|
||||
|
||||
<Theming definition={TabsDefinition} />
|
||||
|
||||
<ChangelogComponent component="tabs" />
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user