diff --git a/package.json b/package.json index c9f4e1962..a16d3f015 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "dependencies": { "@headlessui/react": "^1.7.19", "@popperjs/core": "^2.11.8", + "@tanstack/react-table": "^8.20.5", "@tippyjs/react": "^4.2.6", "chalk": "^4.1.2", "clsx": "^2.1.1", diff --git a/src/components/DataTable/DataTable.module.css b/src/components/DataTable/DataTable.module.css index dd2170c2e..87dd391be 100644 --- a/src/components/DataTable/DataTable.module.css +++ b/src/components/DataTable/DataTable.module.css @@ -2,11 +2,8 @@ # DATA TABLE \*------------------------------------*/ -/** - * DataTable - */ - /* Visible table caption */ + /* TODO-AH: make it so that we have the search bar and actions wrap together instead of separately */ .data-table__caption-container { display: flex; align-items: flex-end; @@ -15,7 +12,7 @@ gap: calc(var(--eds-size-3) / 16 * 1rem) calc(var(--eds-size-6) / 16 * 1rem); text-align: start; - margin: 0 calc(var(--eds-size-3) / 16 * 1rem) calc(var(--eds-size-4) / 16 * 1rem); + margin: 0 calc(var(--eds-size-3) / 16 * 1rem) calc(var(--eds-size-4) / 16 * 1rem); } .data-table__caption-text { @@ -37,9 +34,13 @@ } .data-table__table { - border: 1px solid; width: 100%; + /* TODO-AH: add class instead of tag for styles */ + th { + padding: 0; + } + .data-table__caption + &, .data-table__subcaption + & { margin-top: calc(var(--eds-size-4) / 16 * 1rem); @@ -50,11 +51,107 @@ width: calc(var(--eds-size-34) / 16 * 1rem); } +.data-table--tableStyle-border { + /* TODO-AH: token for 1px */ + border: 1px solid; +} + +.data-table__cell-text { + text-align: start; + + .data-table__cell--alignment-leading & { + text-align: start; + } + + .data-table__cell--alignment-trailing & { + text-align: end; + } +} + +.data-table__cell--alignment-leading { + justify-content: flex-start; +} + +.data-table__cell--alignment-trailing { + justify-content: flex-end; +} + +.data-table__header-cell { + display: flex; + gap: calc(var(--eds-size-1) / 16 * 1rem); + align-items: center; + font: var(--eds-theme-typography-title-md); + + .data-table--size-sm & { + font: var(--eds-theme-typography-title-sm); + /* TODO-AH: we want to use top-/bottom-padding of 5px to give overall height divisible by 8 (32px) */ + padding: calc(var(--eds-size-half) / 16 * 1rem) calc(var(--eds-size-1) / 16 * 1rem); + } + + .data-table--size-md & { + padding: calc(var(--eds-size-2) / 16 * 1rem); + } + + .data-table__cell-sublabel { + display: block; + font: var(--eds-theme-typography-body-sm); + } +} + +.data-table__cell { + display: flex; + gap: calc(var(--eds-size-1) / 16 * 1rem); + font: var(--eds-theme-typography-body-md); + align-items: center; + + .data-table--size-sm & { + font: var(--eds-theme-typography-body-sm); + padding: calc(var(--eds-size-half) / 16 * 1rem) calc(var(--eds-size-1) / 16 * 1rem); + } + + .data-table--size-md & { + padding: calc(var(--eds-size-2) / 16 * 1rem); + } + + .data-table__cell-sublabel { + display: block; + font: var(--eds-theme-typography-body-sm); + } + + /* TODO-AH .data-cell__cell--icon {} */ +} + +.data-table__row { + .data-table--rowStyle-lined & { + border-bottom: 1px solid; + } + + .data-table--rowStyle-striped &:nth-child(even) { + background-color: var(--eds-theme-color-background-table-row-stripe-2); + } + + .data-table--rowStyle-striped &:nth-child(odd) { + background-color: var(--eds-theme-color-background-table-row-stripe-1); + } +} + +.data-table__header-row { + border-bottom: 1px solid; + /* TODO-AH: figure out positioning styles for sticky headers/columns */ + position: sticky; + top: 0; +} + /** * Color Tokens */ .data-table { display: block; + position: relative; + + .data-table__table { + background-color: var(--eds-theme-color-background-utility-base-1); + } .data-table__caption { color: var(--eds-theme-color-text-utility-default-primary); @@ -63,4 +160,24 @@ .data-table__subcaption { color: var(--eds-theme-color-text-utility-default-secondary); } + + .data-table--tableStyle-border, .data-table__header-row { + border-color: var(--eds-theme-color-border-utility-default-low-emphasis); + } + + .data-table__header-cell { + color: var(--eds-theme-color-text-utility-default-primary); + } + + .data-table__cell { + color: var(--eds-theme-color-text-utility-default-primary); + } + + .data-table--rowStyle-lined { + color: var(--eds-theme-color-border-utility-default-low-emphasis); + } + + .data-table__cell-sublabel, .data-table__header-cell-sublabel { + color: var(--eds-theme-color-text-utility-default-secondary); + } } diff --git a/src/components/DataTable/DataTable.stories.tsx b/src/components/DataTable/DataTable.stories.tsx index 38c077a98..15a8a9b1d 100644 --- a/src/components/DataTable/DataTable.stories.tsx +++ b/src/components/DataTable/DataTable.stories.tsx @@ -1,8 +1,16 @@ import { BADGE } from '@geometricpanda/storybook-addon-badges'; import type { StoryObj, Meta } from '@storybook/react'; + +// Import the helpers from the base library, for later use +import { + createColumnHelper, + getCoreRowModel, + useReactTable, +} from '@tanstack/react-table'; + import React from 'react'; -import { DataTable } from './DataTable'; +import { DataTable, type DataTableProps } from './DataTable'; import { chromaticViewports } from '../../util/viewports'; import Button from '../Button'; import Menu from '../Menu'; @@ -20,38 +28,221 @@ export default { ], }, }, + // TODO-AH: add subcomponent docs after importing the literals argTypes: { actions: { control: false, }, + children: { + control: false, + }, + }, +} as Meta; + +type Args = DataTableProps; + +// Specifying an example data type for the rows of a table +type Person = { + firstName: string; + lastName: string; + age: number; + visits: number; + progress: number; +}; + +// Specifying the example (static) data for the table to use with tanstack primitives +const defaultData: Person[] = [ + { + firstName: 'Tanner', + lastName: 'Lindsey', + age: 24, + visits: 100, + progress: 50, + }, + { + firstName: 'Tandy', + lastName: 'Miller', + age: 40, + visits: 40, + progress: 80, + }, + { + firstName: 'Joe', + lastName: 'Dirte', + age: 45, + visits: 20, + progress: 10, + }, +]; + +// We generate a helper object to generate the display columns and use the type for the structure +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor('firstName', { + header: () => ( + + First Name + + ), + cell: (info) => ( + + {info.getValue()} + + ), + }), + columnHelper.accessor((row) => row.lastName, { + id: 'lastName', + header: () => ( + Last Name + ), + cell: (info) => {info.getValue()}, + }), + columnHelper.accessor('age', { + header: () => ( + Age + ), + cell: (info) => ( + + {info.renderValue()} + + ), + }), + columnHelper.accessor('visits', { + header: () => ( + Visits + ), + cell: (info) => ( + + {info.renderValue()} + + ), + }), + columnHelper.accessor('progress', { + header: () => ( + + Profile Progress + + ), + cell: (info) => ( + = 80 ? 'Complete' : 'Incomplete'} + > + {info.renderValue()} + + ), + }), +]; + +export const Default: StoryObj = { + args: {}, + render: (args) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [data] = React.useState(() => [...defaultData]); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ; }, +}; + +export const TableStyleBorder: StoryObj = { + args: { + tableStyle: 'border', + }, + render: (args) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [data] = React.useState(() => [...defaultData]); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ; + }, +}; + +/** + * When using table size small, we have less padding on the cells and header + * + * **Note**: using `sublabel`s when `size` = `'sm'` is not allowed. + * + * TODO-AH: add warning and prevent sublabels from appearing. + */ +export const TableSizeSm: StoryObj = { + args: { + tableStyle: 'border', + size: 'sm', + }, + render: (args) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [data] = React.useState(() => [...defaultData]); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ; + }, +}; + +export const RowStyleLined: StoryObj = { + args: { + tableStyle: 'border', + rowStyle: 'lined', + }, + render: (args) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [data] = React.useState(() => [...defaultData]); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ; + }, +}; + +export const DefaultWithCustomTable: StoryObj = { args: { children: ( - +
TODO: Table rows/cells HereTODO: Custom table rows/cells here
), }, -} as Meta; - -type Args = React.ComponentProps; - -export const Default: StoryObj = { - args: {}, }; export const WithBasicCaption: StoryObj = { args: { + ...DefaultWithCustomTable.args, caption: 'Fruits of the world', }, }; export const WithFullCaption: StoryObj = { + ...DefaultWithCustomTable, args: { + ...DefaultWithCustomTable.args, caption: 'Fruits of the world', subcaption: "Aren't they all so delicious?", }, @@ -59,6 +250,7 @@ export const WithFullCaption: StoryObj = { export const WithSearch: StoryObj = { args: { + ...DefaultWithCustomTable.args, caption: 'Fruits of the world', subcaption: "Aren't they all so delicious?", onSearchChange: () => {}, @@ -67,6 +259,7 @@ export const WithSearch: StoryObj = { export const WithOnlyActions: StoryObj = { args: { + ...DefaultWithCustomTable.args, actions: ( - - - - + Incomplete + + + + + + + + +`; + +exports[` DefaultWithCustomTable story renders snapshot 1`] = ` +
@@ -130,335 +355,1010 @@ exports[` TableA story renders snapshot 1`] = ` `; -exports[` TableB story renders snapshot 1`] = ` +exports[` RowStyleLined story renders snapshot 1`] = `
-
-
- - -
-
+ + - Seriously, who let this happen? - - - + + +
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
+
+ First Name + + Given Name + +
- - - -
-
- -
+
- - - - - -
- - - - - - + Surname + + + + + + + - -
- TODO: Table rows/cells Here - +
+
+ Age +
+
+
+
+
+ Visits +
+
+
+
+
+ Profile Progress + + "Complete" is > 80% + +
+
+
- -`; - -exports[` TableC story renders snapshot 1`] = ` -
-
-
- -
+
+ Tanner +
- - - -
-
- -
+
- - - - - -
- - - - - + Lindsey + + + + + - -
+
+
+ 24 +
+
+
- TODO: Table rows/cells Here +
+
+ 100 +
+
+
+
+
+ 50 + + Incomplete + +
+
- -`; - -exports[` TableD story renders snapshot 1`] = ` -
-
-
- - -
-
+
+ Tandy +
- - - -
-
- -
+
- -
+
+
+
+ 40 +
+
+
+
+
+ 40 +
+
+
+
+
+ 80 + - - - - - -
-
- - - + + + + + + + + + + + + +
+
+ +
+ Joe +
+
+
+
+
+ Dirte +
+
+
+
+
+ 45 +
+
+
+
+
+ 20 +
+
+
+
+
+ 10 + + Incomplete + +
+
+
+ +`; + +exports[` TableSizeSm story renders snapshot 1`] = ` +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ First Name + + Given Name + +
+
+
+
+
+ Last Name + + Surname + +
+
+
+
+
+ Age +
+
+
+
+
+ Visits +
+
+
+
+
+ Profile Progress + + "Complete" is > 80% + +
+
+
+
+ +
+ Tanner +
+
+
+
+
+ Lindsey +
+
+
+
+
+ 24 +
+
+
+
+
+ 100 +
+
+
+
+
+ 50 + + Incomplete + +
+
+
+
+ +
+ Tandy +
+
+
+
+
+ Miller +
+
+
+
+
+ 40 +
+
+
+
+
+ 40 +
+
+
+
+
+ 80 + + Complete + +
+
+
+
+ +
+ Joe +
+
+
+
+
+ Dirte +
+
+
+
+
+ 45 +
+
+
+
+
+ 20 +
+
+
+
+
+ 10 + + Incomplete + +
+
+
+
+`; + +exports[` TableStyleBorder story renders snapshot 1`] = ` +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -490,7 +1390,7 @@ exports[` WithBasicCaption story renders snapshot 1`] = ` > @@ -528,7 +1428,7 @@ exports[` WithFullCaption story renders snapshot 1`] = ` > @@ -640,7 +1540,7 @@ exports[` WithLongCaption story renders snapshot 1`] = ` > @@ -699,7 +1599,7 @@ exports[` WithOnlyActions story renders snapshot 1`] = ` > @@ -774,7 +1674,7 @@ exports[` WithSearch story renders snapshot 1`] = ` > @@ -886,7 +1786,7 @@ exports[` WithSearchAndActions story renders snapshot 1`] = ` > @@ -1050,7 +1950,7 @@ exports[` WithSearchAndCustomActions story renders snapshot 1`] = ` > diff --git a/src/components/DataTable/index.ts b/src/components/DataTable/index.ts index cadb02a9a..e41e79a72 100644 --- a/src/components/DataTable/index.ts +++ b/src/components/DataTable/index.ts @@ -1 +1,4 @@ export { DataTable as default } from './DataTable'; +export type { DataTableProps } from './DataTable'; +// TODO-AH: export types for any things with type param.s +// TODO-AH: re-export tanstack stuff? diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index ac425018a..4bbbb7ff0 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -218,6 +218,8 @@ const TableHeaderCell = ({ className, ); + // TODO-AH: add warning when in use + const icon: IconName = sortDirection === 'ascending' ? 'arrow-up' diff --git a/src/util/variant-types.ts b/src/util/variant-types.ts index 1ffd404ca..2c8e0c586 100644 --- a/src/util/variant-types.ts +++ b/src/util/variant-types.ts @@ -1,5 +1,6 @@ /** * This contains the broad types of the common programmatic variants: e.g., + * - base component props * - size * - align * - preset @@ -7,6 +8,20 @@ * use Extract to trim any unsupported variants */ +/** + * Component props used by any/every cmoponent in the system. Pick<> to grab any subset + */ +export type EDSBase = { + /** + * Sub-components and other elements appropriate for this parent component (See Sub-components if applicable) + */ + children?: React.ReactNode; + /** + * CSS class names that can be appended to the component. + */ + className?: string; +}; + /** * Size range for components, set at named intervals. Sizes need not be precisely * some distant or multiple apart; they can be defined as a set that increases with @@ -16,6 +31,7 @@ export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'xxxl'; /** * Alignment variants, for horizontal components and sub-components + * TODO-AH: prefer leading and trailing */ export type Align = 'left' | 'center' | 'right'; diff --git a/yarn.lock b/yarn.lock index caea688eb..05b5dac16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2762,6 +2762,7 @@ __metadata: "@storybook/test": "npm:^8.2.9" "@storybook/testing-library": "npm:^0.2.2" "@storybook/theming": "npm:^8.2.9" + "@tanstack/react-table": "npm:^8.20.5" "@testing-library/jest-dom": "npm:^6.5.0" "@testing-library/react": "npm:^16.0.1" "@testing-library/user-event": "npm:^14.5.2" @@ -5879,6 +5880,18 @@ __metadata: languageName: node linkType: hard +"@tanstack/react-table@npm:^8.20.5": + version: 8.20.5 + resolution: "@tanstack/react-table@npm:8.20.5" + dependencies: + "@tanstack/table-core": "npm:8.20.5" + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + checksum: 10/df67094795a0b7e4b34f73abe346443c2e806c572fea31b58759aa8ec5274f613e5e6941090eb16f861bda10d3088731bc6e7f15e5f90326db273bc55b9141ce + languageName: node + linkType: hard + "@tanstack/react-virtual@npm:^3.0.0-beta.60": version: 3.0.1 resolution: "@tanstack/react-virtual@npm:3.0.1" @@ -5891,6 +5904,13 @@ __metadata: languageName: node linkType: hard +"@tanstack/table-core@npm:8.20.5": + version: 8.20.5 + resolution: "@tanstack/table-core@npm:8.20.5" + checksum: 10/5408237920d5796951e925278edbbe76f71006627a4e3da248a810970256f75d973538fe7ae75a32155d4a25a95abc4fffaea337b5120f7940d7e664dc9da87f + languageName: node + linkType: hard + "@tanstack/virtual-core@npm:3.0.0": version: 3.0.0 resolution: "@tanstack/virtual-core@npm:3.0.0"
+
+ +
+ First Name + + Given Name + +
+
+
+
+
+ Last Name + + Surname + +
+
+
+
+
+ Age +
+
+
+
+
+ Visits +
+
+
+
+
+ Profile Progress + + "Complete" is > 80% + +
+
+
+
+ +
+ Tanner +
+
+
- TODO: Table rows/cells Here +
+
+ Lindsey +
+
+
+
+
+ 24 +
+
+
+
+
+ 100 +
+
+
+
+
+ 50 + + Incomplete + +
+
+
+
+ +
+ Tandy +
+
+
+
+
+ Miller +
+
+
+
+
+ 40 +
+
+
+
+
+ 40 +
+
+
+
+
+ 80 + + Complete + +
+
+
+
+ +
+ Joe +
+
+
+
+
+ Dirte +
+
+
+
+
+ 45 +
+
+
+
+
+ 20 +
+
+
+
+
+ 10 + + Incomplete + +
+
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here
- TODO: Table rows/cells Here + TODO: Custom table rows/cells here