diff --git a/.changeset/neat-ducks-wish.md b/.changeset/neat-ducks-wish.md
new file mode 100644
index 0000000000..2568e438c9
--- /dev/null
+++ b/.changeset/neat-ducks-wish.md
@@ -0,0 +1,5 @@
+---
+'@backstage/canon': minor
+---
+
+Improve class name structure using data attributes instead of class names.
diff --git a/.changeset/quiet-rockets-throw.md b/.changeset/quiet-rockets-throw.md
new file mode 100644
index 0000000000..6ba34beeff
--- /dev/null
+++ b/.changeset/quiet-rockets-throw.md
@@ -0,0 +1,5 @@
+---
+'@backstage/canon': patch
+---
+
+Add new DataTable component and update Table component styles.
diff --git a/.changeset/rich-phones-whisper.md b/.changeset/rich-phones-whisper.md
new file mode 100644
index 0000000000..75f0fc0c35
--- /dev/null
+++ b/.changeset/rich-phones-whisper.md
@@ -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.
diff --git a/canon-docs/public/css-classname-structure.png b/canon-docs/public/css-classname-structure.png
new file mode 100644
index 0000000000..df9894ff2b
Binary files /dev/null and b/canon-docs/public/css-classname-structure.png differ
diff --git a/canon-docs/src/app/(docs)/theme/theming/page.mdx b/canon-docs/src/app/(docs)/theme/theming/page.mdx
index e3fc42da44..665755c268 100644
--- a/canon-docs/src/app/(docs)/theme/theming/page.mdx
+++ b/canon-docs/src/app/(docs)/theme/theming/page.mdx
@@ -29,8 +29,100 @@ Here's an example of how your theme.css file should look like:
+## 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.
+
+
+
+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, ...
+
+
+
+
+ Prop
+ Description
+
+
+
+
+
+ --canon-black
+
+
+ Pure black color. This one should be the same in light and dark themes.
+
+
+
+
+ --canon-white
+
+
+ Pure white color. This one should be the same in light and dark themes.
+
+
+
+
+ --canon-gray-1
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-2
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-3
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-4
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-5
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-6
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-7
+
+ You can use these mostly for backgrounds colors.
+
+
+
+ --canon-gray-8
+
+ You can use these mostly for backgrounds colors.
+
+
+
+
### 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.
Use for any panels or elevated surfaces.
-
-
-
-### 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, ...
-
-
-
-
- Prop
- Description
-
-
-
- --canon-bg-accent
+ --canon-bg-surface-2
- Used mostly for primary interactive components.
+ Use for any panels or elevated surfaces.
- --canon-bg-accent-hover
-
-
- Used for interactive components when they are hovered.
+ --canon-bg-solid
+ Used for solid background colors.
- --canon-bg-accent-focus
-
-
- Used for interactive components when they are in focus.
+ --canon-bg-solid-hover
+ Used for solid background colors when hovered.
- --canon-bg-accent-disabled
+ --canon-bg-solid-pressed
+ Used for solid background colors when pressed.
+
+
- Used for interactive components when they are disabled.
+ --canon-bg-solid-disabled
+ Used for solid background colors when disabled.--canon-bg-tint
- Used for secondary interactive components.
+ Used for tint background colors.--canon-bg-tint-hover
-
- Used for interactive components when they are hovered.
-
+ Used for tint background colors when hovered.--canon-bg-tint-focus
-
- Used for interactive components when they are active.
-
+ Used for tint background colors when active.--canon-bg-tint-disabled
-
- Used for interactive components when they are disabled.
-
+ Used for tint background colors when disabled.
-
-
-
-### Status background colors
-
-These backgrounds are intended for non-interactive elements to visually convey a specific state.
-
-
-
-
- Prop
- Description
-
-
- --canon-bg-danger
@@ -189,7 +243,7 @@ are prefixed with `fg` to make it easier to identify.
--canon-fg-primary
- 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.
@@ -197,7 +251,7 @@ are prefixed with `fg` to make it easier to identify.
--canon-fg-secondary
- 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.
@@ -205,7 +259,7 @@ are prefixed with `fg` to make it easier to identify.
--canon-fg-link
- 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.
@@ -213,38 +267,64 @@ are prefixed with `fg` to make it easier to identify.
--canon-fg-link-hover
- 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.
- --canon-fg-accent
+ --canon-fg-disabled
+
+
+ It should be used on top of main background surfaces.
+
+
+
+
+ --canon-fg-solid
+
+
+ It should be used on top of solid background colors.
- It should be used on top of `--canon-bg-accent`.--canon-fg-tint
- It should be used on top of `--canon-bg-tint`.
+
+ It should be used on top of tint background colors.
+
+
+
+
+ --canon-fg-tint-disabled
+
+
+ It should be used on top of tint background colors when disabled.
+ --canon-fg-danger
- It should be used on top of `--canon-bg-danger`.
+
+ It should be used on top of danger background colors.
+ --canon-fg-warning
- It should be used on top of `--canon-bg-warning`.
+
+ It should be used on top of warning background colors.
+ --canon-fg-success
- It should be used on top of `--canon-bg-success`.
+
+ It should be used on top of success background colors.
+
@@ -288,21 +368,9 @@ low contrast to help as a separator with the different background colors.
- --canon-border-tint
+ --canon-border-disabled
- It should be used on top of `--canon-bg-tint`.
-
-
-
- --canon-border-tint-hover
-
- Used when the component is hovered.
-
-
-
- --canon-border-tint-focus
-
- Used when the component is in focus.
+ Used when the component is disabled.
@@ -325,7 +393,40 @@ low contrast to help as a separator with the different background colors.
-## Font families
+### Special colors
+
+These colors are used for special purposes like ring, scrollbar, ...
+
+
+
+
+ Prop
+ Description
+
+
+
+
+
+ --canon-ring
+
+ The color of the ring.
+
+
+
+ --canon-scrollbar
+
+ The color of the scrollbar.
+
+
+
+ --canon-scrollbar-thumb
+
+ The color of the scrollbar thumb.
+
+
+
+
+### 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.
-## 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.
@@ -381,7 +482,7 @@ We have two font weights that we use across Canon. Regular or Bold
-## 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, ...
-### 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.
@@ -543,9 +635,59 @@ Here are the different radius factor we recommend to use
- --canon-radius-factor
+ --canon-radius-1
+
+
+ The radius of the component. Default value is `0.125rem`.
+
+
+
+
+ --canon-radius-2
+
+
+ The radius of the component. Default value is `0.25rem`.
+
+
+
+
+ --canon-radius-3
+
+
+ The radius of the component. Default value is `0.5rem`.
+
+
+
+
+ --canon-radius-4
+
+
+ The radius of the component. Default value is `0.75rem`.
+
+
+
+
+ --canon-radius-5
+
+
+ The radius of the component. Default value is `1rem`.
+
+
+
+
+ --canon-radius-6
+
+
+ The radius of the component. Default value is `1.25rem`.
+
+
+
+
+ --canon-radius-full
+
+
+ The radius of the component. Default value is `9999px`.
- By default the value is set to medium `1`.
diff --git a/packages/canon/css/button.css b/packages/canon/css/button.css
index 312f58c5e6..eb0c44ce1b 100644
--- a/packages/canon/css/button.css
+++ b/packages/canon/css/button.css
@@ -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;
}
diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css
index 264be6375b..bb1ba10018 100644
--- a/packages/canon/css/components.css
+++ b/packages/canon/css/components.css
@@ -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%;
diff --git a/packages/canon/css/datatable.css b/packages/canon/css/datatable.css
new file mode 100644
index 0000000000..edef29f500
--- /dev/null
+++ b/packages/canon/css/datatable.css
@@ -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;
+}
diff --git a/packages/canon/css/heading.css b/packages/canon/css/heading.css
index 615e7a6e8b..f1bea873b4 100644
--- a/packages/canon/css/heading.css
+++ b/packages/canon/css/heading.css
@@ -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);
}
diff --git a/packages/canon/css/iconbutton.css b/packages/canon/css/iconbutton.css
index 7853c59770..e867a1848e 100644
--- a/packages/canon/css/iconbutton.css
+++ b/packages/canon/css/iconbutton.css
@@ -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;
}
diff --git a/packages/canon/css/link.css b/packages/canon/css/link.css
index ddc47c5d04..535beb3d9f 100644
--- a/packages/canon/css/link.css
+++ b/packages/canon/css/link.css
@@ -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);
}
diff --git a/packages/canon/css/select.css b/packages/canon/css/select.css
index 29494c634a..cf8c05622b 100644
--- a/packages/canon/css/select.css
+++ b/packages/canon/css/select.css
@@ -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%;
diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css
index ba8fe6dab2..473b8f5d45 100644
--- a/packages/canon/css/styles.css
+++ b/packages/canon/css/styles.css
@@ -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%;
diff --git a/packages/canon/css/table.css b/packages/canon/css/table.css
index a8e904d7ce..f14ee3759e 100644
--- a/packages/canon/css/table.css
+++ b/packages/canon/css/table.css
@@ -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);
}
diff --git a/packages/canon/css/text.css b/packages/canon/css/text.css
index faa16c7f40..523574f779 100644
--- a/packages/canon/css/text.css
+++ b/packages/canon/css/text.css
@@ -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);
}
diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css
index a542690580..bf21b3bd74 100644
--- a/packages/canon/css/textfield.css
+++ b/packages/canon/css/textfield.css
@@ -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);
diff --git a/packages/canon/package.json b/packages/canon/package.json
index 5a369615b6..ce188f1fb0 100644
--- a/packages/canon/package.json
+++ b/packages/canon/package.json
@@ -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",
diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md
index 0a16c2abcf..44d6a3dd44 100644
--- a/packages/canon/report.api.md
+++ b/packages/canon/report.api.md
@@ -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: (
+ props: {
+ table: Table_2;
+ } & React.HTMLAttributes,
+ ) => JSX.Element;
+ Pagination: ForwardRefExoticComponent<
+ DataTablePaginationProps & RefAttributes
+ >;
+ Table: ForwardRefExoticComponent<
+ DataTableTableProps & RefAttributes
+ >;
+ TableRoot: ForwardRefExoticComponent<
+ Omit<
+ HTMLAttributes & RefAttributes,
+ 'ref'
+ > &
+ RefAttributes
+ >;
+ TableHeader: ForwardRefExoticComponent<
+ HTMLAttributes &
+ RefAttributes
+ >;
+ TableBody: ForwardRefExoticComponent<
+ HTMLAttributes &
+ RefAttributes
+ >;
+ TableRow: ForwardRefExoticComponent<
+ HTMLAttributes & RefAttributes
+ >;
+ TableCell: ForwardRefExoticComponent<
+ TdHTMLAttributes & RefAttributes
+ >;
+ TableCellText: ForwardRefExoticComponent<
+ TableCellTextProps & RefAttributes
+ >;
+ TableCellLink: ForwardRefExoticComponent<
+ TableCellLinkProps & RefAttributes
+ >;
+ TableHead: ForwardRefExoticComponent<
+ ThHTMLAttributes & RefAttributes
+ >;
+};
+
+// @public (undocumented)
+export interface DataTablePaginationProps
+ extends React.HTMLAttributes {}
+
+// @public (undocumented)
+export interface DataTableRootProps
+ extends React.HTMLAttributes {
+ table: Table_2;
+}
+
+// @public (undocumented)
+export interface DataTableTableProps
+ extends React.HTMLAttributes {}
+
// @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 & RefAttributes
->;
+// @public
+export const Table: {
+ Root: ForwardRefExoticComponent<
+ HTMLAttributes & RefAttributes
+ >;
+ Header: ForwardRefExoticComponent<
+ HTMLAttributes &
+ RefAttributes
+ >;
+ Body: ForwardRefExoticComponent<
+ HTMLAttributes &
+ RefAttributes
+ >;
+ Head: ForwardRefExoticComponent<
+ ThHTMLAttributes & RefAttributes
+ >;
+ Row: ForwardRefExoticComponent<
+ HTMLAttributes & RefAttributes
+ >;
+ Cell: ForwardRefExoticComponent<
+ TdHTMLAttributes & RefAttributes
+ >;
+ CellText: ForwardRefExoticComponent<
+ TableCellTextProps & RefAttributes
+ >;
+ CellLink: ForwardRefExoticComponent<
+ TableCellLinkProps & RefAttributes
+ >;
+ Caption: ForwardRefExoticComponent<
+ HTMLAttributes &
+ RefAttributes
+ >;
+};
// @public (undocumented)
-export const TableBody: ForwardRefExoticComponent<
- HTMLAttributes &
- RefAttributes
->;
+export interface TableCellLinkProps
+ extends React.TdHTMLAttributes {
+ // (undocumented)
+ description?: string;
+ // (undocumented)
+ href: string;
+ // (undocumented)
+ render?: useRender.ComponentProps<'a'>['render'];
+ // (undocumented)
+ title: string;
+}
// @public (undocumented)
-export const TableCell: ForwardRefExoticComponent<
- TdHTMLAttributes & RefAttributes
->;
-
-// @public (undocumented)
-export const TableFooter: ForwardRefExoticComponent<
- HTMLAttributes &
- RefAttributes
->;
-
-// @public (undocumented)
-export const TableHead: ForwardRefExoticComponent<
- ThHTMLAttributes & RefAttributes
->;
-
-// @public (undocumented)
-export const TableHeader: ForwardRefExoticComponent<
- HTMLAttributes &
- RefAttributes
->;
-
-// @public (undocumented)
-export const TableRow: ForwardRefExoticComponent<
- HTMLAttributes & RefAttributes
->;
+export interface TableCellTextProps
+ extends React.TdHTMLAttributes {
+ // (undocumented)
+ description?: string;
+ // (undocumented)
+ title: string;
+}
// @public (undocumented)
const Text_2: ForwardRefExoticComponent<
diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx
index 179c35323a..ca652013b4 100644
--- a/packages/canon/src/components/Button/Button.tsx
+++ b/packages/canon/src/components/Button/Button.tsx
@@ -44,18 +44,15 @@ export const Button = forwardRef(
);
},
diff --git a/packages/canon/src/components/Button/styles.css b/packages/canon/src/components/Button/styles.css
index b5d6e1dca0..785fbf3b88 100644
--- a/packages/canon/src/components/Button/styles.css
+++ b/packages/canon/src/components/Button/styles.css
@@ -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;
}
diff --git a/packages/canon/src/components/DataTable/DataTable.stories.tsx b/packages/canon/src/components/DataTable/DataTable.stories.tsx
new file mode 100644
index 0000000000..266aedc533
--- /dev/null
+++ b/packages/canon/src/components/DataTable/DataTable.stories.tsx
@@ -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;
+
+export const Uncontrolled: Story = {
+ render: () => {
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ });
+
+ return (
+
+
+
+
+ );
+ },
+};
+
+export const Controlled: Story = {
+ render: () => {
+ const [pagination, setPagination] = useState({
+ pageIndex: 4,
+ pageSize: 5,
+ });
+
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ state: {
+ pagination,
+ },
+ onPaginationChange: setPagination,
+ });
+
+ return (
+
+
+
+
+ );
+ },
+};
+
+export const WithCustomTable: Story = {
+ render: () => {
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ });
+
+ return (
+
+
+
+ {table.getHeaderGroups().map(headerGroup => (
+
+ {headerGroup.headers.map(header => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map(row => (
+
+ {row.getVisibleCells().map(cell => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext(),
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+ );
+ },
+};
diff --git a/packages/canon/src/components/DataTable/DataTable.tsx b/packages/canon/src/components/DataTable/DataTable.tsx
new file mode 100644
index 0000000000..8be9ae174e
--- /dev/null
+++ b/packages/canon/src/components/DataTable/DataTable.tsx
@@ -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,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => );
+TableRoot.displayName = Table.Root.displayName;
+
+/**
+ * DataTable component for displaying tabular data with pagination
+ * @public
+ */
+export const DataTable = {
+ Root: DataTableRoot as (
+ props: {
+ table: TanstackTable;
+ } & React.HTMLAttributes,
+ ) => 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,
+};
diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx
new file mode 100644
index 0000000000..a2f931aed6
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.stories.tsx
@@ -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;
+
+export default meta;
+type Story = StoryObj;
+
+export const Default: Story = {
+ render: () => {
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ });
+
+ return (
+
+
+
+ );
+ },
+};
diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css
new file mode 100644
index 0000000000..02d12e73bb
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.styles.css
@@ -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;
+}
diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx
new file mode 100644
index 0000000000..c1c73ee13c
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx
@@ -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,
+ ) => {
+ const { className, ...rest } = props;
+ const { table } = useDataTable();
+ const pageIndex = table?.getState().pagination.pageIndex;
+ const pageSize = table?.getState().pagination.pageSize;
+
+ return (
+
+ );
+ },
+);
+
+DataTablePagination.displayName = 'DataTablePagination';
+
+export { DataTablePagination };
diff --git a/packages/canon/src/components/DataTable/Pagination/types.ts b/packages/canon/src/components/DataTable/Pagination/types.ts
new file mode 100644
index 0000000000..b2a895c465
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Pagination/types.ts
@@ -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 {}
diff --git a/packages/canon/src/components/DataTable/Root/DataTableRoot.styles.css b/packages/canon/src/components/DataTable/Root/DataTableRoot.styles.css
new file mode 100644
index 0000000000..6cdd5fb390
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Root/DataTableRoot.styles.css
@@ -0,0 +1,5 @@
+.canon-DataTableRoot {
+ display: flex;
+ flex-direction: column;
+ gap: var(--canon-space-3);
+}
diff --git a/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx b/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx
new file mode 100644
index 0000000000..8207fe4412
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Root/DataTableRoot.tsx
@@ -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 = {
+ table: Table;
+};
+
+/** @public */
+export const DataTableContext = createContext | null>(
+ null,
+);
+
+/** @public */
+const DataTableRoot = forwardRef(
+ (
+ props: DataTableRootProps,
+ ref: React.ForwardedRef,
+ ) => {
+ const { className, table, ...rest } = props;
+
+ return (
+
+
+
+ );
+ },
+);
+
+DataTableRoot.displayName = 'DataTableRoot';
+
+export { DataTableRoot };
+
+/** @public */
+export function useDataTable() {
+ const context = useContext(DataTableContext);
+ if (!context) {
+ throw new Error('useDataTable must be used within a DataTableRoot');
+ }
+ return context as DataTableContextType;
+}
diff --git a/packages/canon/src/components/DataTable/Root/types.ts b/packages/canon/src/components/DataTable/Root/types.ts
new file mode 100644
index 0000000000..0a14f81fb3
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Root/types.ts
@@ -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
+ extends React.HTMLAttributes {
+ /**
+ * The table instance.
+ */
+ table: Table;
+}
diff --git a/packages/canon/src/components/DataTable/Table/DataTableTable.tsx b/packages/canon/src/components/DataTable/Table/DataTableTable.tsx
new file mode 100644
index 0000000000..6b0fafa67d
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Table/DataTableTable.tsx
@@ -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) => {
+ const { className, ...rest } = props;
+ const { table } = useDataTable();
+
+ return (
+
+
+ {table.getHeaderGroups().map(headerGroup => (
+
+ {headerGroup.headers.map(header => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map(row => (
+
+ {row.getVisibleCells().map(cell => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+ );
+ },
+);
+
+DataTableTable.displayName = 'DataTableRoot';
+
+export { DataTableTable };
diff --git a/packages/canon/src/components/DataTable/Table/types.ts b/packages/canon/src/components/DataTable/Table/types.ts
new file mode 100644
index 0000000000..a4ef96c732
--- /dev/null
+++ b/packages/canon/src/components/DataTable/Table/types.ts
@@ -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 {}
diff --git a/packages/canon/src/components/DataTable/index.ts b/packages/canon/src/components/DataTable/index.ts
new file mode 100644
index 0000000000..9d8c9becbb
--- /dev/null
+++ b/packages/canon/src/components/DataTable/index.ts
@@ -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';
diff --git a/packages/canon/src/components/DataTable/mocked-columns.tsx b/packages/canon/src/components/DataTable/mocked-columns.tsx
new file mode 100644
index 0000000000..9f2ce72378
--- /dev/null
+++ b/packages/canon/src/components/DataTable/mocked-columns.tsx
@@ -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[] = [
+ {
+ id: 'select',
+ header: ({ table }) => (
+
+ table.toggleAllPageRowsSelected(checked)
+ }
+ aria-label="Select all"
+ />
+ ),
+ cell: ({ row }) => (
+