Merge remote-tracking branch 'origin/master' into techdocs-nfs

This commit is contained in:
Gabriel Dugny
2026-01-16 21:12:54 +01:00
368 changed files with 10313 additions and 4641 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-app-backend': patch
'@backstage/plugin-app-node': patch
---
Updated plugin metadata.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Added missing `aria-label` attributes to `SearchField` components in `Select`, `MenuAutocomplete`, and `MenuAutocompleteListbox` to fix accessibility warnings.
Affected components: Select, MenuAutocomplete, MenuAutocompleteListbox
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Updated documentation for `createApiRef` to clarify the role of the ID in specifying the owning plugin of an API.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/ui': patch
---
Fixes disabled state in primary and secondary buttons in Backstage UI.
+13
View File
@@ -0,0 +1,13 @@
---
'@backstage/plugin-app-react': patch
---
Moved the following blueprints from `@backstage/frontend-plugin-api`:
- `IconBundleBlueprint`
- `NavContentBlueprint`
- `RouterBlueprint`
- `SignInPageBlueprint`
- `SwappableComponentBlueprint`
- `ThemeBlueprint`
- `TranslationBlueprint`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Bumped create-app version.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Remove unused abort controller in debug:wait action
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-app-react': minor
---
Initial release of this web library for `@backstage/plugin-app`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
Clean up process event listeners on backend stop to prevent leaks
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/ui': patch
---
Added new Popover component for Backstage UI with automatic overflow handling, and full placement support. Also introduced `--bui-shadow` token for consistent elevation styling across overlay components (Popover, Tooltip, Menu).
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-catalog': patch
---
Header in EntityLayout should always be shown.
Monitoring the loading status caused flickering when the refresh() method of the Async Entity was invoked.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Fixed Table sorting indicator not being visible when a column is actively sorted.
Affected components: Table, Column
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/ui': patch
---
Fixed Menu component trigger button not toggling correctly. Removed custom click-outside handler that was interfering with React Aria's built-in state management, allowing the menu to properly open and close when clicking the trigger button.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-org': patch
---
Adds the Resource kind to the `useGetEntities` fallback so that Resources are included by default when no specific kinds are provided in the group ownership card.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Update colour token again in ItemCardHeader to respect theme definition.
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Added support for column width configuration in Table component. Columns now accept `width`, `defaultWidth`, `minWidth`, and `maxWidth` props for responsive layout control.
Affected components: Table, Column
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-mui-to-bui': patch
---
Fix installation command
+19 -1
View File
@@ -1,5 +1,5 @@
{
"mode": "pre",
"mode": "exit",
"tag": "next",
"initialVersions": {
"example-app": "0.2.116",
@@ -207,22 +207,40 @@
"@backstage/plugin-user-settings-common": "0.0.1"
},
"changesets": [
"action-filtering-feature",
"better-ghosts-tell",
"chilly-maps-heal",
"clever-streets-roll",
"cold-dogs-study",
"create-app-1766498726",
"create-app-1767101113",
"create-app-1768306186",
"dependabot-1e2091a",
"dependabot-aed8e01",
"early-boxes-work",
"easy-maps-appear",
"every-bears-add",
"every-lions-pay",
"few-teams-punch",
"fluffy-bushes-remain",
"hot-colts-float",
"hungry-mugs-fall",
"major-mangos-shine",
"mighty-flowers-travel",
"modern-drinks-battle",
"new-windows-reply",
"polite-pillows-accept",
"proud-streets-switch",
"quick-cars-poke",
"renovate-ccf3e66",
"rude-trains-marry",
"shaggy-bats-film",
"slick-laws-push",
"slow-clouds-tie",
"ten-vans-agree",
"thin-flies-travel",
"thin-teams-roll",
"tricky-tips-invite",
"twenty-candles-open",
"weak-lemons-create",
"wild-toys-retire"
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Added support for custom pagination options in `useTable` hook and `Table` component. You can now configure `pageSizeOptions` to customize the page size dropdown, and hook into pagination events via `onPageSizeChange`, `onNextPage`, and `onPreviousPage` callbacks. When `pageSize` doesn't match any option, the first option is used and a warning is logged.
Affected components: Table, TablePagination
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': minor
---
BREAKING: The ability for plugins to override APIs has been restricted to only allow overrides of APIs within the same plugin. For example, a plugin can no longer override any of the core APIs provided by the `app` plugin, this must be done with an `app` module instead.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/ui': minor
---
Introduce new `ToggleButton` & `ToggleButtonGroup` components in Backstage UI
+13
View File
@@ -0,0 +1,13 @@
---
'@backstage/frontend-plugin-api': patch
---
The following blueprints are being restricted to only be used in app plugin overrides and modules. They are being moved to the `@backstage/plugin-app-react` package and have been deprecated:
- `IconBundleBlueprint`
- `NavContentBlueprint`
- `RouterBlueprint`
- `SignInPageBlueprint`
- `SwappableComponentBlueprint`
- `ThemeBlueprint`
- `TranslationBlueprint`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Fixed help text for `backstage-repo-tools package schema openapi generate` command.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-auth-node': patch
'@backstage/cli': patch
---
Upgrade `zod-validation-error` to version 4
+7
View File
@@ -0,0 +1,7 @@
---
'@backstage/ui': patch
---
Added `className` and `style` props to the `Table` component.
Affected components: Table
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder': patch
---
Fixed bug in RepoUrlPickerComponent component where repository names were not being autocompleted.
+13
View File
@@ -0,0 +1,13 @@
---
'@backstage/plugin-app': patch
---
The following blueprints are being restricted to only be used in app plugin overrides and modules. They will now produce a deprecation warning when used outside of the app plugin:
- `IconBundleBlueprint`
- `NavContentBlueprint`
- `RouterBlueprint`
- `SignInPageBlueprint`
- `SwappableComponentBlueprint`
- `ThemeBlueprint`
- `TranslationBlueprint`
+20
View File
@@ -0,0 +1,20 @@
---
'@backstage/plugin-techdocs-node': minor
---
**BREAKING:** It's now possible to use the credentials from the `integrations.awsS3` config to authenticate with AWS S3. The new priority is:
1. `aws.accounts`
2. `techdocs.publisher.awsS3.credentials`
3. `integrations.awsS3`
4. Default credential chain
In case of multiple `integrations.awsS3` are present, the target integration is determined by the `accessKeyId` in `techdocs.publisher.awsS3.credentials` if provided. Otherwise, the default credential chain is used.
This means that depending on your setup, this feature may break your existing setup.
In general:
- if you are configuring `aws.accounts`, no action is required
- if you are configuring `techdocs.publisher.awsS3.credentials`, no action is required
- if you are configuring multiple integrations under `integrations.awsS3`, no action is required
- if you are configuring a single integration under `integrations.awsS3`, make sure that the integration has access to the bucket you are using for TechDocs
+14
View File
@@ -0,0 +1,14 @@
---
'@backstage/frontend-plugin-api': patch
'@backstage/backend-test-utils': patch
'@backstage/plugin-scaffolder-backend': patch
'@backstage/backend-defaults': patch
'@backstage/plugin-permission-common': patch
'@backstage/plugin-scaffolder-react': patch
'@backstage/plugin-permission-node': patch
'@backstage/plugin-scaffolder-node': patch
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-auth-node': patch
---
Upgrade `zod-to-json-schema` to latest version
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': patch
---
Updated WidgetOverlay color to use `alpha(theme.palette.background.paper, 0.93)` for better theme alignment instead of hardcoded RGBA
+2 -2
View File
@@ -10,7 +10,7 @@ yarn.lock @backstage/maintainers @backst
/.changeset @backstage/operations-maintainers
/.changeset/*.md
/.github @backstage/operations-maintainers
/.github/vale @backstage/documentation-maintainers
/.github/vale @backstage/maintainers @backstage/documentation-maintainers
/.patches @backstage/operations-maintainers
/beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers
/docs @backstage/maintainers @backstage/documentation-maintainers
@@ -25,7 +25,7 @@ yarn.lock @backstage/maintainers @backst
/docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers
/docs/releases @backstage/operations-maintainers
/docs/tooling @backstage/tooling-maintainers
/microsite @backstage/documentation-maintainers
/microsite @backstage/maintainers @backstage/documentation-maintainers
/microsite/data/plugins @backstage/maintainers
/microsite/static @backstage/maintainers @backstage/documentation-maintainers
/packages @backstage/framework-maintainers
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
egress-policy: audit
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.9'
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: setup python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.10'
+1 -1
View File
@@ -244,7 +244,7 @@ jobs:
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: 22.x
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
with:
python-version: '3.9'
+5 -6
View File
@@ -219,11 +219,12 @@
--bui-bg-solid: #1ed760;
--bui-bg-solid-hover: #3be477;
--bui-bg-solid-pressed: #1abc54;
--bui-bg-solid-disabled: var(--bui-gray-2);
--bui-bg-solid-disabled: #0f6c30;
--bui-fg-primary: var(--bui-black);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-solid: var(--bui-black);
--bui-fg-solid-disabled: #62ab7c;
--bui-border: var(--bui-gray-3);
--bui-border-hover: rgba(0, 0, 0, 0.3);
@@ -246,14 +247,12 @@
[data-theme-mode='dark'][data-theme-name='spotify'] {
--bui-bg-surface-0: var(--bui-black);
--bui-bg-solid: #1ed760;
--bui-bg-solid-hover: #3be477;
--bui-bg-solid-pressed: #1abc54;
--bui-bg-solid-disabled: #0f6c30;
--bui-bg-tint: #242424;
--bui-bg-tint-hover: #202020;
--bui-bg-tint-pressed: #292929;
--bui-bg-tint-disabled: rgba(255, 255, 255, 0.8);
--bui-bg-danger: #3b1219;
--bui-bg-warning: #302008;
--bui-bg-success: #132d21;
@@ -264,7 +263,7 @@
--bui-fg-link-hover: var(--bui-white);
--bui-fg-disabled: var(--bui-gray-5);
--bui-fg-solid: var(--bui-black);
--bui-fg-solid-disabled: #083618;
--bui-fg-solid-disabled: #072f15;
--bui-fg-tint: var(--bui-white);
--bui-fg-tint-disabled: var(--bui-gray-5);
--bui-fg-danger: #e22b2b;
-6
View File
@@ -189,15 +189,9 @@ catalog:
target: ../catalog-model/examples/acme-corp.yaml
rules:
- allow: [User, Group]
# Example component for github-actions and TechDocs
- type: file
target: ../../plugins/github-actions/examples/sample.yaml
# Example component for TechDocs
- type: file
target: ../../plugins/techdocs-backend/examples/documented-component/catalog-info.yaml
# Example component for ADRs
- type: file
target: ../../plugins/adr/examples/component/catalog-info.yaml
# Backstage example templates
- type: file
target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml
+1 -3
View File
@@ -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",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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&apos;s the edge of the world and all of Western civilization
</Text>
<Text as="p">
The sun may rise in the East, at least it settled in a final location
</Text>
<Text as="p">
It&apos;s understood that Hollywood sells Californication
</Text>
</Box>
);
export const Default = () => {
return (
<Accordion>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const WithSubtitle = () => {
return (
<Accordion>
<AccordionTrigger
title="Advanced Settings"
subtitle="Configure additional options"
/>
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const CustomTrigger = () => {
return (
<Accordion>
<AccordionTrigger>
<Box>
<Text as="div" variant="body-large" weight="bold">
Custom Multi-line Trigger
</Text>
<Text as="div" variant="body-medium" color="secondary">
Click to expand additional details and configuration options
</Text>
</Box>
</AccordionTrigger>
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const DefaultExpanded = () => {
return (
<Accordion defaultExpanded>
<AccordionTrigger title="Toggle Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
);
};
export const GroupSingleOpen = () => {
return (
<AccordionGroup>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Third Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
</AccordionGroup>
);
};
export const GroupMultipleOpen = () => {
return (
<AccordionGroup allowsMultiple>
<Accordion>
<AccordionTrigger title="First Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Second Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
<Accordion>
<AccordionTrigger title="Third Panel" />
<AccordionPanel>
<Content />
</AccordionPanel>
</Accordion>
</AccordionGroup>
);
};
@@ -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 &quot;Charles de
Dreuille&quot; to screen readers:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="informative"
/>
</Flex>
</Flex>
<Flex direction="column" gap="1">
<Text variant="title-x-small">Decoration</Text>
<Text variant="body-medium">
Use when avatar appears with adjacent text. Hidden from screen
readers:
</Text>
<Flex gap="2" align="center">
<Avatar
src="https://avatars.githubusercontent.com/u/1540635?v=4"
name="Charles de Dreuille"
purpose="decoration"
/>
<Text>Charles de Dreuille</Text>
</Flex>
</Flex>
</Flex>
);
};
@@ -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"
/>
);
};
@@ -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&apos;s a second paragraph that adds more content to our card
body. Having multiple paragraphs helps to visualize how spacing works
within the card component.
</Text>
<Text>
This third paragraph continues to add more text to ensure we have a
proper demonstration of a card with significant content. This makes it
easier to test scrolling behavior and overall layout when content
exceeds the initial view.
</Text>
</CardBody>
<CardFooter>
<Text>Footer</Text>
</CardFooter>
</Card>
);
};
@@ -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} />
@@ -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,
};
@@ -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,
};
@@ -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>
);
};
+41
View File
@@ -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>
);
@@ -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} />
@@ -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>
);
};
+92
View File
@@ -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>`;

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