Merge branch 'backstage:master' into feature/catalog-backend-module-bitbucket-server_-_revert-of-27850

This commit is contained in:
knowacki23
2025-04-15 08:25:08 +02:00
committed by GitHub
118 changed files with 3864 additions and 1129 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Add new breakpoint helpers up(), down() and current breakpoint to help you use our breakpoints in your React components.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
Improve class name structure using data attributes instead of class names.
+1 -1
View File
@@ -1,5 +1,5 @@
{
"mode": "pre",
"mode": "exit",
"tag": "next",
"initialVersions": {
"example-app": "0.2.107",
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': patch
---
Add new DataTable component and update Table component styles.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/repo-tools': patch
---
Checking up the files where `createRouter` has been declared and check if `@deprecated` tag exists. If it does not exist then only the message will appear.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': minor
---
Internal update to move the `new` and `create-github-app` to their own module.
+1 -1
View File
@@ -62,7 +62,7 @@ jobs:
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
- name: Build and push
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: './example-app'
file: ./example-app/packages/backend/Dockerfile
@@ -30,7 +30,7 @@ jobs:
run: |
mkdir -p ./pr
echo $PR_NUMBER > ./pr/pr_number
- uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: pr_number-${{ github.event.pull_request.number }}
path: pr/
+1 -1
View File
@@ -58,7 +58,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: 'Upload artifact'
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@@ -0,0 +1,84 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Tabs } from '@/components/Tabs';
import { CodeBlock } from '@/components/CodeBlock';
import {
MenuSnippet,
ButtonSnippet,
LinkSnippet,
} from '@/snippets/stories-snippets';
import { linkPropDefs } from './props';
# Link
A link component that renders a `<a>` element.
<Snippet
align="center"
py={4}
preview={<LinkSnippet story="Default" />}
code={`<Link href="https://backstage.io">Sign up for Backstage</Link>`}
/>
<Tabs.Root>
<Tabs.List>
<Tabs.Tab>Usage</Tabs.Tab>
<Tabs.Tab>Theming</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>
<CodeBlock
code={`import { Link } from '@backstage/canon';
<Link href="https://backstage.io">Sign up for Backstage</Link>`}
/>
</Tabs.Panel>
<Tabs.Panel>
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
your brand. For additional flexibility, you can use the provided class names for each element listed below.
- `canon-Link`
- `canon-Link[data-variant='body']`
- `canon-Link[data-variant='subtitle']`
- `canon-Link[data-variant='caption']`
- `canon-Link[data-variant='label']`
- `canon-Link[data-weight='regular']`
- `canon-Link[data-weight='bold']`
</Tabs.Panel>
</Tabs.Root>
## API reference
<PropsTable data={linkPropDefs} />
## Examples
### Variants
Here's a view when links have different variants.
<Snippet
align="center"
py={4}
open
preview={<LinkSnippet story="AllVariants" />}
code={`<Flex gap="4" direction="column">
<Link href="https://canon.backstage.io" variant="subtitle" />
<Link href="https://canon.backstage.io" variant="body" />
<Link href="https://canon.backstage.io" variant="caption" />
<Link href="https://canon.backstage.io" variant="label" />
</Flex>`}
/>
### Weights
Here's a view when links have different weights.
<Snippet
align="center"
py={4}
open
preview={<LinkSnippet story="AllWeights" />}
code={`<Flex gap="4" direction="column">
<Link href="https://canon.backstage.io" weight="regular" />
<Link href="https://canon.backstage.io" weight="bold" />
</Flex>`}
/>
@@ -0,0 +1,22 @@
import { classNamePropDefs, stylePropDefs } from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const linkPropDefs: Record<string, PropDef> = {
to: {
type: 'string',
},
variant: {
type: 'enum',
values: ['subtitle', 'body', 'caption', 'label'],
default: 'body',
responsive: true,
},
weight: {
type: 'enum',
values: ['regular', 'bold'],
default: 'regular',
responsive: true,
},
...classNamePropDefs,
...stylePropDefs,
};
@@ -0,0 +1,157 @@
import { PropsTable } from '@/components/PropsTable';
import { Snippet } from '@/components/Snippet';
import { Tabs } from '@/components/Tabs';
import { CodeBlock } from '@/components/CodeBlock';
import { MenuSnippet } from '@/snippets/stories-snippets';
import { BaseUI } from '@/components/HeadlessBanners/BaseUI';
import {
menuRootPropDefs,
menuTriggerPropDefs,
menuPositionerPropDefs,
menuItemPropDefs,
} from './props';
# Menu
A list of actions in a dropdown, enhanced with keyboard navigation.
<Snippet
align="center"
py={4}
preview={<MenuSnippet story="Default" />}
code={`<Menu.Root>
<Menu.Trigger
render={props => (
<Button
{...props}
size="medium"
variant="secondary"
iconEnd="chevron-down"
>
Menu
</Button>
)}
/>
<Menu.Portal>
<Menu.Positioner sideOffset={8} align="start">
<Menu.Popup>
<Menu.Item>Settings</Menu.Item>
<Menu.Item>Invite new members</Menu.Item>
<Menu.Item>Download app</Menu.Item>
<Menu.Item>Log out</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>`}
/>
<Tabs.Root>
<Tabs.List>
<Tabs.Tab>Usage</Tabs.Tab>
<Tabs.Tab>Theming</Tabs.Tab>
</Tabs.List>
<Tabs.Panel>
<CodeBlock
code={`import { Menu } from '@backstage/canon';
<Menu.Root>
<Menu.Trigger />
<Menu.Portal>
<Menu.Positioner>
<Menu.Popup>
<Menu.Item />
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>`}
/>
</Tabs.Panel>
<Tabs.Panel>
We recommend starting with our [global tokens](/theme/theming) to customize the library and align it with
your brand. For additional flexibility, you can use the provided class names for each element listed below.
- `canon-MenuTrigger`
- `canon-MenuBackdrop`
- `canon-MenuPositioner`
- `canon-MenuPopup`
- `canon-MenuArrow`
- `canon-MenuItem`
- `canon-MenuGroup`
- `canon-MenuGroupLabel`
- `canon-MenuRadioGroup`
- `canon-MenuRadioItem`
- `canon-MenuRadioItemIndicator`
- `canon-MenuCheckboxItem`
- `canon-MenuCheckboxItemIndicator`
- `canon-MenuSubmenuTrigger`
- `canon-MenuSeparator`
</Tabs.Panel>
</Tabs.Root>
## API reference
<BaseUI href="https://base-ui.com/react/components/menu" />
### Menu.Root
Groups all parts of the menu. Doesn't render its own HTML element.
<PropsTable data={menuRootPropDefs} />
### Menu.Trigger
The trigger by default render a simple unstyled button. Because menus can be rendered in different ways, we recommend
using the `render` prop to render a custom trigger.
<CodeBlock code={`<Menu.Trigger render={props => <Button {...props} />} />`} />
<PropsTable data={menuTriggerPropDefs} />
### Menu.Positioner
Positions the menu popup against the trigger. Renders a `<div>` element.
<PropsTable data={menuPositionerPropDefs} />
### Menu.Item
An individual interactive item in the menu. Renders a `<div>` element.
<PropsTable data={menuItemPropDefs} />
## Examples
### Open on hover
To create a menu that opens on hover, add the `openOnHover` prop to the Root part.
You can additionally configure how quickly the menu opens on hover using the `delay` prop.
<Snippet
align="center"
py={4}
open
preview={<MenuSnippet story="OpenOnHover" />}
code={`<Menu.Root openOnHover delay={100}>
<Menu.Trigger
render={props => (
<Button
{...props}
size="medium"
variant="secondary"
iconEnd="chevron-down"
>
Menu
</Button>
)}
/>
<Menu.Portal>
<Menu.Positioner sideOffset={8} align="start">
<Menu.Popup>
<Menu.Item>Settings</Menu.Item>
<Menu.Item>Invite new members</Menu.Item>
<Menu.Item>Download app</Menu.Item>
<Menu.Item>Log out</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
</Menu.Root>`}
/>
@@ -0,0 +1,142 @@
import {
classNamePropDefs,
stylePropDefs,
renderPropDefs,
} from '@/utils/propDefs';
import type { PropDef } from '@/utils/propDefs';
export const menuRootPropDefs: Record<string, PropDef> = {
defaultOpen: {
type: 'boolean',
default: 'false',
},
open: {
type: 'boolean',
},
onOpenChange: {
type: 'enum',
values: ['(open, event) => void'],
},
closeParentOnEsc: {
type: 'boolean',
default: 'true',
},
modal: {
type: 'boolean',
default: 'true',
},
onOpenChangeComplete: {
type: 'enum',
values: ['(open) => void'],
},
disabled: {
type: 'boolean',
default: 'false',
},
openOnHover: {
type: 'boolean',
},
delay: {
type: 'number',
default: '100',
},
loop: {
type: 'boolean',
default: 'true',
},
orientation: {
type: 'enum',
values: ['horizontal', 'vertical'],
default: 'vertical',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuTriggerPropDefs: Record<string, PropDef> = {
...renderPropDefs,
...classNamePropDefs,
...stylePropDefs,
};
export const menuPositionerPropDefs: Record<string, PropDef> = {
align: {
type: 'enum',
values: ['start', 'center', 'end'],
default: 'center',
},
alignOffset: {
type: 'enum',
values: ['number', '(data) => number'],
default: '0',
},
side: {
type: 'enum',
values: ['bottom', 'inline-end', 'inline-start', 'left', 'right', 'top'],
default: 'bottom',
},
sideOffset: {
type: 'enum',
values: ['number', '(data) => number'],
default: '0',
},
arrowPadding: {
type: 'number',
default: '5',
},
anchor: {
type: 'enum',
values: [
'React.Ref',
'Element',
'VirtualElement',
'(() => Element | VirtualElement | null)',
'null',
],
},
collisionBoundary: {
type: 'enum',
values: ['clipping-ancestors', 'Element', 'Element[]', 'Rect'],
default: 'clipping-ancestors',
},
collisionPadding: {
type: 'enum',
values: ['number', 'Rect'],
default: '5',
},
sticky: {
type: 'boolean',
default: 'false',
},
positionMethod: {
type: 'enum',
values: ['absolute', 'fixed'],
default: 'absolute',
},
trackAnchor: {
type: 'boolean',
default: 'true',
},
...classNamePropDefs,
...stylePropDefs,
};
export const menuItemPropDefs: Record<string, PropDef> = {
label: {
type: 'string',
},
onClick: {
type: 'enum',
values: ['(event) => void'],
},
closeOnClick: {
type: 'boolean',
default: 'true',
},
disabled: {
type: 'boolean',
default: 'false',
},
...classNamePropDefs,
...stylePropDefs,
};
+237 -95
View File
@@ -29,8 +29,100 @@ Here's an example of how your theme.css file should look like:
<CodeBlock lang="css" code={customTheme} />
## CSS class name structure
All Canon components come with a set of CSS classes that you can use to style them. To make it
easier to identify the class name you can use, we use a specific structure for the class names.
<img
src="/css-classname-structure.png"
style={{ width: '100%', marginBottom: '32px', marginTop: '16px' }}
/>
Every component has a unique prefix `.canon-` followed by the component name. Component props
are represented using the `data-` attribute. That way, class names are easily identifiable.
## Available CSS variables
### Base colors
These colors are used for special purposes like ring, scrollbar, ...
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-black</Chip>
</Table.Cell>
<Table.Cell>
Pure black color. This one should be the same in light and dark themes.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-white</Chip>
</Table.Cell>
<Table.Cell>
Pure white color. This one should be the same in light and dark themes.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-1</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-2</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-3</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-4</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-5</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-6</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-7</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-gray-8</Chip>
</Table.Cell>
<Table.Cell>You can use these mostly for backgrounds colors.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Core background colors
These colors are used for the background of your application. We are mostly using for now a
@@ -57,98 +149,60 @@ color of your app.
</Table.Cell>
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Component background colors
These colors are meants for interactive components like buttons, callout avatar, chips, ...
We have two main level of background you can customise. The accent one is mostly used as a primary option
for interactive components. The tint one is used for secondary components like callouts, avatars, ...
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-accent</Chip>
<Chip head>--canon-bg-surface-2</Chip>
</Table.Cell>
<Table.Cell>Used mostly for primary interactive components.</Table.Cell>
<Table.Cell>Use for any panels or elevated surfaces.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-accent-hover</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are hovered.
<Chip head>--canon-bg-solid</Chip>
</Table.Cell>
<Table.Cell>Used for solid background colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-accent-focus</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are in focus.
<Chip head>--canon-bg-solid-hover</Chip>
</Table.Cell>
<Table.Cell>Used for solid background colors when hovered.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-accent-disabled</Chip>
<Chip head>--canon-bg-solid-pressed</Chip>
</Table.Cell>
<Table.Cell>Used for solid background colors when pressed.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Used for interactive components when they are disabled.
<Chip head>--canon-bg-solid-disabled</Chip>
</Table.Cell>
<Table.Cell>Used for solid background colors when disabled.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-tint</Chip>
</Table.Cell>
<Table.Cell>Used for secondary interactive components.</Table.Cell>
<Table.Cell>Used for tint background colors.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-tint-hover</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are hovered.
</Table.Cell>
<Table.Cell>Used for tint background colors when hovered.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-tint-focus</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are active.
</Table.Cell>
<Table.Cell>Used for tint background colors when active.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-tint-disabled</Chip>
</Table.Cell>
<Table.Cell>
Used for interactive components when they are disabled.
</Table.Cell>
<Table.Cell>Used for tint background colors when disabled.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Status background colors
These backgrounds are intended for non-interactive elements to visually convey a specific state.
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-bg-danger</Chip>
@@ -189,7 +243,7 @@ are prefixed with `fg` to make it easier to identify.
<Chip head>--canon-fg-primary</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
@@ -197,7 +251,7 @@ are prefixed with `fg` to make it easier to identify.
<Chip head>--canon-fg-secondary</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
@@ -205,7 +259,7 @@ are prefixed with `fg` to make it easier to identify.
<Chip head>--canon-fg-link</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
@@ -213,38 +267,64 @@ are prefixed with `fg` to make it easier to identify.
<Chip head>--canon-fg-link-hover</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of `--canon-bg-app` or `--canon-bg-surface-1`.
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-accent</Chip>
<Chip head>--canon-fg-disabled</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of main background surfaces.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-solid</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of solid background colors.
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-accent`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-tint</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-tint`.</Table.Cell>
<Table.Cell>
It should be used on top of tint background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-tint-disabled</Chip>
</Table.Cell>
<Table.Cell>
It should be used on top of tint background colors when disabled.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-danger</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-danger`.</Table.Cell>
<Table.Cell>
It should be used on top of danger background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-warning</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-warning`.</Table.Cell>
<Table.Cell>
It should be used on top of warning background colors.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-fg-success</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-success`.</Table.Cell>
<Table.Cell>
It should be used on top of success background colors.
</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
@@ -288,21 +368,9 @@ low contrast to help as a separator with the different background colors.
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint</Chip>
<Chip head>--canon-border-disabled</Chip>
</Table.Cell>
<Table.Cell>It should be used on top of `--canon-bg-tint`.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint-hover</Chip>
</Table.Cell>
<Table.Cell>Used when the component is hovered.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-border-tint-focus</Chip>
</Table.Cell>
<Table.Cell>Used when the component is in focus.</Table.Cell>
<Table.Cell>Used when the component is disabled.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
@@ -325,7 +393,40 @@ low contrast to help as a separator with the different background colors.
</Table.Body>
</Table.Root>
## Font families
### Special colors
These colors are used for special purposes like ring, scrollbar, ...
<Table.Root>
<Table.Header>
<Table.HeaderRow>
<Table.HeaderCell>Prop</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.HeaderRow>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-ring</Chip>
</Table.Cell>
<Table.Cell>The color of the ring.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-scrollbar</Chip>
</Table.Cell>
<Table.Cell>The color of the scrollbar.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-scrollbar-thumb</Chip>
</Table.Cell>
<Table.Cell>The color of the scrollbar thumb.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
### Font families
We have two fonts that we use across Canon. The first one is the sans-serif
font that we use for the body of the application. The second one is the
@@ -354,9 +455,9 @@ monospace font that we use for code blocks and tables.
</Table.Body>
</Table.Root>
## Font weights
### Font weights
We have two font weights that we use across Canon. Regular or Bold
We have two font weights that we use across Canon. Regular or Bold.
<Table.Root>
<Table.Header>
@@ -381,7 +482,7 @@ We have two font weights that we use across Canon. Regular or Bold
</Table.Body>
</Table.Root>
## Spacing
### Spacing
We built a spacing system based on a single value `--canon-space`. This value is
used to calculate the spacing for all the components. By default if you would like to
@@ -520,18 +621,9 @@ tokens for pretty much each spacing properties like padding, margin, gaps, ...
</Table.Body>
</Table.Root>
### Radius (Work in progress)
### Radius
Our radius system is a little bit different from spacing as we use a factor to calculate the radius values.
Under the hood different components uses different radius values. For example, button can be set to have full radius
while a checkbox will have to remain a square.
Here are the different radius factor we recommend to use
- None: `0`
- Small: `0.75`
- Medium: `1`
- Large: `1.5`
We use a radius system to make sure that the components have a consistent look and feel.
<Table.Root>
<Table.Header>
@@ -543,9 +635,59 @@ Here are the different radius factor we recommend to use
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-factor</Chip>
<Chip head>--canon-radius-1</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `0.125rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-2</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `0.25rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-3</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `0.5rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-4</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `0.75rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-5</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `1rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-6</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `1.25rem`.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-radius-full</Chip>
</Table.Cell>
<Table.Cell>
The radius of the component. Default value is `9999px`.
</Table.Cell>
<Table.Cell>By default the value is set to medium `1`.</Table.Cell>
</Table.Row>
</Table.Body>
</Table.Root>
@@ -13,6 +13,8 @@ import * as TextFieldStories from '../../../packages/canon/src/components/TextFi
import * as TextStories from '../../../packages/canon/src/components/Text/Text.stories';
import * as FlexStories from '../../../packages/canon/src/components/Flex/Flex.stories';
import * as SelectStories from '../../../packages/canon/src/components/Select/Select.stories';
import * as MenuStories from '../../../packages/canon/src/components/Menu/Menu.stories';
import * as LinkStories from '../../../packages/canon/src/components/Link/Link.stories';
export const BoxSnippet = ({ story }: { story: keyof typeof BoxStories }) => {
const stories = composeStories(BoxStories);
@@ -125,3 +127,17 @@ export const SelectSnippet = ({
return StoryComponent ? <StoryComponent /> : null;
};
export const MenuSnippet = ({ story }: { story: keyof typeof MenuStories }) => {
const stories = composeStories(MenuStories);
const StoryComponent = stories[story as keyof typeof stories];
return StoryComponent ? <StoryComponent /> : null;
};
export const LinkSnippet = ({ story }: { story: keyof typeof LinkStories }) => {
const stories = composeStories(LinkStories);
const StoryComponent = stories[story as keyof typeof stories];
return StoryComponent ? <StoryComponent /> : null;
};
+10
View File
@@ -91,6 +91,16 @@ export const components: Page[] = [
slug: 'icon-button',
status: 'alpha',
},
{
title: 'Link',
slug: 'link',
status: 'alpha',
},
{
title: 'Menu',
slug: 'menu',
status: 'alpha',
},
{
title: 'Select',
slug: 'select',
+8
View File
@@ -203,3 +203,11 @@ export const childrenPropDefs: Record<string, PropDef> = {
responsive: false,
},
};
export const renderPropDefs: Record<string, PropDef> = {
render: {
type: 'enum',
values: ['React.ReactElement', '(props, state) => React.ReactElement'],
responsive: false,
},
};
+9 -9
View File
@@ -916,11 +916,11 @@ __metadata:
linkType: hard
"@types/node@npm:^20":
version: 20.17.24
resolution: "@types/node@npm:20.17.24"
version: 20.17.30
resolution: "@types/node@npm:20.17.30"
dependencies:
undici-types: "npm:~6.19.2"
checksum: 10/0c5fe294b003656b0f95ffce295c08e7ed3fa814ba9bf891e146dced90322d186e63837db9d9bb14d4f294fbe1a6fa1c89ced0b173380c8aaeb90de6c3eddb3b
checksum: 10/69fd3b177417be77b459e8f1dd4e78c85c686167086920fbf35a9fda301709bbeee6a87ad2591fb1ddd96c65e725ec6bb527a06496626a1c94367d1361048f8d
languageName: node
linkType: hard
@@ -5410,22 +5410,22 @@ __metadata:
linkType: hard
"typescript@npm:^5":
version: 5.8.2
resolution: "typescript@npm:5.8.2"
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/dbc2168a55d56771f4d581997be52bab5cbc09734fec976cfbaabd787e61fb4c6cf9125fd48c6f98054ce549c77ecedefc7f64252a830dd8e9c3381f61fbeb78
checksum: 10/65c40944c51b513b0172c6710ee62e951b70af6f75d5a5da745cb7fab132c09ae27ffdf7838996e3ed603bb015dadd099006658046941bd0ba30340cc563ae92
languageName: node
linkType: hard
"typescript@patch:typescript@npm%3A^5#optional!builtin<compat/typescript>":
version: 5.8.2
resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin<compat/typescript>::version=5.8.2&hash=5786d5"
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#optional!builtin<compat/typescript>::version=5.8.3&hash=5786d5"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 10/97920a082ffc57583b1cb6bc4faa502acc156358e03f54c7fc7fdf0b61c439a717f4c9070c449ee9ee683d4cfc3bb203127c2b9794b2950f66d9d307a4ff262c
checksum: 10/b9b1e73dabac5dc730c041325dbd9c99467c1b0d239f1b74ec3b90d831384af3e2ba973946232df670519147eb51a2c20f6f96163cea2b359f03de1e2091cc4f
languageName: node
linkType: hard
+52 -52
View File
@@ -2961,94 +2961,94 @@ __metadata:
languageName: node
linkType: hard
"@swc/core-darwin-arm64@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-darwin-arm64@npm:1.11.10"
"@swc/core-darwin-arm64@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-darwin-arm64@npm:1.11.21"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
"@swc/core-darwin-x64@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-darwin-x64@npm:1.11.10"
"@swc/core-darwin-x64@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-darwin-x64@npm:1.11.21"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
"@swc/core-linux-arm-gnueabihf@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.10"
"@swc/core-linux-arm-gnueabihf@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-linux-arm-gnueabihf@npm:1.11.21"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
"@swc/core-linux-arm64-gnu@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-linux-arm64-gnu@npm:1.11.10"
"@swc/core-linux-arm64-gnu@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-linux-arm64-gnu@npm:1.11.21"
conditions: os=linux & cpu=arm64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-arm64-musl@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-linux-arm64-musl@npm:1.11.10"
"@swc/core-linux-arm64-musl@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-linux-arm64-musl@npm:1.11.21"
conditions: os=linux & cpu=arm64 & libc=musl
languageName: node
linkType: hard
"@swc/core-linux-x64-gnu@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-linux-x64-gnu@npm:1.11.10"
"@swc/core-linux-x64-gnu@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-linux-x64-gnu@npm:1.11.21"
conditions: os=linux & cpu=x64 & libc=glibc
languageName: node
linkType: hard
"@swc/core-linux-x64-musl@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-linux-x64-musl@npm:1.11.10"
"@swc/core-linux-x64-musl@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-linux-x64-musl@npm:1.11.21"
conditions: os=linux & cpu=x64 & libc=musl
languageName: node
linkType: hard
"@swc/core-win32-arm64-msvc@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-win32-arm64-msvc@npm:1.11.10"
"@swc/core-win32-arm64-msvc@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-win32-arm64-msvc@npm:1.11.21"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
"@swc/core-win32-ia32-msvc@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-win32-ia32-msvc@npm:1.11.10"
"@swc/core-win32-ia32-msvc@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-win32-ia32-msvc@npm:1.11.21"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
"@swc/core-win32-x64-msvc@npm:1.11.10":
version: 1.11.10
resolution: "@swc/core-win32-x64-msvc@npm:1.11.10"
"@swc/core-win32-x64-msvc@npm:1.11.21":
version: 1.11.21
resolution: "@swc/core-win32-x64-msvc@npm:1.11.21"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
"@swc/core@npm:^1.3.46":
version: 1.11.10
resolution: "@swc/core@npm:1.11.10"
version: 1.11.21
resolution: "@swc/core@npm:1.11.21"
dependencies:
"@swc/core-darwin-arm64": "npm:1.11.10"
"@swc/core-darwin-x64": "npm:1.11.10"
"@swc/core-linux-arm-gnueabihf": "npm:1.11.10"
"@swc/core-linux-arm64-gnu": "npm:1.11.10"
"@swc/core-linux-arm64-musl": "npm:1.11.10"
"@swc/core-linux-x64-gnu": "npm:1.11.10"
"@swc/core-linux-x64-musl": "npm:1.11.10"
"@swc/core-win32-arm64-msvc": "npm:1.11.10"
"@swc/core-win32-ia32-msvc": "npm:1.11.10"
"@swc/core-win32-x64-msvc": "npm:1.11.10"
"@swc/core-darwin-arm64": "npm:1.11.21"
"@swc/core-darwin-x64": "npm:1.11.21"
"@swc/core-linux-arm-gnueabihf": "npm:1.11.21"
"@swc/core-linux-arm64-gnu": "npm:1.11.21"
"@swc/core-linux-arm64-musl": "npm:1.11.21"
"@swc/core-linux-x64-gnu": "npm:1.11.21"
"@swc/core-linux-x64-musl": "npm:1.11.21"
"@swc/core-win32-arm64-msvc": "npm:1.11.21"
"@swc/core-win32-ia32-msvc": "npm:1.11.21"
"@swc/core-win32-x64-msvc": "npm:1.11.21"
"@swc/counter": "npm:^0.1.3"
"@swc/types": "npm:^0.1.19"
"@swc/types": "npm:^0.1.21"
peerDependencies:
"@swc/helpers": "*"
"@swc/helpers": ">=0.5.17"
dependenciesMeta:
"@swc/core-darwin-arm64":
optional: true
@@ -3073,7 +3073,7 @@ __metadata:
peerDependenciesMeta:
"@swc/helpers":
optional: true
checksum: 10/4fcda5222fe4311dd6fe07586c72458f86a3a7ccee09cc7115ff3308f12d84a019d0b6996bcc778014f30f9a683de2d94a21498f69f8ed53941912c851716572
checksum: 10/98a0f201a0a4aa026b0b07c61c8da49d94d4ac3b059b466416c90b9b2cb4f457d836ae55ccfe90b0a89c7bdde36526ba5b7747c4e616c0d556c733e3728e9dd4
languageName: node
linkType: hard
@@ -3084,12 +3084,12 @@ __metadata:
languageName: node
linkType: hard
"@swc/types@npm:^0.1.19":
version: 0.1.19
resolution: "@swc/types@npm:0.1.19"
"@swc/types@npm:^0.1.21":
version: 0.1.21
resolution: "@swc/types@npm:0.1.21"
dependencies:
"@swc/counter": "npm:^0.1.3"
checksum: 10/693147cc9b23147164ddff9cb89477c369fbeb103319584779352a9ff1c72e0a70b97a89dfd97629040db8956d668d7b7a8fed328ffea46a3e8c18577e396994
checksum: 10/6554bf5c78519f49099a2ba448d170191a14b1c7a35df848f10ee4d6c03ecd681e5213884905187de1d1d221589ec8b5cb77f477d099dc1627c3ec9d7f2fcdb0
languageName: node
linkType: hard
@@ -5942,11 +5942,11 @@ __metadata:
linkType: hard
"docusaurus-pushfeedback@npm:^1.0.0":
version: 1.0.1
resolution: "docusaurus-pushfeedback@npm:1.0.1"
version: 1.0.3
resolution: "docusaurus-pushfeedback@npm:1.0.3"
peerDependencies:
"@docusaurus/core": 3.x
checksum: 10/5160e33eded0c4bec51fba986561c33b69eee46799d69051188990d66353e43e49d826f5e44c9f73a67d0df36aa03ee7a3679b6d0cd6b0d9fed5f0f78fe4a1a3
checksum: 10/ee80ae0c1fc079b2c317cab86d83ba50cce18938a6ce0ac647aad25dc68fc0ad659e55c11e93d0e7c23270c503c3cb300111dfbdd04785ef7aaf6fb123b57eae
languageName: node
linkType: hard
+6 -6
View File
@@ -16,7 +16,7 @@
}
}
.canon-Button--variant-primary {
.canon-Button[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -40,7 +40,7 @@
}
}
.canon-Button--variant-secondary {
.canon-Button[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -66,24 +66,24 @@
}
}
.canon-Button--size-medium {
.canon-Button[data-size="medium"] {
font-size: var(--canon-font-size-4);
padding: 0 var(--canon-space-3);
height: 40px;
}
.canon-Button--size-small {
.canon-Button[data-size="small"] {
font-size: var(--canon-font-size-3);
padding: 0 var(--canon-space-2);
height: 32px;
}
.canon-Button--size-small .canon-Button--icon {
.canon-Button[data-size="small"] .canon-ButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-Button--size-medium .canon-Button--icon {
.canon-Button[data-size="medium"] .canon-ButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
+123 -116
View File
@@ -22,7 +22,7 @@
}
}
.canon-Button--variant-primary {
.canon-Button[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -46,7 +46,7 @@
}
}
.canon-Button--variant-secondary {
.canon-Button[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -72,28 +72,58 @@
}
}
.canon-Button--size-medium {
.canon-Button[data-size="medium"] {
font-size: var(--canon-font-size-4);
padding: 0 var(--canon-space-3);
height: 40px;
}
.canon-Button--size-small {
.canon-Button[data-size="small"] {
font-size: var(--canon-font-size-3);
padding: 0 var(--canon-space-2);
height: 32px;
}
.canon-Button--size-small .canon-Button--icon {
.canon-Button[data-size="small"] .canon-ButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-Button--size-medium .canon-Button--icon {
.canon-Button[data-size="medium"] .canon-ButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
.canon-DataTableRoot {
gap: var(--canon-space-3);
flex-direction: column;
display: flex;
}
.canon-DataTablePagination {
padding-top: var(--canon-space-5);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
.canon-DataTablePagination--select {
min-width: 10.5rem;
}
.canon-Flex {
display: flex;
}
@@ -179,117 +209,94 @@
display: flex;
}
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
.canon-TableRoot {
caption-side: bottom;
border-collapse: collapse;
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
& table {
caption-side: bottom;
width: 100%;
font-size: var(--canon-font-size-sm);
border-collapse: collapse;
}
}
.table-head {
.canon-TableHead {
text-align: left;
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
}
.table-body {
& tr:last-child {
border-bottom: none;
}
.canon-TableBody {
color: var(--canon-fg-primary);
}
.table-row {
.canon-TableRow {
border-bottom: 1px solid var(--canon-border);
transition: color .2s ease-in-out;
&:hover td {
background-color: var(--canon-bg-tint);
}
& .table-cell:first-child {
border-top-left-radius: var(--canon-radius-2);
border-bottom-left-radius: var(--canon-radius-2);
box-shadow: inset 4px 2px 0 0 var(--canon-bg-surface-1), inset 4px -2px 0 0 var(--canon-bg-surface-1);
padding-left: var(--canon-space-3);
}
& .table-cell:last-child {
border-top-right-radius: var(--canon-radius-2);
border-bottom-right-radius: var(--canon-radius-2);
box-shadow: inset -4px 2px 0 0 var(--canon-bg-surface-1), inset -4px -2px 0 0 var(--canon-bg-surface-1);
padding-right: var(--canon-space-3);
}
}
.table-cell {
.canon-TableBody .canon-TableRow:hover {
background-color: var(--canon-gray-2);
}
.canon-TableCell {
padding: var(--canon-space-3);
background-color: var(--canon-bg);
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1), inset 0px -2px 0 0 var(--canon-bg-surface-1);
font-size: var(--canon-font-size-3);
}
.canon-TableCellText, .canon-TableCellLink {
gap: var(--canon-space-0_5);
flex-direction: column;
display: flex;
}
.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-fg-primary);
margin: 0;
padding: 0;
}
.canon-Text--variant-body {
.canon-Text[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Text--variant-subtitle {
.canon-Text[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Text--variant-caption {
.canon-Text[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Text--variant-label {
.canon-Text[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Text--weight-regular {
.canon-Text[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Text--weight-bold {
.canon-Text[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
.canon-Text--color-primary {
.canon-Text[data-color="primary"] {
color: var(--canon-fg-primary);
}
.canon-Text--color-secondary {
.canon-Text[data-color="secondary"] {
color: var(--canon-fg-secondary);
}
.canon-Text--color-danger {
.canon-Text[data-color="danger"] {
color: var(--canon-fg-danger);
}
.canon-Text--color-warning {
.canon-Text[data-color="warning"] {
color: var(--canon-fg-warning);
}
.canon-Text--color-success {
.canon-Text[data-color="success"] {
color: var(--canon-fg-success);
}
@@ -301,32 +308,32 @@
line-height: 100%;
}
.canon-Heading--variant-display {
.canon-Heading[data-variant="display"] {
font-size: var(--canon-font-size-10);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title1 {
.canon-Heading[data-variant="title1"] {
font-size: var(--canon-font-size-9);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title2 {
.canon-Heading[data-variant="title2"] {
font-size: var(--canon-font-size-8);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title3 {
.canon-Heading[data-variant="title3"] {
font-size: var(--canon-font-size-7);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title4 {
.canon-Heading[data-variant="title4"] {
font-size: var(--canon-font-size-title4);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title5 {
.canon-Heading[data-variant="title5"] {
font-size: var(--canon-font-size-5);
font-weight: var(--canon-font-weight-bold);
}
@@ -349,7 +356,7 @@
}
}
.canon-IconButton--variant-primary {
.canon-IconButton[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -373,7 +380,7 @@
}
}
.canon-IconButton--variant-secondary {
.canon-IconButton[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -399,24 +406,24 @@
}
}
.canon-IconButton--size-medium {
.canon-IconButton[data-size="medium"] {
font-size: var(--canon-font-size-4);
width: 40px;
height: 40px;
}
.canon-IconButton--size-small {
.canon-IconButton[data-size="small"] {
font-size: var(--canon-font-size-3);
width: 32px;
height: 32px;
}
.canon-IconButton--size-small .canon-IconButton--icon {
.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-IconButton--size-medium .canon-IconButton--icon {
.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
@@ -428,14 +435,14 @@
display: flex;
}
.canon-TextField--label {
.canon-TextFieldLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-TextField--description {
.canon-TextFieldDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -443,7 +450,7 @@
margin: 0;
}
.canon-TextField--error {
.canon-TextFieldError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -451,7 +458,7 @@
margin: 0;
}
.canon-TextField--input {
.canon-TextFieldInput {
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
padding: 0 var(--canon-space-4);
@@ -464,39 +471,39 @@
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
}
.canon-TextField--input::placeholder {
.canon-TextFieldInput::placeholder {
color: var(--canon-fg-secondary);
}
.canon-TextField--input:hover {
.canon-TextFieldInput:hover {
border-color: var(--canon-border-hover);
}
.canon-TextField--input:focus-visible {
.canon-TextFieldInput:focus-visible {
outline-color: var(--canon-border-pressed);
border-color: var(--canon-border-pressed);
outline-width: 0;
}
.canon-TextField--input[data-invalid] {
.canon-TextFieldInput[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-TextField--input[data-disabled] {
.canon-TextFieldInput[data-disabled] {
opacity: .5;
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextField--input-size-small {
.canon-TextFieldInput[data-size="small"] {
height: 2rem;
}
.canon-TextField--input-size-medium {
.canon-TextFieldInput[data-size="medium"] {
height: 2.5rem;
}
.canon-TextField--required {
.canon-TextFieldRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
@@ -573,31 +580,31 @@
}
}
.canon-Link--variant-body {
.canon-Link[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Link--variant-subtitle {
.canon-Link[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Link--variant-caption {
.canon-Link[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Link--variant-label {
.canon-Link[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Link--weight-regular {
.canon-Link[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Link--weight-bold {
.canon-Link[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
@@ -721,14 +728,14 @@
display: flex;
}
.canon-Select--label {
.canon-SelectLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-Select--description {
.canon-SelectDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -736,7 +743,7 @@
margin: 0;
}
.canon-Select--error {
.canon-SelectError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -744,7 +751,7 @@
margin: 0;
}
.canon-Select--trigger {
.canon-SelectTrigger {
box-sizing: border-box;
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
@@ -763,38 +770,38 @@
display: flex;
}
.canon-Select--trigger::placeholder {
.canon-SelectTrigger::placeholder {
color: var(--canon-fg-secondary);
}
.canon-Select--trigger:hover {
.canon-SelectTrigger:hover {
border-color: var(--canon-border-hover);
}
.canon-Select--trigger:focus-visible {
.canon-SelectTrigger:focus-visible {
border-color: var(--canon-border-pressed);
outline: 0;
}
.canon-Select--trigger[data-invalid] {
.canon-SelectTrigger[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-Select--trigger[data-invalid]:hover, .canon-Select--trigger[data-invalid]:focus-visible {
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
border-width: 2px;
}
.canon-Select--trigger[data-disabled] {
.canon-SelectTrigger[data-disabled] {
cursor: not-allowed;
border-color: var(--canon-border-disabled);
color: var(--canon-fg-disabled);
}
.canon-Select--trigger-size-small {
.canon-SelectTrigger[data-size="small"] {
height: 2rem;
}
.canon-Select--trigger-size-medium {
.canon-SelectTrigger[data-size="medium"] {
height: 3rem;
}
@@ -803,11 +810,11 @@
transition: transform .2s;
}
.canon-Select--trigger[data-popup-open] .canon-SelectIcon {
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
transform: rotate(180deg);
}
.canon-Select--popup {
.canon-SelectPopup {
box-sizing: border-box;
max-height: var(--available-height);
background-color: var(--canon-bg-surface-1);
@@ -822,12 +829,12 @@
box-shadow: 0 4px 12px #0003;
}
.canon-Select--popup[data-starting-style], .canon-Select--popup[data-ending-style] {
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
opacity: 0;
transform: scale(.9);
}
.canon-Select--item {
.canon-SelectItem {
width: var(--anchor-width);
padding-block: var(--canon-space-2);
padding-inline: var(--canon-space-4);
@@ -845,13 +852,13 @@
position: relative;
}
.canon-Select--item[data-highlighted] {
.canon-SelectItem[data-highlighted] {
z-index: 0;
color: var(--canon-fg-primary);
position: relative;
}
.canon-Select--item[data-highlighted]:before {
.canon-SelectItem[data-highlighted]:before {
content: "";
z-index: -1;
background-color: var(--canon-bg-tint-hover);
@@ -861,37 +868,37 @@
inset-inline: .25rem;
}
.canon-Select--item[data-disabled] {
.canon-SelectItem[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
}
.canon-Select--item-indicator {
.canon-SelectItemIndicator {
grid-area: icon;
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--item-text {
.canon-SelectItemText {
flex: 1;
grid-area: text;
}
.canon-Select--required {
.canon-SelectRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-left: var(--canon-space-1);
}
.canon-Select--icon {
.canon-SelectIcon {
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--value {
.canon-SelectValue {
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
+23
View File
@@ -0,0 +1,23 @@
.canon-DataTablePagination {
padding-top: var(--canon-space-5);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
.canon-DataTablePagination--select {
min-width: 10.5rem;
}
+6 -6
View File
@@ -6,32 +6,32 @@
line-height: 100%;
}
.canon-Heading--variant-display {
.canon-Heading[data-variant="display"] {
font-size: var(--canon-font-size-10);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title1 {
.canon-Heading[data-variant="title1"] {
font-size: var(--canon-font-size-9);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title2 {
.canon-Heading[data-variant="title2"] {
font-size: var(--canon-font-size-8);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title3 {
.canon-Heading[data-variant="title3"] {
font-size: var(--canon-font-size-7);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title4 {
.canon-Heading[data-variant="title4"] {
font-size: var(--canon-font-size-title4);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title5 {
.canon-Heading[data-variant="title5"] {
font-size: var(--canon-font-size-5);
font-weight: var(--canon-font-weight-bold);
}
+6 -6
View File
@@ -16,7 +16,7 @@
}
}
.canon-IconButton--variant-primary {
.canon-IconButton[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -40,7 +40,7 @@
}
}
.canon-IconButton--variant-secondary {
.canon-IconButton[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -66,24 +66,24 @@
}
}
.canon-IconButton--size-medium {
.canon-IconButton[data-size="medium"] {
font-size: var(--canon-font-size-4);
width: 40px;
height: 40px;
}
.canon-IconButton--size-small {
.canon-IconButton[data-size="small"] {
font-size: var(--canon-font-size-3);
width: 32px;
height: 32px;
}
.canon-IconButton--size-small .canon-IconButton--icon {
.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-IconButton--size-medium .canon-IconButton--icon {
.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
+6 -6
View File
@@ -16,30 +16,30 @@
}
}
.canon-Link--variant-body {
.canon-Link[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Link--variant-subtitle {
.canon-Link[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Link--variant-caption {
.canon-Link[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Link--variant-label {
.canon-Link[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Link--weight-regular {
.canon-Link[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Link--weight-bold {
.canon-Link[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
+24 -24
View File
@@ -5,14 +5,14 @@
display: flex;
}
.canon-Select--label {
.canon-SelectLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-Select--description {
.canon-SelectDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -20,7 +20,7 @@
margin: 0;
}
.canon-Select--error {
.canon-SelectError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -28,7 +28,7 @@
margin: 0;
}
.canon-Select--trigger {
.canon-SelectTrigger {
box-sizing: border-box;
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
@@ -47,38 +47,38 @@
display: flex;
}
.canon-Select--trigger::placeholder {
.canon-SelectTrigger::placeholder {
color: var(--canon-fg-secondary);
}
.canon-Select--trigger:hover {
.canon-SelectTrigger:hover {
border-color: var(--canon-border-hover);
}
.canon-Select--trigger:focus-visible {
.canon-SelectTrigger:focus-visible {
border-color: var(--canon-border-pressed);
outline: 0;
}
.canon-Select--trigger[data-invalid] {
.canon-SelectTrigger[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-Select--trigger[data-invalid]:hover, .canon-Select--trigger[data-invalid]:focus-visible {
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
border-width: 2px;
}
.canon-Select--trigger[data-disabled] {
.canon-SelectTrigger[data-disabled] {
cursor: not-allowed;
border-color: var(--canon-border-disabled);
color: var(--canon-fg-disabled);
}
.canon-Select--trigger-size-small {
.canon-SelectTrigger[data-size="small"] {
height: 2rem;
}
.canon-Select--trigger-size-medium {
.canon-SelectTrigger[data-size="medium"] {
height: 3rem;
}
@@ -87,11 +87,11 @@
transition: transform .2s;
}
.canon-Select--trigger[data-popup-open] .canon-SelectIcon {
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
transform: rotate(180deg);
}
.canon-Select--popup {
.canon-SelectPopup {
box-sizing: border-box;
max-height: var(--available-height);
background-color: var(--canon-bg-surface-1);
@@ -106,12 +106,12 @@
box-shadow: 0 4px 12px #0003;
}
.canon-Select--popup[data-starting-style], .canon-Select--popup[data-ending-style] {
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
opacity: 0;
transform: scale(.9);
}
.canon-Select--item {
.canon-SelectItem {
width: var(--anchor-width);
padding-block: var(--canon-space-2);
padding-inline: var(--canon-space-4);
@@ -129,13 +129,13 @@
position: relative;
}
.canon-Select--item[data-highlighted] {
.canon-SelectItem[data-highlighted] {
z-index: 0;
color: var(--canon-fg-primary);
position: relative;
}
.canon-Select--item[data-highlighted]:before {
.canon-SelectItem[data-highlighted]:before {
content: "";
z-index: -1;
background-color: var(--canon-bg-tint-hover);
@@ -145,37 +145,37 @@
inset-inline: .25rem;
}
.canon-Select--item[data-disabled] {
.canon-SelectItem[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
}
.canon-Select--item-indicator {
.canon-SelectItemIndicator {
grid-area: icon;
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--item-text {
.canon-SelectItemText {
flex: 1;
grid-area: text;
}
.canon-Select--required {
.canon-SelectRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-left: var(--canon-space-1);
}
.canon-Select--icon {
.canon-SelectIcon {
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--value {
.canon-SelectValue {
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
+123 -116
View File
@@ -9246,7 +9246,7 @@
}
}
.canon-Button--variant-primary {
.canon-Button[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -9270,7 +9270,7 @@
}
}
.canon-Button--variant-secondary {
.canon-Button[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -9296,28 +9296,58 @@
}
}
.canon-Button--size-medium {
.canon-Button[data-size="medium"] {
font-size: var(--canon-font-size-4);
padding: 0 var(--canon-space-3);
height: 40px;
}
.canon-Button--size-small {
.canon-Button[data-size="small"] {
font-size: var(--canon-font-size-3);
padding: 0 var(--canon-space-2);
height: 32px;
}
.canon-Button--size-small .canon-Button--icon {
.canon-Button[data-size="small"] .canon-ButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-Button--size-medium .canon-Button--icon {
.canon-Button[data-size="medium"] .canon-ButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
.canon-DataTableRoot {
gap: var(--canon-space-3);
flex-direction: column;
display: flex;
}
.canon-DataTablePagination {
padding-top: var(--canon-space-5);
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--left {
justify-content: space-between;
align-items: center;
display: flex;
}
.canon-DataTablePagination--right {
justify-content: space-between;
align-items: center;
gap: var(--canon-space-2);
display: flex;
}
.canon-DataTablePagination--select {
min-width: 10.5rem;
}
.canon-Flex {
display: flex;
}
@@ -9403,117 +9433,94 @@
display: flex;
}
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
.canon-TableRoot {
caption-side: bottom;
border-collapse: collapse;
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
& table {
caption-side: bottom;
width: 100%;
font-size: var(--canon-font-size-sm);
border-collapse: collapse;
}
}
.table-head {
.canon-TableHead {
text-align: left;
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
}
.table-body {
& tr:last-child {
border-bottom: none;
}
.canon-TableBody {
color: var(--canon-fg-primary);
}
.table-row {
.canon-TableRow {
border-bottom: 1px solid var(--canon-border);
transition: color .2s ease-in-out;
&:hover td {
background-color: var(--canon-bg-tint);
}
& .table-cell:first-child {
border-top-left-radius: var(--canon-radius-2);
border-bottom-left-radius: var(--canon-radius-2);
box-shadow: inset 4px 2px 0 0 var(--canon-bg-surface-1), inset 4px -2px 0 0 var(--canon-bg-surface-1);
padding-left: var(--canon-space-3);
}
& .table-cell:last-child {
border-top-right-radius: var(--canon-radius-2);
border-bottom-right-radius: var(--canon-radius-2);
box-shadow: inset -4px 2px 0 0 var(--canon-bg-surface-1), inset -4px -2px 0 0 var(--canon-bg-surface-1);
padding-right: var(--canon-space-3);
}
}
.table-cell {
.canon-TableBody .canon-TableRow:hover {
background-color: var(--canon-gray-2);
}
.canon-TableCell {
padding: var(--canon-space-3);
background-color: var(--canon-bg);
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1), inset 0px -2px 0 0 var(--canon-bg-surface-1);
font-size: var(--canon-font-size-3);
}
.canon-TableCellText, .canon-TableCellLink {
gap: var(--canon-space-0_5);
flex-direction: column;
display: flex;
}
.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-fg-primary);
margin: 0;
padding: 0;
}
.canon-Text--variant-body {
.canon-Text[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Text--variant-subtitle {
.canon-Text[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Text--variant-caption {
.canon-Text[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Text--variant-label {
.canon-Text[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Text--weight-regular {
.canon-Text[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Text--weight-bold {
.canon-Text[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
.canon-Text--color-primary {
.canon-Text[data-color="primary"] {
color: var(--canon-fg-primary);
}
.canon-Text--color-secondary {
.canon-Text[data-color="secondary"] {
color: var(--canon-fg-secondary);
}
.canon-Text--color-danger {
.canon-Text[data-color="danger"] {
color: var(--canon-fg-danger);
}
.canon-Text--color-warning {
.canon-Text[data-color="warning"] {
color: var(--canon-fg-warning);
}
.canon-Text--color-success {
.canon-Text[data-color="success"] {
color: var(--canon-fg-success);
}
@@ -9525,32 +9532,32 @@
line-height: 100%;
}
.canon-Heading--variant-display {
.canon-Heading[data-variant="display"] {
font-size: var(--canon-font-size-10);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title1 {
.canon-Heading[data-variant="title1"] {
font-size: var(--canon-font-size-9);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title2 {
.canon-Heading[data-variant="title2"] {
font-size: var(--canon-font-size-8);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title3 {
.canon-Heading[data-variant="title3"] {
font-size: var(--canon-font-size-7);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title4 {
.canon-Heading[data-variant="title4"] {
font-size: var(--canon-font-size-title4);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title5 {
.canon-Heading[data-variant="title5"] {
font-size: var(--canon-font-size-5);
font-weight: var(--canon-font-weight-bold);
}
@@ -9573,7 +9580,7 @@
}
}
.canon-IconButton--variant-primary {
.canon-IconButton[data-variant="primary"] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color .15s, box-shadow .15s;
@@ -9597,7 +9604,7 @@
}
}
.canon-IconButton--variant-secondary {
.canon-IconButton[data-variant="secondary"] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -9623,24 +9630,24 @@
}
}
.canon-IconButton--size-medium {
.canon-IconButton[data-size="medium"] {
font-size: var(--canon-font-size-4);
width: 40px;
height: 40px;
}
.canon-IconButton--size-small {
.canon-IconButton[data-size="small"] {
font-size: var(--canon-font-size-3);
width: 32px;
height: 32px;
}
.canon-IconButton--size-small .canon-IconButton--icon {
.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-IconButton--size-medium .canon-IconButton--icon {
.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
@@ -9652,14 +9659,14 @@
display: flex;
}
.canon-TextField--label {
.canon-TextFieldLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-TextField--description {
.canon-TextFieldDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -9667,7 +9674,7 @@
margin: 0;
}
.canon-TextField--error {
.canon-TextFieldError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -9675,7 +9682,7 @@
margin: 0;
}
.canon-TextField--input {
.canon-TextFieldInput {
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
padding: 0 var(--canon-space-4);
@@ -9688,39 +9695,39 @@
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
}
.canon-TextField--input::placeholder {
.canon-TextFieldInput::placeholder {
color: var(--canon-fg-secondary);
}
.canon-TextField--input:hover {
.canon-TextFieldInput:hover {
border-color: var(--canon-border-hover);
}
.canon-TextField--input:focus-visible {
.canon-TextFieldInput:focus-visible {
outline-color: var(--canon-border-pressed);
border-color: var(--canon-border-pressed);
outline-width: 0;
}
.canon-TextField--input[data-invalid] {
.canon-TextFieldInput[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-TextField--input[data-disabled] {
.canon-TextFieldInput[data-disabled] {
opacity: .5;
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextField--input-size-small {
.canon-TextFieldInput[data-size="small"] {
height: 2rem;
}
.canon-TextField--input-size-medium {
.canon-TextFieldInput[data-size="medium"] {
height: 2.5rem;
}
.canon-TextField--required {
.canon-TextFieldRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
@@ -9797,31 +9804,31 @@
}
}
.canon-Link--variant-body {
.canon-Link[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Link--variant-subtitle {
.canon-Link[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Link--variant-caption {
.canon-Link[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Link--variant-label {
.canon-Link[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Link--weight-regular {
.canon-Link[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Link--weight-bold {
.canon-Link[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
@@ -9945,14 +9952,14 @@
display: flex;
}
.canon-Select--label {
.canon-SelectLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-Select--description {
.canon-SelectDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -9960,7 +9967,7 @@
margin: 0;
}
.canon-Select--error {
.canon-SelectError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -9968,7 +9975,7 @@
margin: 0;
}
.canon-Select--trigger {
.canon-SelectTrigger {
box-sizing: border-box;
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
@@ -9987,38 +9994,38 @@
display: flex;
}
.canon-Select--trigger::placeholder {
.canon-SelectTrigger::placeholder {
color: var(--canon-fg-secondary);
}
.canon-Select--trigger:hover {
.canon-SelectTrigger:hover {
border-color: var(--canon-border-hover);
}
.canon-Select--trigger:focus-visible {
.canon-SelectTrigger:focus-visible {
border-color: var(--canon-border-pressed);
outline: 0;
}
.canon-Select--trigger[data-invalid] {
.canon-SelectTrigger[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-Select--trigger[data-invalid]:hover, .canon-Select--trigger[data-invalid]:focus-visible {
.canon-SelectTrigger[data-invalid]:hover, .canon-SelectTrigger[data-invalid]:focus-visible {
border-width: 2px;
}
.canon-Select--trigger[data-disabled] {
.canon-SelectTrigger[data-disabled] {
cursor: not-allowed;
border-color: var(--canon-border-disabled);
color: var(--canon-fg-disabled);
}
.canon-Select--trigger-size-small {
.canon-SelectTrigger[data-size="small"] {
height: 2rem;
}
.canon-Select--trigger-size-medium {
.canon-SelectTrigger[data-size="medium"] {
height: 3rem;
}
@@ -10027,11 +10034,11 @@
transition: transform .2s;
}
.canon-Select--trigger[data-popup-open] .canon-SelectIcon {
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
transform: rotate(180deg);
}
.canon-Select--popup {
.canon-SelectPopup {
box-sizing: border-box;
max-height: var(--available-height);
background-color: var(--canon-bg-surface-1);
@@ -10046,12 +10053,12 @@
box-shadow: 0 4px 12px #0003;
}
.canon-Select--popup[data-starting-style], .canon-Select--popup[data-ending-style] {
.canon-SelectPopup[data-starting-style], .canon-SelectPopup[data-ending-style] {
opacity: 0;
transform: scale(.9);
}
.canon-Select--item {
.canon-SelectItem {
width: var(--anchor-width);
padding-block: var(--canon-space-2);
padding-inline: var(--canon-space-4);
@@ -10069,13 +10076,13 @@
position: relative;
}
.canon-Select--item[data-highlighted] {
.canon-SelectItem[data-highlighted] {
z-index: 0;
color: var(--canon-fg-primary);
position: relative;
}
.canon-Select--item[data-highlighted]:before {
.canon-SelectItem[data-highlighted]:before {
content: "";
z-index: -1;
background-color: var(--canon-bg-tint-hover);
@@ -10085,37 +10092,37 @@
inset-inline: .25rem;
}
.canon-Select--item[data-disabled] {
.canon-SelectItem[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
}
.canon-Select--item-indicator {
.canon-SelectItemIndicator {
grid-area: icon;
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--item-text {
.canon-SelectItemText {
flex: 1;
grid-area: text;
}
.canon-Select--required {
.canon-SelectRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-left: var(--canon-space-1);
}
.canon-Select--icon {
.canon-SelectIcon {
justify-content: center;
align-items: center;
display: flex;
}
.canon-Select--value {
.canon-SelectValue {
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
+2 -56
View File
@@ -1,58 +1,4 @@
.table {
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
width: 100%;
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
.canon-TableCell {
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
position: relative;
overflow: auto;
& table {
caption-side: bottom;
width: 100%;
font-size: var(--canon-font-size-sm);
border-collapse: collapse;
}
}
.table-head {
text-align: left;
padding: var(--canon-space-3);
}
.table-body {
& tr:last-child {
border-bottom: none;
}
}
.table-row {
transition: color .2s ease-in-out;
&:hover td {
background-color: var(--canon-bg-tint);
}
& .table-cell:first-child {
border-top-left-radius: var(--canon-radius-2);
border-bottom-left-radius: var(--canon-radius-2);
box-shadow: inset 4px 2px 0 0 var(--canon-bg-surface-1), inset 4px -2px 0 0 var(--canon-bg-surface-1);
padding-left: var(--canon-space-3);
}
& .table-cell:last-child {
border-top-right-radius: var(--canon-radius-2);
border-bottom-right-radius: var(--canon-radius-2);
box-shadow: inset -4px 2px 0 0 var(--canon-bg-surface-1), inset -4px -2px 0 0 var(--canon-bg-surface-1);
padding-right: var(--canon-space-3);
}
}
.table-cell {
padding: var(--canon-space-3);
background-color: var(--canon-bg);
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1), inset 0px -2px 0 0 var(--canon-bg-surface-1);
}
+11 -12
View File
@@ -1,54 +1,53 @@
.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-fg-primary);
margin: 0;
padding: 0;
}
.canon-Text--variant-body {
.canon-Text[data-variant="body"] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Text--variant-subtitle {
.canon-Text[data-variant="subtitle"] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Text--variant-caption {
.canon-Text[data-variant="caption"] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Text--variant-label {
.canon-Text[data-variant="label"] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Text--weight-regular {
.canon-Text[data-weight="regular"] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Text--weight-bold {
.canon-Text[data-weight="bold"] {
font-weight: var(--canon-font-weight-bold);
}
.canon-Text--color-primary {
.canon-Text[data-color="primary"] {
color: var(--canon-fg-primary);
}
.canon-Text--color-secondary {
.canon-Text[data-color="secondary"] {
color: var(--canon-fg-secondary);
}
.canon-Text--color-danger {
.canon-Text[data-color="danger"] {
color: var(--canon-fg-danger);
}
.canon-Text--color-warning {
.canon-Text[data-color="warning"] {
color: var(--canon-fg-warning);
}
.canon-Text--color-success {
.canon-Text[data-color="success"] {
color: var(--canon-fg-success);
}
+12 -12
View File
@@ -5,14 +5,14 @@
display: flex;
}
.canon-TextField--label {
.canon-TextFieldLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-TextField--description {
.canon-TextFieldDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -20,7 +20,7 @@
margin: 0;
}
.canon-TextField--error {
.canon-TextFieldError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -28,7 +28,7 @@
margin: 0;
}
.canon-TextField--input {
.canon-TextFieldInput {
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
padding: 0 var(--canon-space-4);
@@ -41,39 +41,39 @@
transition: border-color .2s ease-in-out, outline-color .2s ease-in-out;
}
.canon-TextField--input::placeholder {
.canon-TextFieldInput::placeholder {
color: var(--canon-fg-secondary);
}
.canon-TextField--input:hover {
.canon-TextFieldInput:hover {
border-color: var(--canon-border-hover);
}
.canon-TextField--input:focus-visible {
.canon-TextFieldInput:focus-visible {
outline-color: var(--canon-border-pressed);
border-color: var(--canon-border-pressed);
outline-width: 0;
}
.canon-TextField--input[data-invalid] {
.canon-TextFieldInput[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-TextField--input[data-disabled] {
.canon-TextFieldInput[data-disabled] {
opacity: .5;
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextField--input-size-small {
.canon-TextFieldInput[data-size="small"] {
height: 2rem;
}
.canon-TextField--input-size-medium {
.canon-TextFieldInput[data-size="medium"] {
height: 2.5rem;
}
.canon-TextField--required {
.canon-TextFieldRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
+1
View File
@@ -56,6 +56,7 @@
"@storybook/react": "^8.6.8",
"@storybook/react-webpack5": "^8.6.8",
"@storybook/test": "^8.6.8",
"@tanstack/react-table": "^8.21.2",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"chalk": "^5.4.1",
+118 -33
View File
@@ -17,6 +17,7 @@ import { ReactNode } from 'react';
import { RefAttributes } from 'react';
import type { RemixiconComponentType } from '@remixicon/react';
import { ScrollArea as ScrollArea_2 } from '@base-ui-components/react/scroll-area';
import { Table as Table_2 } from '@tanstack/react-table';
import { TdHTMLAttributes } from 'react';
import { ThHTMLAttributes } from 'react';
import { Tooltip as Tooltip_2 } from '@base-ui-components/react/tooltip';
@@ -219,6 +220,65 @@ export interface ContainerProps {
style?: React.CSSProperties;
}
// @public
export const DataTable: {
Root: <TData>(
props: {
table: Table_2<TData>;
} & React.HTMLAttributes<HTMLDivElement>,
) => JSX.Element;
Pagination: ForwardRefExoticComponent<
DataTablePaginationProps & RefAttributes<HTMLDivElement>
>;
Table: ForwardRefExoticComponent<
DataTableTableProps & RefAttributes<HTMLTableElement>
>;
TableRoot: ForwardRefExoticComponent<
Omit<
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>,
'ref'
> &
RefAttributes<HTMLTableElement>
>;
TableHeader: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
TableBody: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
TableRow: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>
>;
TableCell: ForwardRefExoticComponent<
TdHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
TableCellText: ForwardRefExoticComponent<
TableCellTextProps & RefAttributes<HTMLDivElement>
>;
TableCellLink: ForwardRefExoticComponent<
TableCellLinkProps & RefAttributes<HTMLDivElement>
>;
TableHead: ForwardRefExoticComponent<
ThHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
};
// @public (undocumented)
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {}
// @public (undocumented)
export interface DataTableRootProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
table: Table_2<TData>;
}
// @public (undocumented)
export interface DataTableTableProps
extends React.HTMLAttributes<HTMLTableElement> {}
// @public (undocumented)
export type Display = 'none' | 'flex' | 'block' | 'inline';
@@ -1028,43 +1088,61 @@ export type StylingPropDef = {
parseValue?: (value: string) => string | undefined;
};
// @public (undocumented)
export const Table: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>
>;
// @public
export const Table: {
Root: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableElement> & RefAttributes<HTMLTableElement>
>;
Header: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
Body: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
Head: ForwardRefExoticComponent<
ThHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
Row: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>
>;
Cell: ForwardRefExoticComponent<
TdHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
CellText: ForwardRefExoticComponent<
TableCellTextProps & RefAttributes<HTMLDivElement>
>;
CellLink: ForwardRefExoticComponent<
TableCellLinkProps & RefAttributes<HTMLDivElement>
>;
Caption: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableCaptionElement> &
RefAttributes<HTMLTableCaptionElement>
>;
};
// @public (undocumented)
export const TableBody: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
export interface TableCellLinkProps
extends React.TdHTMLAttributes<HTMLTableCellElement> {
// (undocumented)
description?: string;
// (undocumented)
href: string;
// (undocumented)
render?: useRender.ComponentProps<'a'>['render'];
// (undocumented)
title: string;
}
// @public (undocumented)
export const TableCell: ForwardRefExoticComponent<
TdHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
// @public (undocumented)
export const TableFooter: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
// @public (undocumented)
export const TableHead: ForwardRefExoticComponent<
ThHTMLAttributes<HTMLTableCellElement> & RefAttributes<HTMLTableCellElement>
>;
// @public (undocumented)
export const TableHeader: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableSectionElement> &
RefAttributes<HTMLTableSectionElement>
>;
// @public (undocumented)
export const TableRow: ForwardRefExoticComponent<
HTMLAttributes<HTMLTableRowElement> & RefAttributes<HTMLTableRowElement>
>;
export interface TableCellTextProps
extends React.TdHTMLAttributes<HTMLTableCellElement> {
// (undocumented)
description?: string;
// (undocumented)
title: string;
}
// @public (undocumented)
const Text_2: ForwardRefExoticComponent<
@@ -1143,6 +1221,13 @@ export const Tooltip: {
>;
};
// @public (undocumented)
export const useBreakpoint: () => {
breakpoint: Breakpoint;
up: (key: Breakpoint) => boolean;
down: (key: Breakpoint) => boolean;
};
// @public (undocumented)
export const useIcons: () => IconContextProps;
@@ -44,18 +44,15 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
<button
ref={ref}
disabled={disabled}
className={clsx(
'canon-Button',
`canon-Button--size-${responsiveSize}`,
`canon-Button--variant-${responsiveVariant}`,
className,
)}
className={clsx('canon-Button', className)}
data-size={responsiveSize}
data-variant={responsiveVariant}
style={style}
{...rest}
>
{iconStart && <Icon name={iconStart} className="canon-Button--icon" />}
{iconStart && <Icon name={iconStart} className="canon-ButtonIcon" />}
{children}
{iconEnd && <Icon name={iconEnd} className="canon-Button--icon" />}
{iconEnd && <Icon name={iconEnd} className="canon-ButtonIcon" />}
</button>
);
},
@@ -32,7 +32,7 @@
}
}
.canon-Button--variant-primary {
.canon-Button[data-variant='primary'] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color 150ms ease, box-shadow 150ms ease;
@@ -56,7 +56,7 @@
}
}
.canon-Button--variant-secondary {
.canon-Button[data-variant='secondary'] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -82,24 +82,24 @@
}
}
.canon-Button--size-medium {
.canon-Button[data-size='medium'] {
font-size: var(--canon-font-size-4);
padding: 0 var(--canon-space-3);
height: 40px;
}
.canon-Button--size-small {
.canon-Button[data-size='small'] {
font-size: var(--canon-font-size-3);
padding: 0 var(--canon-space-2);
height: 32px;
}
.canon-Button--size-small .canon-Button--icon {
.canon-Button[data-size='small'] .canon-ButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-Button--size-medium .canon-Button--icon {
.canon-Button[data-size='medium'] .canon-ButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
@@ -0,0 +1,145 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { DataTable } from '.';
import { data, DataProps } from './mocked-components';
import { columns } from './mocked-columns';
import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
PaginationState,
} from '@tanstack/react-table';
import { useState } from 'react';
const meta = {
title: 'Components/DataTable',
} satisfies Meta;
export default meta;
type Story = StoryObj<typeof meta>;
export const Uncontrolled: Story = {
render: () => {
const table = useReactTable<DataProps>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<DataTable.Root table={table}>
<DataTable.Table />
<DataTable.Pagination />
</DataTable.Root>
);
},
};
export const Controlled: Story = {
render: () => {
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 4,
pageSize: 5,
});
const table = useReactTable<DataProps>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
state: {
pagination,
},
onPaginationChange: setPagination,
});
return (
<DataTable.Root table={table}>
<DataTable.Table />
<DataTable.Pagination />
</DataTable.Root>
);
},
};
export const WithCustomTable: Story = {
render: () => {
const table = useReactTable<DataProps>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
});
return (
<DataTable.Root table={table}>
<DataTable.TableRoot>
<DataTable.TableHeader>
{table.getHeaderGroups().map(headerGroup => (
<DataTable.TableRow key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<DataTable.TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</DataTable.TableHead>
);
})}
</DataTable.TableRow>
))}
</DataTable.TableHeader>
<DataTable.TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<DataTable.TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map(cell => (
<DataTable.TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</DataTable.TableCell>
))}
</DataTable.TableRow>
))
) : (
<DataTable.TableRow>
<DataTable.TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</DataTable.TableCell>
</DataTable.TableRow>
)}
</DataTable.TableBody>
</DataTable.TableRoot>
<DataTable.Pagination />
</DataTable.Root>
);
},
};
@@ -0,0 +1,50 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import { Table } from '../Table';
import { DataTableRoot } from './Root/DataTableRoot';
import { DataTablePagination } from './Pagination/DataTablePagination';
import { Table as TanstackTable } from '@tanstack/react-table';
import { DataTableTable } from './Table/DataTableTable';
const TableRoot = forwardRef<
React.ElementRef<typeof Table.Root>,
React.ComponentPropsWithoutRef<typeof Table.Root>
>(({ className, ...props }, ref) => <Table.Root ref={ref} {...props} />);
TableRoot.displayName = Table.Root.displayName;
/**
* DataTable component for displaying tabular data with pagination
* @public
*/
export const DataTable = {
Root: DataTableRoot as <TData>(
props: {
table: TanstackTable<TData>;
} & React.HTMLAttributes<HTMLDivElement>,
) => JSX.Element,
Pagination: DataTablePagination,
Table: DataTableTable,
TableRoot: TableRoot,
TableHeader: Table.Header,
TableBody: Table.Body,
TableRow: Table.Row,
TableCell: Table.Cell,
TableCellText: Table.CellText,
TableCellLink: Table.CellLink,
TableHead: Table.Head,
};
@@ -0,0 +1,55 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { DataTablePagination } from './DataTablePagination';
import {
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { data, DataProps } from '../mocked-components';
import { columns } from '../mocked-columns';
import { DataTable } from '../DataTable';
const meta = {
title: 'Components/DataTable/Pagination',
component: DataTablePagination,
} satisfies Meta<typeof DataTablePagination>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const table = useReactTable<DataProps>({
data,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
});
return (
<DataTable.Root table={table}>
<DataTable.Pagination />
</DataTable.Root>
);
},
};
@@ -0,0 +1,23 @@
.canon-DataTablePagination {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: var(--canon-space-5);
}
.canon-DataTablePagination--left {
display: flex;
align-items: center;
justify-content: space-between;
}
.canon-DataTablePagination--right {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--canon-space-2);
}
.canon-DataTablePagination--select {
min-width: 10.5rem;
}
@@ -0,0 +1,90 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import { Text } from '../../Text';
import { DataTablePaginationProps } from './types';
import { IconButton } from '../../IconButton';
import clsx from 'clsx';
import { Select } from '../../Select';
import { useDataTable } from '../Root/DataTableRoot';
/** @public */
const DataTablePagination = forwardRef(
(
props: DataTablePaginationProps,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
const { className, ...rest } = props;
const { table } = useDataTable();
const pageIndex = table?.getState().pagination.pageIndex;
const pageSize = table?.getState().pagination.pageSize;
return (
<div
ref={ref}
className={clsx('canon-DataTablePagination', className)}
{...rest}
>
<div className="canon-DataTablePagination--left">
{!table.options.manualPagination && (
<Select
name="pageSize"
size="small"
placeholder="Show 10 results"
options={[
{ label: 'Show 5 results', value: '5' },
{ label: 'Show 10 results', value: '10' },
{ label: 'Show 20 results', value: '20' },
{ label: 'Show 30 results', value: '30' },
{ label: 'Show 40 results', value: '40' },
{ label: 'Show 50 results', value: '50' },
]}
value={pageSize?.toString()}
onValueChange={value => {
table?.setPageSize(Number(value));
}}
className="canon-DataTablePagination--select"
/>
)}
</div>
<div className="canon-DataTablePagination--right">
<Text variant="body">{`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${
((pageIndex ?? 0) + 1) * (pageSize ?? 10)
} of ${table?.getRowCount()}`}</Text>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.previousPage()}
disabled={!table?.getCanPreviousPage()}
icon="chevron-left"
/>
<IconButton
variant="secondary"
size="small"
onClick={() => table?.nextPage()}
disabled={!table?.getCanNextPage()}
icon="chevron-right"
/>
</div>
</div>
);
},
);
DataTablePagination.displayName = 'DataTablePagination';
export { DataTablePagination };
@@ -0,0 +1,19 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @public */
export interface DataTablePaginationProps
extends React.HTMLAttributes<HTMLDivElement> {}
@@ -0,0 +1,5 @@
.canon-DataTableRoot {
display: flex;
flex-direction: column;
gap: var(--canon-space-3);
}
@@ -0,0 +1,62 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef, createContext, useContext } from 'react';
import clsx from 'clsx';
import { DataTableRootProps } from './types';
import { Table } from '@tanstack/react-table';
type DataTableContextType<TData> = {
table: Table<TData>;
};
/** @public */
export const DataTableContext = createContext<DataTableContextType<any> | null>(
null,
);
/** @public */
const DataTableRoot = forwardRef(
<TData,>(
props: DataTableRootProps<TData>,
ref: React.ForwardedRef<HTMLDivElement>,
) => {
const { className, table, ...rest } = props;
return (
<DataTableContext.Provider value={{ table }}>
<div
ref={ref}
className={clsx('canon-DataTable', className)}
{...rest}
/>
</DataTableContext.Provider>
);
},
);
DataTableRoot.displayName = 'DataTableRoot';
export { DataTableRoot };
/** @public */
export function useDataTable<TData>() {
const context = useContext(DataTableContext);
if (!context) {
throw new Error('useDataTable must be used within a DataTableRoot');
}
return context as DataTableContextType<TData>;
}
@@ -0,0 +1,26 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Table } from '@tanstack/react-table';
/** @public */
export interface DataTableRootProps<TData>
extends React.HTMLAttributes<HTMLDivElement> {
/**
* The table instance.
*/
table: Table<TData>;
}
@@ -0,0 +1,82 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import clsx from 'clsx';
import { DataTableTableProps } from './types';
import { Table } from '../../Table';
import { useDataTable } from '../Root/DataTableRoot';
import { flexRender } from '@tanstack/react-table';
/** @public */
const DataTableTable = forwardRef(
(props: DataTableTableProps, ref: React.ForwardedRef<HTMLTableElement>) => {
const { className, ...rest } = props;
const { table } = useDataTable();
return (
<Table.Root ref={ref} className={clsx(className)} {...rest}>
<Table.Header>
{table.getHeaderGroups().map(headerGroup => (
<Table.Row key={headerGroup.id}>
{headerGroup.headers.map(header => {
return (
<Table.Head key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</Table.Head>
);
})}
</Table.Row>
))}
</Table.Header>
<Table.Body>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map(row => (
<Table.Row
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map(cell => (
<Table.Cell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</Table.Cell>
))}
</Table.Row>
))
) : (
<Table.Row>
<Table.Cell
colSpan={table.getAllColumns().length}
className="h-24 text-center"
>
No results.
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table.Root>
);
},
);
DataTableTable.displayName = 'DataTableRoot';
export { DataTableTable };
@@ -0,0 +1,19 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @public */
export interface DataTableTableProps
extends React.HTMLAttributes<HTMLTableElement> {}
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './DataTable';
export * from './Root/types';
export * from './Pagination/types';
export * from './Table/types';
@@ -0,0 +1,74 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ColumnDef } from '@tanstack/react-table';
import { DataProps } from './mocked-components';
import { Checkbox } from '../Checkbox';
import { DataTable } from './DataTable';
export const columns: ColumnDef<DataProps>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
onChange={(checked: boolean) =>
table.toggleAllPageRowsSelected(checked)
}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<td>
<Checkbox
checked={row.getIsSelected()}
onChange={(checked: boolean) => row.toggleSelected(checked)}
aria-label="Select row"
/>
</td>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => (
<DataTable.TableCellLink
title={row.getValue('name')}
description={row.original.description}
href="/"
/>
),
},
{
accessorKey: 'owner',
header: 'Owner',
cell: ({ row }) => (
<DataTable.TableCellText title={row.getValue('owner')} />
),
},
{
accessorKey: 'type',
header: 'Type',
cell: ({ row }) => <DataTable.TableCellText title={row.getValue('type')} />,
},
{
accessorKey: 'tags',
header: 'Tags',
cell: ({ row }) => <DataTable.TableCellText title={row.getValue('tags')} />,
},
];
@@ -0,0 +1,818 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface DataProps {
name: string;
owner: string;
type: 'documentation' | 'library' | 'service' | 'website' | 'other';
description?: string;
tags?: string[];
}
export const data: DataProps[] = [
{
name: 'authentication-and-authorization-service',
owner: 'security-team',
type: 'service',
description:
'A comprehensive service handling user authentication and role-based access control across all applications.',
tags: ['security', 'authentication', 'authorization'],
},
{
name: 'user-interface-dashboard-and-analytics-platform',
owner: 'frontend-team',
type: 'website',
description:
'Interactive dashboard providing real-time analytics and data visualization for business metrics.',
tags: ['analytics', 'visualization', 'dashboard'],
},
{
name: 'payment-gateway',
owner: 'finance-team',
type: 'service',
description:
'Secure payment processing system supporting multiple payment methods and currencies.',
tags: ['payments', 'security', 'finance'],
},
{
name: 'real-time-analytics-processing-and-visualization-engine',
owner: 'data-team',
type: 'service',
description:
'High-performance engine for processing and visualizing streaming data analytics.',
tags: ['analytics', 'real-time', 'data-processing'],
},
{
name: 'notification-center',
owner: 'platform-team',
type: 'service',
description:
'Centralized system for managing and delivering notifications across multiple channels.',
tags: ['notifications', 'messaging'],
},
{
name: 'administrative-control-panel-and-user-management-interface',
owner: 'frontend-team',
type: 'website',
description:
'Admin interface for managing users, permissions, and system configurations.',
tags: ['admin', 'user-management', 'configuration'],
},
{
name: 'search-indexer',
owner: 'search-team',
type: 'service',
description:
'Service responsible for indexing and updating searchable content across the platform.',
tags: ['search', 'indexing'],
},
{
name: 'cross-platform-mobile-application-framework',
owner: 'mobile-team',
type: 'website',
description:
'Framework enabling development of cross-platform mobile applications with shared codebase.',
tags: ['mobile', 'framework', 'cross-platform'],
},
{
name: 'database-migration',
owner: 'devops-team',
type: 'other',
description:
'Tools and scripts for managing database schema migrations and data transformations.',
tags: ['database', 'migration', 'devops'],
},
{
name: 'api-gateway',
owner: 'platform-team',
type: 'service',
description:
'Central entry point for all API requests, handling routing, authentication, and rate limiting.',
tags: ['api', 'gateway', 'security', 'routing'],
},
{
name: 'content-management',
owner: 'content-team',
type: 'service',
description:
'System for managing and delivering digital content across multiple channels.',
tags: ['content', 'management', 'delivery'],
},
{
name: 'enterprise-reporting-and-analytics-dashboard',
owner: 'analytics-team',
type: 'website',
description:
'Comprehensive business intelligence platform for enterprise-wide reporting and analytics.',
tags: ['analytics', 'reporting', 'business-intelligence'],
},
{
name: 'image-processing-and-optimization-service',
owner: 'media-team',
type: 'service',
description:
'Service for processing, optimizing, and delivering images across different devices and networks.',
tags: ['media', 'optimization', 'processing'],
},
{
name: 'customer-portal',
owner: 'frontend-team',
type: 'website',
description:
'Self-service portal for customers to manage their accounts and access services.',
tags: ['customer', 'self-service'],
},
{
name: 'log-aggregator',
owner: 'devops-team',
type: 'service',
description:
'Centralized logging system for collecting, processing, and analyzing application logs.',
tags: ['logging', 'monitoring', 'devops'],
},
{
name: 'identity-provider',
owner: 'security-team',
type: 'service',
description:
'Service managing user identities and authentication across the organization.',
tags: ['identity', 'security', 'authentication'],
},
{
name: 'document-storage',
owner: 'storage-team',
type: 'service',
description:
'Secure and scalable document storage system with version control and access management.',
tags: ['storage', 'documents', 'version-control'],
},
{
name: 'workflow-engine',
owner: 'platform-team',
type: 'service',
description:
'Engine for defining and executing business processes and workflows.',
tags: ['workflow', 'automation'],
},
{
name: 'mobile-backend',
owner: 'mobile-team',
type: 'service',
description:
'Backend services supporting mobile applications with optimized APIs and data synchronization.',
tags: ['mobile', 'backend', 'api'],
},
{
name: 'system-monitoring-and-alerting-dashboard',
owner: 'devops-team',
type: 'website',
description:
'Real-time monitoring and alerting system for infrastructure and application health.',
tags: ['monitoring', 'alerting', 'devops', 'infrastructure'],
},
{
name: 'email-service',
owner: 'communication-team',
type: 'service',
description:
'Reliable email delivery service with templates and tracking capabilities.',
tags: ['email', 'communication'],
},
{
name: 'data-pipeline',
owner: 'data-team',
type: 'service',
description:
'ETL pipeline for processing and transforming large volumes of data.',
tags: ['data', 'etl', 'pipeline'],
},
{
name: 'configuration-manager',
owner: 'platform-team',
type: 'service',
description:
'Centralized system for managing application configurations across environments.',
tags: ['configuration', 'management'],
},
{
name: 'testing-framework',
owner: 'qa-team',
type: 'library',
description:
'Comprehensive testing framework supporting various types of automated tests.',
tags: ['testing', 'automation', 'qa'],
},
{
name: 'cache-service',
owner: 'platform-team',
type: 'service',
description:
'Distributed caching service for improving application performance.',
tags: ['caching', 'performance'],
},
{
name: 'billing-system',
owner: 'finance-team',
type: 'service',
description:
'System for managing customer billing, invoicing, and payment processing.',
tags: ['billing', 'finance', 'payments'],
},
{
name: 'comprehensive-product-documentation-and-api-reference',
owner: 'docs-team',
type: 'documentation',
description:
'Complete documentation covering product features, APIs, and integration guides.',
tags: ['documentation', 'api', 'reference'],
},
{
name: 'queue-manager',
owner: 'platform-team',
type: 'service',
description:
'Message queue system for asynchronous processing and event handling.',
tags: ['queue', 'messaging', 'async'],
},
{
name: 'security-scanner',
owner: 'security-team',
type: 'other',
description:
'Automated security scanning tool for identifying vulnerabilities in code and infrastructure.',
tags: ['security', 'scanning', 'vulnerability'],
},
{
name: 'user-profile',
owner: 'frontend-team',
type: 'website',
description:
'User profile management interface with personalization features.',
tags: ['user', 'profile', 'personalization'],
},
{
name: 'data-warehouse',
owner: 'data-team',
type: 'service',
description:
'Centralized data repository for business intelligence and analytics.',
tags: ['data', 'warehouse', 'analytics'],
},
{
name: 'deployment-automation',
owner: 'devops-team',
type: 'other',
description:
'Automated deployment pipeline for continuous integration and delivery.',
tags: ['deployment', 'automation', 'ci-cd', 'devops'],
},
{
name: 'chat-service',
owner: 'communication-team',
type: 'service',
description:
'Real-time chat service supporting text, file sharing, and group conversations.',
tags: ['chat', 'communication', 'real-time'],
},
{
name: 'analytics-dashboard',
owner: 'analytics-team',
type: 'website',
description:
'Interactive dashboard for visualizing and analyzing business metrics.',
tags: ['analytics', 'dashboard', 'visualization'],
},
{
name: 'file-uploader',
owner: 'storage-team',
type: 'service',
description:
'Service for handling secure file uploads with progress tracking and validation.',
tags: ['storage', 'upload', 'files'],
},
{
name: 'search-service',
owner: 'search-team',
type: 'service',
description:
'Full-text search service with advanced filtering and ranking capabilities.',
tags: ['search', 'full-text'],
},
{
name: 'mobile-sdk',
owner: 'mobile-team',
type: 'library',
description:
'Software development kit for building mobile applications with native features.',
tags: ['mobile', 'sdk', 'development'],
},
{
name: 'performance-monitor',
owner: 'devops-team',
type: 'service',
description:
'System for monitoring and analyzing application performance metrics.',
tags: ['performance', 'monitoring', 'metrics'],
},
{
name: 'content-delivery',
owner: 'media-team',
type: 'service',
description:
'CDN service for optimized content delivery across global networks.',
tags: ['cdn', 'content', 'delivery'],
},
{
name: 'user-authentication',
owner: 'security-team',
type: 'service',
description:
'Service handling user login, session management, and authentication flows.',
tags: ['authentication', 'security', 'user'],
},
{
name: 'data-export',
owner: 'data-team',
type: 'service',
description:
'Service for exporting data in various formats with scheduling capabilities.',
tags: ['data', 'export', 'scheduling'],
},
{
name: 'admin-api',
owner: 'platform-team',
type: 'service',
description:
'API endpoints for administrative functions and system management.',
tags: ['api', 'admin', 'management'],
},
{
name: 'testing-dashboard',
owner: 'qa-team',
type: 'website',
description: 'Dashboard for monitoring test results and quality metrics.',
tags: ['testing', 'dashboard', 'qa'],
},
{
name: 'message-broker',
owner: 'platform-team',
type: 'service',
description:
'Message broker service for reliable event-driven communication between services.',
tags: ['messaging', 'broker', 'event-driven'],
},
{
name: 'payment-processor',
owner: 'finance-team',
type: 'service',
description:
'Service for processing financial transactions and payment methods.',
tags: ['payments', 'finance', 'processing'],
},
{
name: 'document-viewer',
owner: 'frontend-team',
type: 'website',
description: 'Web-based document viewer supporting multiple file formats.',
tags: ['documents', 'viewer'],
},
{
name: 'load-balancer',
owner: 'devops-team',
type: 'service',
description:
'Service for distributing network traffic across multiple servers.',
tags: ['load-balancing', 'networking', 'infrastructure'],
},
{
name: 'security-audit',
owner: 'security-team',
type: 'other',
description:
'Tools and processes for conducting security audits and compliance checks.',
tags: ['security', 'audit', 'compliance'],
},
{
name: 'user-settings',
owner: 'frontend-team',
type: 'website',
description:
'Interface for users to manage their preferences and account settings.',
tags: ['user', 'settings', 'preferences'],
},
{
name: 'data-import',
owner: 'data-team',
type: 'service',
description:
'Service for importing and validating data from external sources.',
tags: ['data', 'import', 'validation'],
},
{
name: 'infrastructure-monitor',
owner: 'devops-team',
type: 'service',
description:
'Monitoring system for infrastructure components and resources.',
tags: ['monitoring', 'infrastructure', 'devops'],
},
{
name: 'notification-manager',
owner: 'communication-team',
type: 'service',
description:
'Service for managing and delivering notifications across multiple channels.',
tags: ['notifications', 'management'],
},
{
name: 'analytics-processor',
owner: 'analytics-team',
type: 'service',
description:
'Service for processing and analyzing business data and metrics.',
tags: ['analytics', 'processing', 'metrics'],
},
{
name: 'file-manager',
owner: 'storage-team',
type: 'website',
description: 'Web interface for managing files and storage resources.',
tags: ['files', 'storage', 'management'],
},
{
name: 'search-index',
owner: 'search-team',
type: 'service',
description: 'Service for maintaining and updating search indices.',
tags: ['search', 'indexing'],
},
{
name: 'mobile-authentication',
owner: 'mobile-team',
type: 'service',
description:
'Authentication service specifically designed for mobile applications.',
tags: ['mobile', 'authentication', 'security'],
},
{
name: 'system-monitor',
owner: 'devops-team',
type: 'service',
description:
'Monitoring service for system health and performance metrics.',
tags: ['monitoring', 'system', 'metrics'],
},
{
name: 'media-processor',
owner: 'media-team',
type: 'service',
description: 'Service for processing and optimizing media files.',
tags: ['media', 'processing', 'optimization'],
},
{
name: 'user-management',
owner: 'security-team',
type: 'service',
description: 'Service for managing user accounts and permissions.',
tags: ['user', 'management', 'security'],
},
{
name: 'data-transformer',
owner: 'data-team',
type: 'service',
description:
'Service for transforming data between different formats and structures.',
tags: ['data', 'transformation'],
},
{
name: 'admin-dashboard',
owner: 'platform-team',
type: 'website',
description:
'Administrative interface for system management and monitoring.',
tags: ['admin', 'dashboard', 'management'],
},
{
name: 'test-automation',
owner: 'qa-team',
type: 'other',
description: 'Tools and frameworks for automating testing processes.',
tags: ['testing', 'automation', 'qa'],
},
{
name: 'event-bus',
owner: 'platform-team',
type: 'service',
description: 'Event-driven communication system between services.',
tags: ['events', 'messaging', 'communication'],
},
{
name: 'invoice-generator',
owner: 'finance-team',
type: 'service',
description: 'Service for generating and managing invoices.',
tags: ['invoices', 'finance'],
},
{
name: 'document-editor',
owner: 'frontend-team',
type: 'website',
description: 'Web-based document editing interface.',
tags: ['documents', 'editor'],
},
{
name: 'service-discovery',
owner: 'devops-team',
type: 'service',
description: 'Service for discovering and registering available services.',
tags: ['discovery', 'services', 'devops'],
},
{
name: 'security-monitor',
owner: 'security-team',
type: 'service',
description: 'Service for monitoring security events and threats.',
tags: ['security', 'monitoring', 'threats'],
},
{
name: 'user-preferences',
owner: 'frontend-team',
type: 'website',
description: 'Interface for managing user preferences and settings.',
tags: ['user', 'preferences'],
},
{
name: 'data-validator',
owner: 'data-team',
type: 'service',
description: 'Service for validating data integrity and format.',
tags: ['data', 'validation'],
},
{
name: 'infrastructure-automation',
owner: 'devops-team',
type: 'other',
description:
'Tools for automating infrastructure provisioning and management.',
tags: ['infrastructure', 'automation', 'devops'],
},
{
name: 'notification-dispatcher',
owner: 'communication-team',
type: 'service',
description:
'Service for dispatching notifications to appropriate channels.',
tags: ['notifications', 'dispatch'],
},
{
name: 'analytics-collector',
owner: 'analytics-team',
type: 'service',
description: 'Service for collecting and aggregating analytics data.',
tags: ['analytics', 'collection', 'aggregation'],
},
{
name: 'file-processor',
owner: 'storage-team',
type: 'service',
description: 'Service for processing and managing files.',
tags: ['files', 'processing'],
},
{
name: 'search-analyzer',
owner: 'search-team',
type: 'service',
description: 'Service for analyzing search queries and results.',
tags: ['search', 'analysis'],
},
{
name: 'mobile-notifications',
owner: 'mobile-team',
type: 'service',
description: 'Service for sending notifications to mobile devices.',
tags: ['mobile', 'notifications'],
},
{
name: 'system-alerts',
owner: 'devops-team',
type: 'service',
description: 'Service for managing and dispatching system alerts.',
tags: ['alerts', 'system', 'monitoring'],
},
{
name: 'media-encoder',
owner: 'media-team',
type: 'service',
description: 'Service for encoding and processing media files.',
tags: ['media', 'encoding'],
},
{
name: 'user-authorization',
owner: 'security-team',
type: 'service',
description: 'Service for managing user permissions and access control.',
tags: ['authorization', 'security', 'user'],
},
{
name: 'data-aggregator',
owner: 'data-team',
type: 'service',
description: 'Service for aggregating data from multiple sources.',
tags: ['data', 'aggregation'],
},
{
name: 'admin-authentication',
owner: 'platform-team',
type: 'service',
description: 'Authentication service for administrative access.',
tags: ['admin', 'authentication', 'security'],
},
{
name: 'test-coverage',
owner: 'qa-team',
type: 'other',
description: 'Tools for measuring and reporting test coverage.',
tags: ['testing', 'coverage', 'qa'],
},
{
name: 'event-processor',
owner: 'platform-team',
type: 'service',
description: 'Service for processing and handling events.',
tags: ['events', 'processing'],
},
{
name: 'payment-validator',
owner: 'finance-team',
type: 'service',
description: 'Service for validating payment transactions.',
tags: ['payments', 'validation', 'finance'],
},
{
name: 'document-converter',
owner: 'frontend-team',
type: 'service',
description: 'Service for converting documents between different formats.',
tags: ['documents', 'conversion'],
},
{
name: 'service-health',
owner: 'devops-team',
type: 'service',
description: 'Service for monitoring and reporting service health status.',
tags: ['health', 'monitoring', 'services'],
},
{
name: 'security-logger',
owner: 'security-team',
type: 'service',
description: 'Service for logging security-related events and activities.',
tags: ['security', 'logging'],
},
{
name: 'user-analytics',
owner: 'frontend-team',
type: 'website',
description:
'Analytics dashboard for user behavior and engagement metrics.',
tags: ['analytics', 'user', 'metrics'],
},
{
name: 'data-cleaner',
owner: 'data-team',
type: 'service',
description: 'Service for cleaning and standardizing data.',
tags: ['data', 'cleaning'],
},
{
name: 'infrastructure-deployer',
owner: 'devops-team',
type: 'other',
description: 'Tools for deploying and managing infrastructure resources.',
tags: ['infrastructure', 'deployment', 'devops'],
},
{
name: 'notification-queue',
owner: 'communication-team',
type: 'service',
description: 'Queue system for managing notification delivery.',
tags: ['notifications', 'queue'],
},
{
name: 'analytics-exporter',
owner: 'analytics-team',
type: 'service',
description: 'Service for exporting analytics data in various formats.',
tags: ['analytics', 'export'],
},
{
name: 'file-validator',
owner: 'storage-team',
type: 'service',
description: 'Service for validating file integrity and format.',
tags: ['files', 'validation'],
},
{
name: 'search-optimizer',
owner: 'search-team',
type: 'service',
description: 'Service for optimizing search performance and relevance.',
tags: ['search', 'optimization'],
},
{
name: 'mobile-analytics',
owner: 'mobile-team',
type: 'service',
description: 'Analytics service specifically for mobile applications.',
tags: ['mobile', 'analytics'],
},
{
name: 'system-logger',
owner: 'devops-team',
type: 'service',
description: 'Service for logging system events and activities.',
tags: ['logging', 'system'],
},
{
name: 'media-validator',
owner: 'media-team',
type: 'service',
description: 'Service for validating media files and formats.',
tags: ['media', 'validation'],
},
{
name: 'user-audit',
owner: 'security-team',
type: 'service',
description: 'Service for auditing user activities and access.',
tags: ['audit', 'user', 'security'],
},
{
name: 'data-normalizer',
owner: 'data-team',
type: 'service',
description: 'Service for normalizing data formats and structures.',
tags: ['data', 'normalization'],
},
{
name: 'admin-authorization',
owner: 'platform-team',
type: 'service',
description: 'Authorization service for administrative functions.',
tags: ['admin', 'authorization', 'security'],
},
{
name: 'test-reporting',
owner: 'qa-team',
type: 'other',
description: 'Tools for generating and managing test reports.',
tags: ['testing', 'reporting', 'qa'],
},
{
name: 'event-aggregator',
owner: 'platform-team',
type: 'service',
description: 'Service for aggregating and processing events.',
tags: ['events', 'aggregation'],
},
{
name: 'payment-reconciler',
owner: 'finance-team',
type: 'service',
description: 'Service for reconciling payment transactions.',
tags: ['payments', 'reconciliation', 'finance'],
},
{
name: 'document-validator',
owner: 'frontend-team',
type: 'service',
description: 'Service for validating document formats and content.',
tags: ['documents', 'validation'],
},
{
name: 'service-monitor',
owner: 'devops-team',
type: 'service',
description: 'Service for monitoring service health and performance.',
tags: ['monitoring', 'services', 'health'],
},
{
name: 'security-validator',
owner: 'security-team',
type: 'service',
description: 'Service for validating security configurations and policies.',
tags: ['security', 'validation'],
},
];
@@ -45,11 +45,8 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
return (
<Component
ref={ref}
className={clsx(
'canon-Heading',
responsiveVariant && `canon-Heading--variant-${responsiveVariant}`,
className,
)}
className={clsx('canon-Heading', className)}
data-variant={responsiveVariant}
{...restProps}
>
{children}
@@ -22,32 +22,32 @@
margin: 0;
}
.canon-Heading--variant-display {
.canon-Heading[data-variant='display'] {
font-size: var(--canon-font-size-10);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title1 {
.canon-Heading[data-variant='title1'] {
font-size: var(--canon-font-size-9);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title2 {
.canon-Heading[data-variant='title2'] {
font-size: var(--canon-font-size-8);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title3 {
.canon-Heading[data-variant='title3'] {
font-size: var(--canon-font-size-7);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title4 {
.canon-Heading[data-variant='title4'] {
font-size: var(--canon-font-size-title4);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title5 {
.canon-Heading[data-variant='title5'] {
font-size: var(--canon-font-size-5);
font-weight: var(--canon-font-weight-bold);
}
@@ -39,16 +39,13 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
return (
<button
ref={ref}
className={clsx(
'canon-IconButton',
`canon-IconButton--size-${responsiveSize}`,
`canon-IconButton--variant-${responsiveVariant}`,
className,
)}
className={clsx('canon-IconButton', className)}
data-size={responsiveSize}
data-variant={responsiveVariant}
style={style}
{...rest}
>
<Icon name={icon} className="canon-IconButton--icon" />
<Icon name={icon} className="canon-IconButtonIcon" />
</button>
);
},
@@ -32,7 +32,7 @@
}
}
.canon-IconButton--variant-primary {
.canon-IconButton[data-variant='primary'] {
background-color: var(--canon-bg-solid);
color: var(--canon-fg-solid);
transition: background-color 150ms ease, box-shadow 150ms ease;
@@ -56,7 +56,7 @@
}
}
.canon-IconButton--variant-secondary {
.canon-IconButton[data-variant='secondary'] {
background-color: var(--canon-bg-surface-1);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-fg-primary);
@@ -82,24 +82,24 @@
}
}
.canon-IconButton--size-medium {
.canon-IconButton[data-size='medium'] {
font-size: var(--canon-font-size-4);
height: 40px;
width: 40px;
}
.canon-IconButton--size-small {
.canon-IconButton[data-size='small'] {
font-size: var(--canon-font-size-3);
height: 32px;
width: 32px;
}
.canon-IconButton--size-small .canon-IconButton--icon {
.canon-IconButton[data-size='small'] .canon-IconButtonIcon {
width: 1rem;
height: 1rem;
}
.canon-IconButton--size-medium .canon-IconButton--icon {
.canon-IconButton[data-size='medium'] .canon-IconButtonIcon {
width: 1.5rem;
height: 1.5rem;
}
+3 -8
View File
@@ -38,14 +38,9 @@ export const Link = forwardRef<HTMLElement, LinkProps>((props, ref) => {
const { renderElement } = useRender({
render,
props: {
className: clsx(
'canon-Link',
responsiveVariant && `canon-Link--variant-${responsiveVariant}`,
responsiveWeight && `canon-Link--weight-${responsiveWeight}`,
className,
),
responsiveVariant,
responsiveWeight,
className: clsx('canon-Link', className),
['data-variant']: responsiveVariant,
['data-weight']: responsiveWeight,
...restProps,
},
refs: [ref, internalRef],
@@ -36,30 +36,30 @@
}
}
.canon-Link--variant-body {
.canon-Link[data-variant='body'] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Link--variant-subtitle {
.canon-Link[data-variant='subtitle'] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Link--variant-caption {
.canon-Link[data-variant='caption'] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Link--variant-label {
.canon-Link[data-variant='label'] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Link--weight-regular {
.canon-Link[data-weight='regular'] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Link--weight-bold {
.canon-Link[data-weight='bold'] {
font-weight: var(--canon-font-weight-bold);
}
@@ -32,7 +32,12 @@ export const Default: Story = {
<>
<Menu.Trigger
render={props => (
<Button {...props} size="small">
<Button
{...props}
size="medium"
variant="secondary"
iconEnd="chevron-down"
>
Menu
</Button>
)}
@@ -40,9 +45,10 @@ export const Default: Story = {
<Menu.Portal>
<Menu.Positioner sideOffset={8} align="start">
<Menu.Popup>
<Menu.Item>Item 1</Menu.Item>
<Menu.Item>Item 2</Menu.Item>
<Menu.Item>Item 3</Menu.Item>
<Menu.Item>Settings</Menu.Item>
<Menu.Item>Invite new members</Menu.Item>
<Menu.Item>Download app</Menu.Item>
<Menu.Item>Log out</Menu.Item>
</Menu.Popup>
</Menu.Positioner>
</Menu.Portal>
@@ -57,3 +63,10 @@ export const Open: Story = {
open: true,
},
};
export const OpenOnHover: Story = {
args: {
...Default.args,
openOnHover: true,
},
};
@@ -21,14 +21,14 @@
width: 100%;
}
.canon-Select--label {
.canon-SelectLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-Select--description {
.canon-SelectDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -36,7 +36,7 @@
padding-top: var(--canon-space-1_5);
}
.canon-Select--error {
.canon-SelectError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -44,7 +44,7 @@
padding-top: var(--canon-space-1_5);
}
.canon-Select--trigger {
.canon-SelectTrigger {
box-sizing: border-box;
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
@@ -63,41 +63,41 @@
gap: var(--canon-space-2);
}
.canon-Select--trigger::placeholder {
.canon-SelectTrigger::placeholder {
color: var(--canon-fg-secondary);
}
.canon-Select--trigger:hover {
.canon-SelectTrigger:hover {
border-color: var(--canon-border-hover);
}
.canon-Select--trigger:focus-visible {
.canon-SelectTrigger:focus-visible {
border-color: var(--canon-border-pressed);
outline: 0;
}
.canon-Select--trigger[data-invalid] {
.canon-SelectTrigger[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-Select--trigger[data-invalid]:hover {
.canon-SelectTrigger[data-invalid]:hover {
border-width: 2px;
}
.canon-Select--trigger[data-invalid]:focus-visible {
.canon-SelectTrigger[data-invalid]:focus-visible {
border-width: 2px;
}
.canon-Select--trigger[data-disabled] {
.canon-SelectTrigger[data-disabled] {
cursor: not-allowed;
border-color: var(--canon-border-disabled);
color: var(--canon-fg-disabled);
}
.canon-Select--trigger-size-small {
.canon-SelectTrigger[data-size='small'] {
height: 2rem;
}
.canon-Select--trigger-size-medium {
.canon-SelectTrigger[data-size='medium'] {
height: 3rem;
}
@@ -106,11 +106,11 @@
transition: transform 0.2s ease;
}
.canon-Select--trigger[data-popup-open] .canon-SelectIcon {
.canon-SelectTrigger[data-popup-open] .canon-SelectIcon {
transform: rotate(180deg);
}
.canon-Select--popup {
.canon-SelectPopup {
box-sizing: border-box;
max-height: var(--available-height);
overflow-y: auto;
@@ -125,13 +125,13 @@
transition: transform 150ms, opacity 150ms;
}
.canon-Select--popup[data-starting-style],
.canon-Select--popup[data-ending-style] {
.canon-SelectPopup[data-starting-style],
.canon-SelectPopup[data-ending-style] {
opacity: 0;
transform: scale(0.9);
}
.canon-Select--item {
.canon-SelectItem {
position: relative;
width: var(--anchor-width);
display: grid;
@@ -149,13 +149,13 @@
outline: none;
}
.canon-Select--item[data-highlighted] {
.canon-SelectItem[data-highlighted] {
z-index: 0;
position: relative;
color: var(--canon-fg-primary);
}
.canon-Select--item[data-highlighted]::before {
.canon-SelectItem[data-highlighted]::before {
content: '';
z-index: -1;
position: absolute;
@@ -165,37 +165,37 @@
background-color: var(--canon-bg-tint-hover);
}
.canon-Select--item[data-disabled] {
.canon-SelectItem[data-disabled] {
cursor: not-allowed;
color: var(--canon-fg-disabled);
}
.canon-Select--item-indicator {
.canon-SelectItemIndicator {
grid-area: icon;
display: flex;
align-items: center;
justify-content: center;
}
.canon-Select--item-text {
.canon-SelectItemText {
flex: 1;
grid-area: text;
}
.canon-Select--required {
.canon-SelectRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
margin-left: var(--canon-space-1);
}
.canon-Select--icon {
.canon-SelectIcon {
display: flex;
align-items: center;
justify-content: center;
}
.canon-Select--value {
.canon-SelectValue {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
+12 -14
View File
@@ -48,10 +48,10 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
return (
<div className={clsx('canon-Select', className)} style={style} ref={ref}>
{label && (
<label className="canon-Select--label" htmlFor={selectId}>
<label className="canon-SelectLabel" htmlFor={selectId}>
{label}
{required && (
<span aria-hidden="true" className="canon-Select--required">
<span aria-hidden="true" className="canon-SelectRequired">
(Required)
</span>
)}
@@ -60,35 +60,33 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
<SelectPrimitive.Root {...rest}>
<SelectPrimitive.Trigger
id={selectId}
className={clsx('canon-Select--trigger', {
'canon-Select--trigger-size-small': responsiveSize === 'small',
'canon-Select--trigger-size-medium': responsiveSize === 'medium',
})}
className="canon-SelectTrigger"
data-size={responsiveSize}
data-invalid={error}
>
<SelectPrimitive.Value
className="canon-Select--value"
className="canon-SelectValue"
placeholder={placeholder}
/>
<SelectPrimitive.Icon className="canon-Select--icon">
<SelectPrimitive.Icon className="canon-SelectIcon">
<Icon name="chevron-down" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
<SelectPrimitive.Portal>
<SelectPrimitive.Backdrop />
<SelectPrimitive.Positioner>
<SelectPrimitive.Popup className="canon-Select--popup">
<SelectPrimitive.Popup className="canon-SelectPopup">
{options?.map(option => (
<SelectPrimitive.Item
key={option.value}
value={option.value}
disabled={option.disabled}
className="canon-Select--item"
className="canon-SelectItem"
>
<SelectPrimitive.ItemIndicator className="canon-Select--item-indicator">
<SelectPrimitive.ItemIndicator className="canon-SelectItemIndicator">
<Icon name="check" />
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemText className="canon-Select--item-text">
<SelectPrimitive.ItemText className="canon-SelectItemText">
{option.label}
</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
@@ -98,12 +96,12 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
</SelectPrimitive.Portal>
</SelectPrimitive.Root>
{description && (
<p className="canon-Select--description" id={descriptionId}>
<p className="canon-SelectDescription" id={descriptionId}>
{description}
</p>
)}
{error && (
<p className="canon-Select--error" id={errorId} role="alert">
<p className="canon-SelectError" id={errorId} role="alert">
{error}
</p>
)}
@@ -14,6 +14,10 @@
* limitations under the License.
*/
import { ComponentType } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { Table } from '../Table';
const invoices = [
{
invoice: 'INV001',
@@ -59,28 +63,15 @@ const invoices = [
},
];
import { ComponentType } from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from '../Table';
const meta = {
title: 'Components/Table',
component: Table,
component: Table.Root,
subcomponents: {
TableBody: TableBody as ComponentType<unknown>,
TableCell: TableCell as ComponentType<unknown>,
TableFooter: TableFooter as ComponentType<unknown>,
TableHead: TableHead as ComponentType<unknown>,
TableHeader: TableHeader as ComponentType<unknown>,
TableRow: TableRow as ComponentType<unknown>,
Body: Table.Body as ComponentType<unknown>,
Cell: Table.Cell as ComponentType<unknown>,
Head: Table.Head as ComponentType<unknown>,
Header: Table.Header as ComponentType<unknown>,
Row: Table.Row as ComponentType<unknown>,
},
} satisfies Meta<typeof Table>;
@@ -89,31 +80,27 @@ type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => (
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[100px]">Invoice</TableHead>
<TableHead>Status</TableHead>
<TableHead>Method</TableHead>
<TableHead className="text-right">Amount</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head className="w-[100px]">Invoice</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Method</Table.Head>
<Table.Head className="text-right">Amount</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{invoices.map(invoice => (
<TableRow key={invoice.invoice}>
<TableCell className="font-medium">{invoice.invoice}</TableCell>
<TableCell>{invoice.paymentStatus}</TableCell>
<TableCell>{invoice.paymentMethod}</TableCell>
<TableCell className="text-right">{invoice.totalAmount}</TableCell>
</TableRow>
<Table.Row key={invoice.invoice}>
<Table.Cell className="font-medium">{invoice.invoice}</Table.Cell>
<Table.Cell>{invoice.paymentStatus}</Table.Cell>
<Table.Cell>{invoice.paymentMethod}</Table.Cell>
<Table.Cell className="text-right">
{invoice.totalAmount}
</Table.Cell>
</Table.Row>
))}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Total</TableCell>
<TableCell className="text-right">$2,500.00</TableCell>
</TableRow>
</TableFooter>
</Table>
</Table.Body>
</Table.Root>
),
};
+28 -48
View File
@@ -13,103 +13,83 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
/** @public */
const Table = forwardRef<
import { forwardRef } from 'react';
import clsx from 'clsx';
import { TableCell } from './TableCell/TableCell';
import { TableCellText } from './TableCellText/TableCellText';
import { TableCellLink } from './TableCellLink/TableCellLink';
const TableRoot = forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="table">
<table ref={ref} className={className} {...props} />
</div>
<table ref={ref} className={clsx('canon-TableRoot', className)} {...props} />
));
Table.displayName = 'Table';
TableRoot.displayName = 'TableRoot';
/** @public */
const TableHeader = forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead
ref={ref}
className={['table-header', className].join(' ')}
className={clsx('canon-TableHeader', className)}
{...props}
/>
));
TableHeader.displayName = 'TableHeader';
/** @public */
const TableBody = forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody ref={ref} className={['table-body', className].join(' ')} {...props} />
<tbody ref={ref} className={clsx('canon-TableBody', className)} {...props} />
));
TableBody.displayName = 'TableBody';
/** @public */
const TableFooter = forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={['table-footer', className].join(' ')}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';
/** @public */
const TableRow = forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr ref={ref} className={['table-row', className].join(' ')} {...props}>
<tr ref={ref} className={clsx('canon-TableRow', className)} {...props}>
{props.children}
</tr>
));
TableRow.displayName = 'TableRow';
/** @public */
const TableHead = forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th ref={ref} className={['table-head', className].join(' ')} {...props} />
<th ref={ref} className={clsx('canon-TableHead', className)} {...props} />
));
TableHead.displayName = 'TableHead';
/** @public */
const TableCell = forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td ref={ref} className={['table-cell', className].join(' ')} {...props} />
));
TableCell.displayName = 'TableCell';
/** @public */
const TableCaption = forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={['table-caption', className].join(' ')}
className={clsx('canon-TableCaption', className)}
{...props}
/>
));
TableCaption.displayName = 'TableCaption';
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
/**
* Table component for displaying tabular data
* @public
*/
export const Table = {
Root: TableRoot,
Header: TableHeader,
Body: TableBody,
Head: TableHead,
Row: TableRow,
Cell: TableCell,
CellText: TableCellText,
CellLink: TableCellLink,
Caption: TableCaption,
};
@@ -0,0 +1,32 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { TableCell } from './TableCell';
const meta = {
title: 'Components/Table/TableCell',
component: TableCell,
} satisfies Meta<typeof TableCell>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Hello world',
},
};
@@ -0,0 +1,20 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-TableCell {
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
}
@@ -0,0 +1,29 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import clsx from 'clsx';
/** @public */
const TableCell = forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td ref={ref} className={clsx('canon-TableCell', className)} {...props} />
));
TableCell.displayName = 'TableCell';
export { TableCell };
@@ -0,0 +1,40 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { TableCellLink } from './TableCellLink';
const meta = {
title: 'Components/Table/TableCellLink',
component: TableCellLink,
} satisfies Meta<typeof TableCellLink>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: 'I am a link',
href: 'https://canon.backstage.io',
},
};
export const WithDescription: Story = {
args: {
...Default.args,
description: 'This is a description',
},
};
@@ -0,0 +1,21 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-TableCellLink {
display: flex;
flex-direction: column;
gap: var(--canon-space-0_5);
}
@@ -0,0 +1,46 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import clsx from 'clsx';
import { TableCellLinkProps } from './types';
import { Text } from '../../Text/Text';
import { Link } from '../../Link/Link';
/** @public */
const TableCellLink = forwardRef<HTMLDivElement, TableCellLinkProps>(
({ className, title, description, href, render, ...props }, ref) => (
<div
ref={ref}
className={clsx('canon-TableCellLink', className)}
{...props}
>
{title && (
<Link href={href} render={render}>
{title}
</Link>
)}
{description && (
<Text variant="body" color="secondary">
{description}
</Text>
)}
</div>
),
);
TableCellLink.displayName = 'TableCellLink';
export { TableCellLink };
@@ -0,0 +1,26 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { useRender } from '@base-ui-components/react/use-render';
/** @public */
export interface TableCellLinkProps
extends React.TdHTMLAttributes<HTMLTableCellElement> {
title: string;
description?: string;
href: string;
render?: useRender.ComponentProps<'a'>['render'];
}
@@ -0,0 +1,39 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Meta, StoryObj } from '@storybook/react';
import { TableCellText } from './TableCellText';
const meta = {
title: 'Components/Table/TableCellText',
component: TableCellText,
} satisfies Meta<typeof TableCellText>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: 'Hello world',
},
};
export const WithDescription: Story = {
args: {
...Default.args,
description: 'This is a description',
},
};
@@ -0,0 +1,21 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.canon-TableCellText {
display: flex;
flex-direction: column;
gap: var(--canon-space-0_5);
}
@@ -0,0 +1,41 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { forwardRef } from 'react';
import clsx from 'clsx';
import { TableCellTextProps } from './types';
import { Text } from '../../Text/Text';
/** @public */
const TableCellText = forwardRef<HTMLDivElement, TableCellTextProps>(
({ className, title, description, ...props }, ref) => (
<div
ref={ref}
className={clsx('canon-TableCellText', className)}
{...props}
>
{title && <Text variant="body">{title}</Text>}
{description && (
<Text variant="body" color="secondary">
{description}
</Text>
)}
</div>
),
);
TableCellText.displayName = 'TableCellText';
export { TableCellText };
@@ -0,0 +1,22 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** @public */
export interface TableCellTextProps
extends React.TdHTMLAttributes<HTMLTableCellElement> {
title: string;
description?: string;
}
+4 -9
View File
@@ -13,12 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export {
Table,
TableHead,
TableRow,
TableHeader,
TableCell,
TableBody,
TableFooter,
} from './Table';
export * from './Table';
export * from './TableCellText/types';
export * from './TableCellLink/types';
+12 -48
View File
@@ -1,61 +1,25 @@
.table {
position: relative;
overflow: auto;
.canon-TableRoot {
width: 100%;
background-color: var(--canon-bg-surface-1);
border-radius: var(--canon-radius-2);
padding-bottom: var(--canon-space-0_5);
padding-top: var(--canon-space-0_5);
font-size: var(--canon-font-size-3);
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-regular);
table {
width: 100%;
caption-side: bottom;
font-size: var(--canon-font-size-sm);
border-collapse: collapse;
}
caption-side: bottom;
border-collapse: collapse;
}
.table-head {
.canon-TableHead {
text-align: left;
padding: var(--canon-space-3);
font-size: var(--canon-font-size-3);
color: var(--canon-fg-primary);
}
.table-body {
tr:last-child {
border-bottom: none;
}
.canon-TableBody {
color: var(--canon-fg-primary);
}
.table-row {
.canon-TableRow {
border-bottom: 1px solid var(--canon-border);
transition: color 0.2s ease-in-out;
&:hover td {
background-color: var(--canon-bg-tint);
}
& .table-cell:first-child {
border-top-left-radius: var(--canon-radius-2);
border-bottom-left-radius: var(--canon-radius-2);
box-shadow: inset 4px 2px 0 0 var(--canon-bg-surface-1),
inset 4px -2px 0 0 var(--canon-bg-surface-1);
padding-left: var(--canon-space-3);
}
& .table-cell:last-child {
border-top-right-radius: var(--canon-radius-2);
border-bottom-right-radius: var(--canon-radius-2);
box-shadow: inset -4px 2px 0 0 var(--canon-bg-surface-1),
inset -4px -2px 0 0 var(--canon-bg-surface-1);
padding-right: var(--canon-space-3);
}
}
.table-cell {
padding: var(--canon-space-3);
background-color: var(--canon-bg);
box-shadow: inset 0px 2px 0 0 var(--canon-bg-surface-1),
inset 0px -2px 0 0 var(--canon-bg-surface-1);
.canon-TableBody .canon-TableRow:hover {
background-color: var(--canon-gray-2);
}
+4 -7
View File
@@ -41,13 +41,10 @@ export const Text = forwardRef<HTMLParagraphElement, TextProps>(
return (
<p
ref={ref}
className={clsx(
'canon-Text',
responsiveVariant && `canon-Text--variant-${responsiveVariant}`,
responsiveWeight && `canon-Text--weight-${responsiveWeight}`,
responsiveColor && `canon-Text--color-${responsiveColor}`,
className,
)}
className={clsx('canon-Text', className)}
data-variant={responsiveVariant}
data-weight={responsiveWeight}
data-color={responsiveColor}
style={style}
{...restProps}
>
+11 -12
View File
@@ -16,55 +16,54 @@
.canon-Text {
font-family: var(--canon-font-regular);
color: var(--canon-fg-primary);
padding: 0;
margin: 0;
}
.canon-Text--variant-body {
.canon-Text[data-variant='body'] {
font-size: var(--canon-font-size-3);
line-height: 140%;
}
.canon-Text--variant-subtitle {
.canon-Text[data-variant='subtitle'] {
font-size: var(--canon-font-size-4);
line-height: 140%;
}
.canon-Text--variant-caption {
.canon-Text[data-variant='caption'] {
font-size: var(--canon-font-size-2);
line-height: 140%;
}
.canon-Text--variant-label {
.canon-Text[data-variant='label'] {
font-size: var(--canon-font-size-1);
line-height: 140%;
}
.canon-Text--weight-regular {
.canon-Text[data-weight='regular'] {
font-weight: var(--canon-font-weight-regular);
}
.canon-Text--weight-bold {
.canon-Text[data-weight='bold'] {
font-weight: var(--canon-font-weight-bold);
}
.canon-Text--color-primary {
.canon-Text[data-color='primary'] {
color: var(--canon-fg-primary);
}
.canon-Text--color-secondary {
.canon-Text[data-color='secondary'] {
color: var(--canon-fg-secondary);
}
.canon-Text--color-danger {
.canon-Text[data-color='danger'] {
color: var(--canon-fg-danger);
}
.canon-Text--color-warning {
.canon-Text[data-color='warning'] {
color: var(--canon-fg-warning);
}
.canon-Text--color-success {
.canon-Text[data-color='success'] {
color: var(--canon-fg-success);
}
@@ -21,14 +21,14 @@
width: 100%;
}
.canon-TextField--label {
.canon-TextFieldLabel {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-TextField--description {
.canon-TextFieldDescription {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-secondary);
@@ -36,7 +36,7 @@
padding-top: var(--canon-space-1_5);
}
.canon-TextField--error {
.canon-TextFieldError {
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-danger);
@@ -44,7 +44,7 @@
padding-top: var(--canon-space-1_5);
}
.canon-TextField--input {
.canon-TextFieldInput {
border-radius: var(--canon-radius-3);
border: 1px solid var(--canon-border);
padding: 0 var(--canon-space-4);
@@ -57,39 +57,39 @@
width: 100%;
}
.canon-TextField--input::placeholder {
.canon-TextFieldInput::placeholder {
color: var(--canon-fg-secondary);
}
.canon-TextField--input:hover {
.canon-TextFieldInput:hover {
border-color: var(--canon-border-hover);
}
.canon-TextField--input:focus-visible {
.canon-TextFieldInput:focus-visible {
outline-color: var(--canon-border-pressed);
outline-width: 0px;
border-color: var(--canon-border-pressed);
}
.canon-TextField--input[data-invalid] {
.canon-TextFieldInput[data-invalid] {
border-color: var(--canon-fg-danger);
}
.canon-TextField--input[data-disabled] {
.canon-TextFieldInput[data-disabled] {
opacity: 0.5;
cursor: not-allowed;
border: 1px solid var(--canon-border-disabled);
}
.canon-TextField--input-size-small {
.canon-TextFieldInput[data-size='small'] {
height: 2rem;
}
.canon-TextField--input-size-medium {
.canon-TextFieldInput[data-size='medium'] {
height: 2.5rem;
}
.canon-TextField--required {
.canon-TextFieldRequired {
color: var(--canon-fg-secondary);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
@@ -50,10 +50,10 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
ref={ref}
>
{label && (
<label className="canon-TextField--label" htmlFor={inputId}>
<label className="canon-TextFieldLabel" htmlFor={inputId}>
{label}
{required && (
<span aria-hidden="true" className="canon-TextField--required">
<span aria-hidden="true" className="canon-TextFieldRequired">
(Required)
</span>
)}
@@ -61,10 +61,8 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
)}
<Input
id={inputId}
className={clsx('canon-TextField--input', {
'canon-TextField--input-size-small': responsiveSize === 'small',
'canon-TextField--input-size-medium': responsiveSize === 'medium',
})}
className="canon-TextFieldInput"
data-size={responsiveSize}
aria-labelledby={label ? inputId : undefined}
aria-describedby={clsx({
[descriptionId]: description,
@@ -75,12 +73,12 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
{...rest}
/>
{description && (
<p className="canon-TextField--description" id={descriptionId}>
<p className="canon-TextFieldDescription" id={descriptionId}>
{description}
</p>
)}
{error && (
<p className="canon-TextField--error" id={errorId} role="alert">
<p className="canon-TextFieldError" id={errorId} role="alert">
{error}
</p>
)}
+5
View File
@@ -16,12 +16,17 @@
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/DataTable/Root/DataTableRoot.styles.css';
@import '../components/DataTable/Pagination/DataTablePagination.styles.css';
@import '../components/Flex/styles.css';
@import '../components/Grid/styles.css';
@import '../components/Container/styles.css';
@import '../components/Icon/styles.css';
@import '../components/Checkbox/styles.css';
@import '../components/Table/styles.css';
@import '../components/Table/TableCell/TableCell.styles.css';
@import '../components/Table/TableCellText/TableCellText.styles.css';
@import '../components/Table/TableCellLink/TableCellLink.styles.css';
@import '../components/Text/styles.css';
@import '../components/Heading/styles.css';
@import '../components/IconButton/styles.css';
+28 -6
View File
@@ -25,18 +25,40 @@ export const breakpoints: { name: string; id: Breakpoint; value: number }[] = [
{ name: 'Extra Large', id: 'xl', value: 1536 },
];
export const useBreakpoint = (): Breakpoint => {
// TODO: Perhaps refactor for useMediaQuery to accept an array of queries
/** @public */
export const useBreakpoint = () => {
// Call all media queries at the top level
const matches = breakpoints.map(breakpoint => {
const match = useMediaQuery(`(min-width: ${breakpoint.value}px)`);
return match;
return useMediaQuery(`(min-width: ${breakpoint.value}px)`);
});
// Pre-calculate all the up/down values we need
const upMatches = new Map(
breakpoints.map(bp => [bp.id, useMediaQuery(`(min-width: ${bp.value}px)`)]),
);
const downMatches = new Map(
breakpoints.map(bp => [
bp.id,
useMediaQuery(`(max-width: ${bp.value - 1}px)`),
]),
);
let breakpoint: Breakpoint = breakpoints[0].id;
for (let i = matches.length - 1; i >= 0; i--) {
if (matches[i]) {
return breakpoints[i].id;
breakpoint = breakpoints[i].id;
break;
}
}
return breakpoints[0].id;
return {
breakpoint,
up: (key: Breakpoint): boolean => {
return upMatches.get(key) ?? false;
},
down: (key: Breakpoint): boolean => {
return downMatches.get(key) ?? false;
},
};
};
@@ -20,12 +20,10 @@ import { useBreakpoint, breakpoints } from './useBreakpoint';
type ResponsiveValue = string | Partial<Record<Breakpoint, string>>;
export const useResponsiveValue = (value: ResponsiveValue) => {
const currentBreakpoint = useBreakpoint();
const { breakpoint } = useBreakpoint();
if (typeof value === 'object') {
const index = breakpoints.findIndex(
breakpoint => breakpoint.id === currentBreakpoint,
);
const index = breakpoints.findIndex(b => b.id === breakpoint);
for (let i = index; i >= 0; i--) {
if (value[breakpoints[i].id]) {
+4
View File
@@ -33,6 +33,7 @@ export * from './components/Heading';
// UI components
export * from './components/Button';
export * from './components/DataTable';
export * from './components/Icon';
export * from './components/IconButton';
export * from './components/Checkbox';
@@ -47,3 +48,6 @@ export * from './components/Select';
// Types
export * from './types';
export * from './props';
// Hooks
export { useBreakpoint } from './hooks/useBreakpoint';
@@ -18,9 +18,11 @@ Commands:
config:docs
config:print
config:schema
create-github-app
help [command]
info
migrate [command]
new
package [command]
repo [command]
versions:bump
@@ -132,6 +134,15 @@ Options:
--version
```
### `backstage-cli-alpha create-github-app`
```
Usage: program [options] <github-org>
Options:
-h, --help
```
### `backstage-cli-alpha info`
```
@@ -204,6 +215,23 @@ Options:
-h, --help
```
### `backstage-cli-alpha new`
```
Usage: program [options]
Options:
--baseVersion <version>
--license <license>
--no-private
--npm-registry <URL>
--option <name>=<value>
--scope <scope>
--select <name>
--skip-install
-h, --help
```
### `backstage-cli-alpha package`
```
+2
View File
@@ -25,6 +25,8 @@ import chalk from 'chalk';
),
);
const initializer = new CliInitializer();
initializer.add(import('./modules/new/alpha'));
initializer.add(import('./modules/create-github-app/alpha'));
initializer.add(import('./modules/info/alpha'));
initializer.add(import('./modules/config/alpha'));
initializer.add(import('./modules/build/alpha'));
+6 -61
View File
@@ -15,7 +15,6 @@
*/
import { Command } from 'commander';
import { lazy } from '../lib/lazy';
import { registerCommands as registerConfigCommands } from '../modules/config';
import {
registerPackageCommands as registerPackageBuildCommands,
@@ -40,6 +39,9 @@ import {
registerPackageCommands as registerMaintenancePackageCommands,
registerRepoCommands as registerMaintenanceRepoCommands,
} from '../modules/maintenance';
import { removed } from '../lib/removed';
import { registerCommands as registerNewCommands } from '../modules/new';
import { registerCommands as registerCreateGithubAppCommands } from '../modules/create-github-app';
export function registerRepoCommand(program: Command) {
const command = program
@@ -66,62 +68,16 @@ export function registerScriptCommand(program: Command) {
}
export function registerCommands(program: Command) {
program
.command('new')
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./new/new'), 'default'));
registerConfigCommands(program);
registerRepoCommand(program);
registerScriptCommand(program);
registerMigrateCommand(program);
registerBuildCommands(program);
registerInfoCommands(program);
program
.command('create-github-app <github-org>')
.description('Create new GitHub App in your organization.')
.action(lazy(() => import('./create-github-app'), 'default'));
registerNewCommands(program);
registerCreateGithubAppCommands(program);
// Notifications for removed commands
program
.command('create')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('create-plugin')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('plugin:diff')
.allowUnknownOption(true)
@@ -145,14 +101,3 @@ export function registerCommands(program: Command) {
program.command('install').allowUnknownOption(true).action(removed());
program.command('onboard').allowUnknownOption(true).action(removed());
}
function removed(message?: string) {
return () => {
console.error(
message
? `This command has been removed, ${message}`
: 'This command has been removed',
);
process.exit(1);
};
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export function removed(message?: string) {
return () => {
console.error(
message
? `This command has been removed, ${message}`
: 'This command has been removed',
);
process.exit(1);
};
}
@@ -0,0 +1,38 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createCliPlugin } from '../../wiring/factory';
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
export default createCliPlugin({
pluginId: 'new',
init: async reg => {
reg.addCommand({
path: ['create-github-app'],
description: 'Create new GitHub App in your organization.',
execute: async ({ args }) => {
const command = new Command();
const defaultCommand = command
.argument('<github-org>')
.action(
lazy(() => import('./commands/create-github-app'), 'default'),
);
await defaultCommand.parseAsync(args, { from: 'user' });
},
});
},
});
@@ -18,7 +18,7 @@ import fs from 'fs-extra';
import chalk from 'chalk';
import { stringify as stringifyYaml } from 'yaml';
import inquirer, { Question, Answers } from 'inquirer';
import { paths } from '../../lib/paths';
import { paths } from '../../../../lib/paths';
import { GithubCreateAppServer } from './GithubCreateAppServer';
import openBrowser from 'react-dev-utils/openBrowser';
@@ -0,0 +1,29 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
export function registerCommands(program: Command) {
program
.command('create-github-app <github-org>')
.description('Create new GitHub App in your organization.')
.action(
lazy(
() => import('../create-github-app/commands/create-github-app'),
'default',
),
);
}
+86
View File
@@ -0,0 +1,86 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { createCliPlugin } from '../../wiring/factory';
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
import { removed } from '../../lib/removed';
export default createCliPlugin({
pluginId: 'new',
init: async reg => {
reg.addCommand({
path: ['new'],
description:
'Open up an interactive guide to creating new things in your app',
execute: async ({ args }) => {
const command = new Command();
const defaultCommand = command
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./commands/new'), 'default'));
await defaultCommand.parseAsync(args, { from: 'user' });
},
});
reg.addCommand({
path: ['create'],
description: 'Create a new Backstage app',
deprecated: true,
execute: async () => {
removed("use 'backstage-cli new' instead")();
},
});
reg.addCommand({
path: ['create-plugin'],
description: 'Create a new Backstage plugin',
deprecated: true,
execute: async () => {
removed("use 'backstage-cli new' instead")();
},
});
},
});
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { createNewPackage } from '../../lib/new/createNewPackage';
import { createNewPackage } from '../lib/createNewPackage';
import { default as newCommand } from './new';
jest.mock('../../lib/new/createNewPackage');
jest.mock('../lib/createNewPackage');
describe.each([
[undefined, undefined, undefined],
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createNewPackage } from '../../lib/new/createNewPackage';
import { createNewPackage } from '../lib/createNewPackage';
type ArgOptions = {
option: string[];
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Command } from 'commander';
import { lazy } from '../../lib/lazy';
import { removed } from '../../lib/removed';
export function registerCommands(program: Command) {
program
.command('new')
.storeOptionsAsProperties(false)
.description(
'Open up an interactive guide to creating new things in your app',
)
.option(
'--select <name>',
'Select the thing you want to be creating upfront',
)
.option(
'--option <name>=<value>',
'Pre-fill options for the creation process',
(opt, arr: string[]) => [...arr, opt],
[],
)
.option(
'--skip-install',
`Skips running 'yarn install' and 'yarn lint --fix'`,
)
.option('--scope <scope>', 'The scope to use for new packages')
.option(
'--npm-registry <URL>',
'The package registry to use for new packages',
)
.option(
'--baseVersion <version>',
'The version to use for any new packages (default: 0.1.0)',
)
.option(
'--license <license>',
'The license to use for any new packages (default: Apache-2.0)',
)
.option('--no-private', 'Do not mark new packages as private')
.action(lazy(() => import('./commands/new'), 'default'));
program
.command('create')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
program
.command('create-plugin')
.allowUnknownOption(true)
.action(removed("use 'backstage-cli new' instead"));
}
@@ -24,9 +24,9 @@ import startCase from 'lodash/startCase';
import upperCase from 'lodash/upperCase';
import upperFirst from 'lodash/upperFirst';
import lowerFirst from 'lodash/lowerFirst';
import { Lockfile } from '../../versioning';
import { paths } from '../../paths';
import { createPackageVersionProvider } from '../../version';
import { Lockfile } from '../../../../lib/versioning';
import { paths } from '../../../../lib/paths';
import { createPackageVersionProvider } from '../../../../lib/version';
const builtInHelpers = {
camelCase,
@@ -15,8 +15,8 @@
*/
import { assertError } from '@backstage/errors';
import { addCodeownersEntry } from '../../codeowners';
import { Task } from '../../tasks';
import { addCodeownersEntry } from '../../../../lib/codeowners';
import { Task } from '../../../../lib/tasks';
import {
PortableTemplate,
PortableTemplateConfig,
@@ -16,8 +16,8 @@
import fs from 'fs-extra';
import upperFirst from 'lodash/upperFirst';
import camelCase from 'lodash/camelCase';
import { paths } from '../../paths';
import { Task } from '../../tasks';
import { paths } from '../../../../lib/paths';
import { Task } from '../../../../lib/tasks';
import { PortableTemplateInput } from '../types';
export async function installNewPackage(input: PortableTemplateInput) {
@@ -17,7 +17,7 @@
import { relative as relativePath } from 'node:path';
import { writeTemplateContents } from './writeTemplateContents';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { paths } from '../../paths';
import { paths } from '../../../../lib/paths';
const baseConfig = {
version: '0.1.0',

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