From b0aae7f05f499335026c632d38a5915677933dd0 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 27 Jan 2023 13:06:17 -0600 Subject: [PATCH 01/14] feat(react): add DataTable component --- docs/content/drafts/DataTable.mdx | 92 +++++ src/DataTable/DataTable.docs.json | 121 +++++++ src/DataTable/DataTable.stories.tsx | 231 +++++++++++++ src/DataTable/__tests__/DataTable.test.tsx | 251 ++++++++++++++ src/DataTable/index.tsx | 370 +++++++++++++++++++++ 5 files changed, 1065 insertions(+) create mode 100644 docs/content/drafts/DataTable.mdx create mode 100644 src/DataTable/DataTable.docs.json create mode 100644 src/DataTable/DataTable.stories.tsx create mode 100644 src/DataTable/__tests__/DataTable.test.tsx create mode 100644 src/DataTable/index.tsx diff --git a/docs/content/drafts/DataTable.mdx b/docs/content/drafts/DataTable.mdx new file mode 100644 index 00000000000..55058ca949e --- /dev/null +++ b/docs/content/drafts/DataTable.mdx @@ -0,0 +1,92 @@ +--- +title: DataTable +componentId: data-table +status: Draft +--- + +import data from '../../../src/DataTable/DataTable.docs.json' + +## Examples + +```jsx + { + return + }, + }, + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + }, + ]} +/> +``` + +## Props + + + +## Status + + diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json new file mode 100644 index 00000000000..0e7ca8c9acd --- /dev/null +++ b/src/DataTable/DataTable.docs.json @@ -0,0 +1,121 @@ +{ + "$schema": "../../script/components-json/component.schema.json", + "id": "data-table", + "name": "DataTable", + "status": "draft", + "a11yReviewed": false, + "stories": [], + "props": [ + { + "name": "aria-describedby", + "type": "string", + "description": "Provide an id to an element which uniquely describes this table" + }, + { + "name": "aria-labelledby", + "type": "string", + "description": "Provide an id to an element which uniquely labels this table" + }, + { + "name": "data", + "type": "Array", + "description": "Provide a collection of the rows which will be rendered inside of the table" + }, + { + "name": "columns", + "type": "Array>", + "description": "Provide the columns for the table and the fields in `data` to which they correspond" + }, + { + "name": "cellPadding", + "type": "'condensed' | 'normal' | 'spacious'", + "description": "Specify the amount of space that should be available around the contents of a cell" + }, + { + "name": "title", + "type": "string | React.ReactNode", + "description": "Provide a title for the table" + }, + { + "name": "subtitle", + "type": "string | React.ReactNode", + "description": "Provide a subtitle for the table" + } + ], + "subcomponents": [ + { + "name": "Table", + "props": [ + { + "name": "aria-describedby", + "type": "string", + "description": "Provide an id to an element which uniquely describes this table" + }, + { + "name": "aria-labelledby", + "type": "string", + "description": "Provide an id to an element which uniquely labels this table" + }, + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "cellPadding", + "type": "'condensed' | 'normal' | 'spacious'", + "description": "Specify the amount of space that should be available around the contents of a cell" + } + ] + }, + { + "name": "TableHead", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableBody", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableRow", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableHeader", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableCell", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "scope", + "type": "string", + "description": "Provide the scope for a table cell, useful for defining a row header using `scope=\"row\"`" + } + ] + } + ] +} diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx new file mode 100644 index 00000000000..fa63ecfd83f --- /dev/null +++ b/src/DataTable/DataTable.stories.tsx @@ -0,0 +1,231 @@ +import {Meta, ComponentStory} from '@storybook/react' +import React from 'react' +import {DataTable, Table, TableHead, TableBody, TableRow, TableHeader, TableCell} from '../DataTable' +import Label from '../Label' +import LabelGroup from '../LabelGroup' +import RelativeTime from '../RelativeTime' + +export default { + title: 'Drafts/Components/DataTable', + component: DataTable, + subcomponents: { + Table, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, + }, +} as Meta + +const now = Date.now() +const Second = 1000 +const Minute = 60 * Second +const Hour = 60 * Minute +const Day = 24 * Hour +const Week = 7 * Day +const Month = 4 * Week + +interface Repo { + id: number + name: string + type: 'public' | 'internal' + updatedAt: number + securityFeatures: { + dependabot: Array + codeScanning: Array + } +} + +const data: Array = [ + { + id: 1, + name: 'codeql-dca-worker', + type: 'internal', + updatedAt: now, + securityFeatures: { + dependabot: ['alerts', 'security updates'], + codeScanning: ['report secrets'], + }, + }, + { + id: 2, + name: 'aegir', + type: 'public', + updatedAt: now - 5 * Minute, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: ['report secrets'], + }, + }, + { + id: 3, + name: 'strapi', + type: 'public', + updatedAt: now - 1 * Hour, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 4, + name: 'codeql-ci-nightlies', + type: 'public', + updatedAt: now - 6 * Hour, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, + { + id: 5, + name: 'dependabot-updates', + type: 'public', + updatedAt: now - 1 * Day, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 6, + name: 'tsx-create-react-app', + type: 'public', + updatedAt: now - 1 * Week, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 7, + name: 'bootstrap', + type: 'public', + updatedAt: now - 1 * Month, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, + { + id: 8, + name: 'docker-templates', + type: 'public', + updatedAt: now - 3 * Month, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, +] + +function uppercase(input: string): string { + return input[0].toUpperCase() + input.slice(1) +} + +export const Playground: ComponentStory = args => { + return ( + { + return + }, + }, + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return + }, + }, + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + }, + { + header: 'Code scanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( + + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + }, + ]} + /> + ) +} + +Playground.args = { + cellPadding: 'normal', + title: 'Repositories', + subtitle: 'A subtitle could appear here to give extra context to the data.', +} + +Playground.argTypes = { + 'aria-describedby': { + control: false, + table: { + disable: true, + }, + }, + 'aria-labelledby': { + control: false, + table: { + disable: true, + }, + }, + columns: { + control: false, + table: { + disable: true, + }, + }, + data: { + control: false, + table: { + disable: true, + }, + }, + cellPadding: { + control: { + type: 'radio', + }, + type: { + name: 'enum', + value: ['condensed', 'normal', 'spacious'], + }, + }, + title: { + control: { + type: 'text', + }, + }, + subtitle: { + control: { + type: 'text', + }, + }, +} diff --git a/src/DataTable/__tests__/DataTable.test.tsx b/src/DataTable/__tests__/DataTable.test.tsx new file mode 100644 index 00000000000..36021f61c1d --- /dev/null +++ b/src/DataTable/__tests__/DataTable.test.tsx @@ -0,0 +1,251 @@ +import React from 'react' +import {DataTable} from '..' +import {render, screen} from '@testing-library/react' + +describe('DataTable', () => { + it('should render a semantic through `data` and `columns`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render() + + //
+ expect(screen.getByRole('table')).toBeInTheDocument() + + // + expect(screen.getAllByRole('row').length).toBe(4) + // {children} +} + +// ---------------------------------------------------------------------------- +// TableBody +// ---------------------------------------------------------------------------- + +interface TableBodyProps extends React.ComponentPropsWithoutRef<'tbody'> { + children?: React.ReactNode +} + +function TableBody({children}: TableBodyProps) { + return {children} +} + +// ---------------------------------------------------------------------------- +// TableHeader +// ---------------------------------------------------------------------------- + +interface TableHeaderProps extends React.ComponentPropsWithoutRef<'th'> { + children?: React.ReactNode +} + +function TableHeader({children}: TableHeaderProps) { + return +} + +// ---------------------------------------------------------------------------- +// TableRow +// ---------------------------------------------------------------------------- + +interface TableRowProps extends React.ComponentPropsWithoutRef<'tr'> { + children?: React.ReactNode +} + +function TableRow({children}: TableRowProps) { + return {children} +} + +// ---------------------------------------------------------------------------- +// TableCell +// ---------------------------------------------------------------------------- + +interface TableCellProps extends React.ComponentPropsWithoutRef<'td'> { + children?: React.ReactNode + + /** + * Provide the scope for a table cell, useful for defining a row header using + * `scope="row"` + */ + scope?: string | undefined +} + +function TableCell({children, scope}: TableCellProps) { + const BaseComponent = scope ? 'th' : 'td' + + return ( + + {children} + + ) +} + +export {DataTable, Table, TableHead, TableBody, TableRow, TableHeader, TableCell} From 352e2f80ae9f56c3e8e8a31c6c45bf97c04388e2 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 27 Jan 2023 13:29:30 -0600 Subject: [PATCH 02/14] chore: remove support for row hover and title/subtitle --- src/DataTable/DataTable.docs.json | 10 ----- src/DataTable/DataTable.stories.tsx | 12 ------ src/DataTable/__tests__/DataTable.test.tsx | 50 ---------------------- src/DataTable/index.tsx | 44 +------------------ 4 files changed, 1 insertion(+), 115 deletions(-) diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json index 0e7ca8c9acd..2fdd2882802 100644 --- a/src/DataTable/DataTable.docs.json +++ b/src/DataTable/DataTable.docs.json @@ -30,16 +30,6 @@ "name": "cellPadding", "type": "'condensed' | 'normal' | 'spacious'", "description": "Specify the amount of space that should be available around the contents of a cell" - }, - { - "name": "title", - "type": "string | React.ReactNode", - "description": "Provide a title for the table" - }, - { - "name": "subtitle", - "type": "string | React.ReactNode", - "description": "Provide a subtitle for the table" } ], "subcomponents": [ diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx index fa63ecfd83f..f972cfb0eae 100644 --- a/src/DataTable/DataTable.stories.tsx +++ b/src/DataTable/DataTable.stories.tsx @@ -180,8 +180,6 @@ export const Playground: ComponentStory = args => { Playground.args = { cellPadding: 'normal', - title: 'Repositories', - subtitle: 'A subtitle could appear here to give extra context to the data.', } Playground.argTypes = { @@ -218,14 +216,4 @@ Playground.argTypes = { value: ['condensed', 'normal', 'spacious'], }, }, - title: { - control: { - type: 'text', - }, - }, - subtitle: { - control: { - type: 'text', - }, - }, } diff --git a/src/DataTable/__tests__/DataTable.test.tsx b/src/DataTable/__tests__/DataTable.test.tsx index 36021f61c1d..aa7920a4d64 100644 --- a/src/DataTable/__tests__/DataTable.test.tsx +++ b/src/DataTable/__tests__/DataTable.test.tsx @@ -79,56 +79,6 @@ describe('DataTable', () => { } }) - it('should support labeling through `title`', () => { - const columns = [ - { - header: 'Name', - field: 'name', - }, - ] - const data = [ - { - id: 1, - name: 'one', - }, - { - id: 2, - name: 'two', - }, - { - id: 3, - name: 'three', - }, - ] - render() - expect(screen.getByRole('table', {name: 'test'})).toBeInTheDocument() - }) - - it('should support describing the table through `subtitle`', () => { - const columns = [ - { - header: 'Name', - field: 'name', - }, - ] - const data = [ - { - id: 1, - name: 'one', - }, - { - id: 2, - name: 'two', - }, - { - id: 3, - name: 'three', - }, - ] - render() - expect(screen.getByRole('table', {description: 'test'})).toBeInTheDocument() - }) - it('should support custom labeling through `aria-labelledby`', () => { const columns = [ { diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx index 8e47eb59e38..80d7285922d 100644 --- a/src/DataTable/index.tsx +++ b/src/DataTable/index.tsx @@ -1,8 +1,6 @@ import React from 'react' import styled from 'styled-components' import {get} from '../constants' -import {useId} from '../hooks/useId' -import Box from '../Box' // ---------------------------------------------------------------------------- // DataTable @@ -43,16 +41,6 @@ export interface DataTableProps { * correspond */ columns: Array> - - /** - * Provide a title for the table - */ - title?: string | React.ReactNode | undefined - - /** - * Provide an optional description for the table - */ - subtitle?: string | React.ReactNode | undefined } interface Column { @@ -91,40 +79,10 @@ function DataTable({ cellPadding, columns, data, - title, - subtitle, }: DataTableProps) { - const id = useId() return ( <> - {title || subtitle ? ( - - {title ? ( - - {title} - - ) : null} - {subtitle ? ( - - {subtitle} - - ) : null} - - ) : null} -
+ expect(screen.getAllByRole('columnheader')).toHaveLength(1) + expect(screen.getByRole('columnheader', {name: 'Name'})).toBeInTheDocument() + + //
+ expect(screen.getAllByRole('cell').length).toBe(3) + }) + + it('should support custom cell rendering with `renderCell`', () => { + const data = [ + { + id: 1, + name: { + value: 'one', + }, + }, + { + id: 2, + name: { + value: 'two', + }, + }, + { + id: 3, + name: { + value: 'three', + }, + }, + ] + render( + { + return row.name.value + }, + }, + ]} + />, + ) + + for (const row of data) { + expect(screen.getByRole('cell', {name: row.name.value})).toBeInTheDocument() + } + }) + + it('should support labeling through `title`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render() + expect(screen.getByRole('table', {name: 'test'})).toBeInTheDocument() + }) + + it('should support describing the table through `subtitle`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render() + expect(screen.getByRole('table', {description: 'test'})).toBeInTheDocument() + }) + + it('should support custom labeling through `aria-labelledby`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render( + <> +

custom-title

+ + , + ) + expect(screen.getByRole('table', {name: 'custom-title'})).toBeInTheDocument() + }) + + it('should support custom descriptions through `aria-describedby`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render( + <> +

custom-description

+ + , + ) + expect(screen.getByRole('table', {description: 'custom-description'})).toBeInTheDocument() + }) + + it('should support customizing the `cellPadding` of cells', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + const {rerender} = render() + + expect(screen.getByRole('table')).toHaveAttribute('data-cell-padding', 'normal') + + rerender() + expect(screen.getByRole('table')).toHaveAttribute('data-cell-padding', 'condensed') + + rerender() + expect(screen.getByRole('table')).toHaveAttribute('data-cell-padding', 'spacious') + }) + + it('should support specifying a rowHeader through `rowHeader` in `columns`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + rowHeader: true, + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render() + for (const row of data) { + expect(screen.getByRole('rowheader', {name: row.name})).toBeInTheDocument() + } + }) +}) diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx new file mode 100644 index 00000000000..8e47eb59e38 --- /dev/null +++ b/src/DataTable/index.tsx @@ -0,0 +1,370 @@ +import React from 'react' +import styled from 'styled-components' +import {get} from '../constants' +import {useId} from '../hooks/useId' +import Box from '../Box' + +// ---------------------------------------------------------------------------- +// DataTable +// ---------------------------------------------------------------------------- +interface Row { + /** + * Provide a value that uniquely identifies the row + */ + id: string | number + + [key: string]: any +} + +export interface DataTableProps { + /** + * Provide an id to an element which uniquely describes this table + */ + 'aria-describedby'?: string | undefined + + /** + * Provide an id to an element which uniquely labels this table + */ + 'aria-labelledby'?: string | undefined + + /** + * Specify the amount of space that should be available around the contents of + * a cell + */ + cellPadding?: 'condensed' | 'normal' | 'spacious' | undefined + + /** + * Provide a collection of the rows which will be rendered inside of the table + */ + data: Array + + /** + * Provide the columns for the table and the fields in `data` to which they + * correspond + */ + columns: Array> + + /** + * Provide a title for the table + */ + title?: string | React.ReactNode | undefined + + /** + * Provide an optional description for the table + */ + subtitle?: string | React.ReactNode | undefined +} + +interface Column { + /** + * Provide the name of the column. This will be rendered as a table header + * within the table itself + */ + header: string + + /** + * Optionally provide a field to render for this column. This may be the key + * of the object or a string that accesses nested objects through `.`. For + * exmaple: `field: a.b.c` + * + * Alternatively, you may provide a `renderCell` for this column to render the + * field in a row + */ + field?: string | undefined + + /** + * Provide a custom component or render prop to render the data for this + * column in a row + */ + renderCell?: ((data: Data) => React.ReactNode) | undefined + + /** + * Specify if the value of this column for a row should be treated as a row + * header + */ + rowHeader?: boolean | undefined +} + +function DataTable({ + 'aria-labelledby': labelledby, + 'aria-describedby': describedby, + cellPadding, + columns, + data, + title, + subtitle, +}: DataTableProps) { + const id = useId() + return ( + <> + {title || subtitle ? ( + + {title ? ( + + {title} + + ) : null} + {subtitle ? ( + + {subtitle} + + ) : null} + + ) : null} + + + + {columns.map(column => { + return {column.header} + })} + + + + {data.map(row => { + return ( + + {columns.map(column => { + const columnProps = { + key: column.header, + scope: column.rowHeader ? 'row' : undefined, + } + + if (column.renderCell) { + return {column.renderCell(row)} + } + + if (column.field) { + const value = row[column.field] + + if (typeof value === 'string' || typeof value === 'number' || React.isValidElement(value)) { + return {value} + } + } + + return null + })} + + ) + })} + +
+ + ) +} + +// ---------------------------------------------------------------------------- +// Table +// ---------------------------------------------------------------------------- + +const StyledTable = styled.table>` + /* Default table styles */ + --table-border-radius: 0.375rem; + --table-cell-padding: var(--cell-padding-block, 0.5rem) var(--cell-padding-inline, 0.75rem); + --table-font-size: 0.75rem; + + background-color: ${get('colors.canvas.default')}; + border-spacing: 0; + border-collapse: separate; + font-size: var(--table-font-size); + line-height: calc(20 / var(--table-font-size)); + width: 100%; + overflow-x: auto; + + /* Density modes: condensed, normal, spacious */ + &[data-cell-padding='condensed'] { + --cell-padding-block: 0.25rem; + --cell-padding-inline: 0.5rem; + } + + &[data-cell-padding='normal'] { + --cell-padding-block: 0.5rem; + --cell-padding-inline: 0.75rem; + } + + &[data-cell-padding='spacious'] { + --cell-padding-block: 0.75rem; + --cell-padding-inline: 1rem; + } + + /* Borders */ + .TableCell:first-child, + .TableHeader:first-child { + border-left: 1px solid ${get('colors.border.default')}; + } + + .TableCell:last-child, + .TableHeader:last-child { + border-right: 1px solid ${get('colors.border.default')}; + } + + .TableHeader, + .TableCell { + border-bottom: 1px solid ${get('colors.border.default')}; + } + + .TableHead .TableRow:first-of-type .TableHeader { + border-top: 1px solid ${get('colors.border.default')}; + } + + /* Border radius */ + .TableHead .TableRow:first-of-type .TableHeader:first-child { + border-top-left-radius: var(--table-border-radius); + } + + .TableHead .TableRow:first-of-type .TableHeader:last-child { + border-top-right-radius: var(--table-border-radius); + } + + .TableBody .TableRow:last-of-type .TableCell:first-child { + border-bottom-left-radius: var(--table-border-radius); + } + + .TableBody .TableRow:last-of-type .TableCell:last-child { + border-bottom-right-radius: var(--table-border-radius); + } + + /* TableHeader, TableCell */ + .TableCell, + .TableHeader { + padding: var(--table-cell-padding); + } + + /** + * Offset padding to make sure type aligns regardless of cell padding + * selection + */ + .TableRow > *:first-child { + padding-inline-start: 1rem; + } + + .TableRow > *:last-child { + padding-inline-end: 1rem; + } + + /* TableHeader */ + .TableHeader { + background-color: ${get('colors.canvas.subtle')}; + color: ${get('colors.fg.muted')}; + font-weight: 600; + text-align: start; + border-top: 1px solid ${get('colors.border.default')}; + } + + /* TableCell */ + .TableCell[scope='row'] { + color: ${get('colors.fg.default')}; + font-weight: 600; + text-align: start; + } +` + +interface TableProps extends React.ComponentPropsWithoutRef<'table'> { + /** + * Provide an id to an element which uniquely describes this table + */ + 'aria-describedby'?: string | undefined + + /** + * Provide an id to an element which uniquely labels this table + */ + 'aria-labelledby'?: string | undefined + + children?: React.ReactNode + + /** + * Specify the amount of space that should be available around the contents of + * a cell + */ + cellPadding?: 'condensed' | 'normal' | 'spacious' | undefined +} + +const Table = React.forwardRef(function Table({cellPadding = 'normal', ...rest}, ref) { + return +}) + +// ---------------------------------------------------------------------------- +// TableHead +// ---------------------------------------------------------------------------- + +interface TableHeadProps extends React.ComponentPropsWithoutRef<'thead'> { + children?: React.ReactNode +} + +function TableHead({children}: TableHeadProps) { + return
{children}
+
{columns.map(column => { From d4825686d239730bc232c25e6571b36252325117 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 27 Jan 2023 13:35:46 -0600 Subject: [PATCH 03/14] chore: update docs to remove $schema key --- src/DataTable/DataTable.docs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json index 2fdd2882802..6ea0e7582ac 100644 --- a/src/DataTable/DataTable.docs.json +++ b/src/DataTable/DataTable.docs.json @@ -1,5 +1,4 @@ { - "$schema": "../../script/components-json/component.schema.json", "id": "data-table", "name": "DataTable", "status": "draft", From 72c9851b526bee16b3fefefe6ef0f4309ee21b2e Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 27 Jan 2023 13:38:20 -0600 Subject: [PATCH 04/14] chore: fix eslint violations --- src/DataTable/index.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx index 80d7285922d..d322a66e072 100644 --- a/src/DataTable/index.tsx +++ b/src/DataTable/index.tsx @@ -11,6 +11,7 @@ interface Row { */ id: string | number + // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any } @@ -96,19 +97,26 @@ function DataTable({ {columns.map(column => { const columnProps = { - key: column.header, scope: column.rowHeader ? 'row' : undefined, } if (column.renderCell) { - return {column.renderCell(row)} + return ( + + {column.renderCell(row)} + + ) } if (column.field) { const value = row[column.field] if (typeof value === 'string' || typeof value === 'number' || React.isValidElement(value)) { - return {value} + return ( + + {value} + + ) } } From e545a8e2dc75b2d6dc9337ad0fd27912dc5c3521 Mon Sep 17 00:00:00 2001 From: joshblack Date: Fri, 27 Jan 2023 19:38:01 +0000 Subject: [PATCH 05/14] Update generated/components.json --- generated/components.json | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/generated/components.json b/generated/components.json index add33745532..3f108f7fa82 100644 --- a/generated/components.json +++ b/generated/components.json @@ -2586,6 +2586,116 @@ ], "subcomponents": [] }, + "data-table": { + "id": "data-table", + "name": "DataTable", + "status": "draft", + "a11yReviewed": false, + "stories": [], + "props": [ + { + "name": "aria-describedby", + "type": "string", + "description": "Provide an id to an element which uniquely describes this table" + }, + { + "name": "aria-labelledby", + "type": "string", + "description": "Provide an id to an element which uniquely labels this table" + }, + { + "name": "data", + "type": "Array", + "description": "Provide a collection of the rows which will be rendered inside of the table" + }, + { + "name": "columns", + "type": "Array>", + "description": "Provide the columns for the table and the fields in `data` to which they correspond" + }, + { + "name": "cellPadding", + "type": "'condensed' | 'normal' | 'spacious'", + "description": "Specify the amount of space that should be available around the contents of a cell" + } + ], + "subcomponents": [ + { + "name": "Table", + "props": [ + { + "name": "aria-describedby", + "type": "string", + "description": "Provide an id to an element which uniquely describes this table" + }, + { + "name": "aria-labelledby", + "type": "string", + "description": "Provide an id to an element which uniquely labels this table" + }, + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "cellPadding", + "type": "'condensed' | 'normal' | 'spacious'", + "description": "Specify the amount of space that should be available around the contents of a cell" + } + ] + }, + { + "name": "TableHead", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableBody", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableRow", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableHeader", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableCell", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "scope", + "type": "string", + "description": "Provide the scope for a table cell, useful for defining a row header using `scope=\"row\"`" + } + ] + } + ] + }, "drafts_dialog": { "id": "drafts_dialog", "name": "Dialog", From cb7f4d0b92996bbb2b858ffbdc0a6fb5e592eba4 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Fri, 27 Jan 2023 15:57:44 -0600 Subject: [PATCH 06/14] chore: remove surrounding fragment after title removal --- src/DataTable/index.tsx | 80 ++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx index d322a66e072..2907aa72aeb 100644 --- a/src/DataTable/index.tsx +++ b/src/DataTable/index.tsx @@ -82,52 +82,50 @@ function DataTable({ data, }: DataTableProps) { return ( - <> -
- - - {columns.map(column => { - return {column.header} - })} - - - - {data.map(row => { - return ( - - {columns.map(column => { - const columnProps = { - scope: column.rowHeader ? 'row' : undefined, - } - - if (column.renderCell) { +
+ + + {columns.map(column => { + return {column.header} + })} + + + + {data.map(row => { + return ( + + {columns.map(column => { + const columnProps = { + scope: column.rowHeader ? 'row' : undefined, + } + + if (column.renderCell) { + return ( + + {column.renderCell(row)} + + ) + } + + if (column.field) { + const value = row[column.field] + + if (typeof value === 'string' || typeof value === 'number' || React.isValidElement(value)) { return ( - {column.renderCell(row)} + {value} ) } - - if (column.field) { - const value = row[column.field] - - if (typeof value === 'string' || typeof value === 'number' || React.isValidElement(value)) { - return ( - - {value} - - ) - } - } - - return null - })} - - ) - })} - -
- + } + + return null + })} + + ) + })} + + ) } From 709729c3285f8ed71a78a19b3639df2e4df648d9 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 2 Feb 2023 16:01:51 -0600 Subject: [PATCH 07/14] Update docs/content/drafts/DataTable.mdx Co-authored-by: Cole Bemis --- docs/content/drafts/DataTable.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/drafts/DataTable.mdx b/docs/content/drafts/DataTable.mdx index 55058ca949e..348b8252d59 100644 --- a/docs/content/drafts/DataTable.mdx +++ b/docs/content/drafts/DataTable.mdx @@ -1,6 +1,6 @@ --- title: DataTable -componentId: data-table +componentId: data_table status: Draft --- From 67ac45aacc69dd648a1625643fb34ed1747243d4 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 2 Feb 2023 16:01:56 -0600 Subject: [PATCH 08/14] Update src/DataTable/DataTable.docs.json Co-authored-by: Cole Bemis --- src/DataTable/DataTable.docs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json index 6ea0e7582ac..1af132b69eb 100644 --- a/src/DataTable/DataTable.docs.json +++ b/src/DataTable/DataTable.docs.json @@ -1,5 +1,5 @@ { - "id": "data-table", + "id": "data_table", "name": "DataTable", "status": "draft", "a11yReviewed": false, From ef7838723cbd801d9ad3958fef21fb0a96cdfe57 Mon Sep 17 00:00:00 2001 From: joshblack Date: Thu, 2 Feb 2023 22:03:39 +0000 Subject: [PATCH 09/14] Update generated/components.json --- generated/components.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generated/components.json b/generated/components.json index 3f108f7fa82..a3d39309c7d 100644 --- a/generated/components.json +++ b/generated/components.json @@ -2586,8 +2586,8 @@ ], "subcomponents": [] }, - "data-table": { - "id": "data-table", + "data_table": { + "id": "data_table", "name": "DataTable", "status": "draft", "a11yReviewed": false, From a5a11abfa4bd8b5bb76691e2a54e6d6a73b27e9c Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 6 Feb 2023 11:13:51 -0600 Subject: [PATCH 10/14] docs(DataTable): remove extra imports and metadata --- src/DataTable/DataTable.stories.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx index f972cfb0eae..332c4503982 100644 --- a/src/DataTable/DataTable.stories.tsx +++ b/src/DataTable/DataTable.stories.tsx @@ -1,6 +1,6 @@ import {Meta, ComponentStory} from '@storybook/react' import React from 'react' -import {DataTable, Table, TableHead, TableBody, TableRow, TableHeader, TableCell} from '../DataTable' +import {DataTable} from '../DataTable' import Label from '../Label' import LabelGroup from '../LabelGroup' import RelativeTime from '../RelativeTime' @@ -8,14 +8,6 @@ import RelativeTime from '../RelativeTime' export default { title: 'Drafts/Components/DataTable', component: DataTable, - subcomponents: { - Table, - TableHead, - TableBody, - TableRow, - TableHeader, - TableCell, - }, } as Meta const now = Date.now() From a3aedac014f8e4aec94df100dd1e4f81b0d2b8fa Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 30 Jan 2023 14:09:57 -0600 Subject: [PATCH 11/14] chore(docs): disable controls warning --- .storybook/preview.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.storybook/preview.js b/.storybook/preview.js index d2b420fb9c3..05242052cd1 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -29,6 +29,9 @@ export const parameters = { root: '#html-addon-root', removeEmptyComments: true, }, + controls: { + hideNoControlsWarning: true, + }, options: { storySort: (a, b) => { const defaultOrder = [ From 0a4e390a36d479789f842d33ecf9f42144ffbcc2 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 30 Jan 2023 14:12:13 -0600 Subject: [PATCH 12/14] feat(DataTable): add support for title and subtitle --- src/DataTable/DataTable.docs.json | 37 +++ src/DataTable/DataTable.features.stories.tsx | 319 +++++++++++++++++++ src/DataTable/DataTable.stories.tsx | 100 +++--- src/DataTable/__tests__/DataTable.test.tsx | 66 +++- src/DataTable/index.tsx | 103 +++++- 5 files changed, 578 insertions(+), 47 deletions(-) create mode 100644 src/DataTable/DataTable.features.stories.tsx diff --git a/src/DataTable/DataTable.docs.json b/src/DataTable/DataTable.docs.json index 1af132b69eb..8a140da5c42 100644 --- a/src/DataTable/DataTable.docs.json +++ b/src/DataTable/DataTable.docs.json @@ -105,6 +105,43 @@ "description": "Provide the scope for a table cell, useful for defining a row header using `scope=\"row\"`" } ] + }, + { + "name": "TableContainer", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableTitle", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "id", + "type": "string", + "required": true + } + ] + }, + { + "name": "TableSubtitle", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "id", + "type": "string", + "required": true + } + ] } ] } diff --git a/src/DataTable/DataTable.features.stories.tsx b/src/DataTable/DataTable.features.stories.tsx new file mode 100644 index 00000000000..2bc06c63334 --- /dev/null +++ b/src/DataTable/DataTable.features.stories.tsx @@ -0,0 +1,319 @@ +import {Meta} from '@storybook/react' +import React from 'react' +import { + DataTable, + Table, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, + TableContainer, + TableTitle, + TableSubtitle, +} from '../DataTable' +import Label from '../Label' +import LabelGroup from '../LabelGroup' +import RelativeTime from '../RelativeTime' + +export default { + title: 'Drafts/Components/DataTable/Features', + component: DataTable, + subcomponents: { + Table, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, + TableContainer, + TableTitle, + TableSubtitle, + }, +} as Meta + +const now = Date.now() +const Second = 1000 +const Minute = 60 * Second +const Hour = 60 * Minute +const Day = 24 * Hour +const Week = 7 * Day +const Month = 4 * Week + +interface Repo { + id: number + name: string + type: 'public' | 'internal' + updatedAt: number + securityFeatures: { + dependabot: Array + codeScanning: Array + } +} + +const data: Array = [ + { + id: 1, + name: 'codeql-dca-worker', + type: 'internal', + updatedAt: now, + securityFeatures: { + dependabot: ['alerts', 'security updates'], + codeScanning: ['report secrets'], + }, + }, + { + id: 2, + name: 'aegir', + type: 'public', + updatedAt: now - 5 * Minute, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: ['report secrets'], + }, + }, + { + id: 3, + name: 'strapi', + type: 'public', + updatedAt: now - 1 * Hour, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 4, + name: 'codeql-ci-nightlies', + type: 'public', + updatedAt: now - 6 * Hour, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, + { + id: 5, + name: 'dependabot-updates', + type: 'public', + updatedAt: now - 1 * Day, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 6, + name: 'tsx-create-react-app', + type: 'public', + updatedAt: now - 1 * Week, + securityFeatures: { + dependabot: [], + codeScanning: [], + }, + }, + { + id: 7, + name: 'bootstrap', + type: 'public', + updatedAt: now - 1 * Month, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, + { + id: 8, + name: 'docker-templates', + type: 'public', + updatedAt: now - 3 * Month, + securityFeatures: { + dependabot: ['alerts'], + codeScanning: [], + }, + }, +] + +function uppercase(input: string): string { + return input[0].toUpperCase() + input.slice(1) +} + +export const Default = () => ( + + + Repositories + + + A subtitle could appear here to give extra context to the data. + + { + return + }, + }, + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return + }, + }, + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + }, + { + header: 'Code scanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( + + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + }, + ]} + /> + +) + +export const WithTitle = () => ( + + + Repositories + + { + return + }, + }, + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return + }, + }, + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + }, + { + header: 'Code scanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( + + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + }, + ]} + /> + +) + +export const WithTitleAndSubtitle = () => ( + + + Repositories + + + A subtitle could appear here to give extra context to the data. + + { + return + }, + }, + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return + }, + }, + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, + }, + { + header: 'Code scanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( + + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + }, + ]} + /> + +) diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx index 332c4503982..d5f757b7e4f 100644 --- a/src/DataTable/DataTable.stories.tsx +++ b/src/DataTable/DataTable.stories.tsx @@ -118,55 +118,65 @@ function uppercase(input: string): string { export const Playground: ComponentStory = args => { return ( - { - return + + + Repositories + + + A subtitle could appear here to give extra context to the data. + + { - return + { + header: 'Type', + field: 'type', + renderCell: row => { + return + }, }, - }, - { - header: 'Dependabot', - renderCell: row => { - return row.securityFeatures.dependabot.length > 0 ? ( - - {row.securityFeatures.dependabot.map(feature => { - return - })} - - ) : null + { + header: 'Updated', + field: 'updatedAt', + renderCell: row => { + return + }, }, - }, - { - header: 'Code scanning', - renderCell: row => { - return row.securityFeatures.codeScanning.length > 0 ? ( - - {row.securityFeatures.codeScanning.map(feature => { - return - })} - - ) : null + { + header: 'Dependabot', + renderCell: row => { + return row.securityFeatures.dependabot.length > 0 ? ( + + {row.securityFeatures.dependabot.map(feature => { + return + })} + + ) : null + }, }, - }, - ]} - /> + { + header: 'Code scanning', + renderCell: row => { + return row.securityFeatures.codeScanning.length > 0 ? ( + + {row.securityFeatures.codeScanning.map(feature => { + return + })} + + ) : null + }, + }, + ]} + /> + ) } diff --git a/src/DataTable/__tests__/DataTable.test.tsx b/src/DataTable/__tests__/DataTable.test.tsx index aa7920a4d64..a2631125e45 100644 --- a/src/DataTable/__tests__/DataTable.test.tsx +++ b/src/DataTable/__tests__/DataTable.test.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {DataTable} from '..' +import {DataTable, TableContainer, TableTitle, TableSubtitle} from '..' import {render, screen} from '@testing-library/react' describe('DataTable', () => { @@ -109,6 +109,38 @@ describe('DataTable', () => { expect(screen.getByRole('table', {name: 'custom-title'})).toBeInTheDocument() }) + it('should support custom labeling through `aria-labelledby` and `TableTitle`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render( + + + custom-title + + + , + ) + expect(screen.getByRole('table', {name: 'custom-title'})).toBeInTheDocument() + }) + it('should support custom descriptions through `aria-describedby`', () => { const columns = [ { @@ -139,6 +171,38 @@ describe('DataTable', () => { expect(screen.getByRole('table', {description: 'custom-description'})).toBeInTheDocument() }) + it('should support custom descriptions through `aria-describedby` and `TableSubtitle`', () => { + const columns = [ + { + header: 'Name', + field: 'name', + }, + ] + const data = [ + { + id: 1, + name: 'one', + }, + { + id: 2, + name: 'two', + }, + { + id: 3, + name: 'three', + }, + ] + render( + + + custom-description + + + , + ) + expect(screen.getByRole('table', {description: 'custom-description'})).toBeInTheDocument() + }) + it('should support customizing the `cellPadding` of cells', () => { const columns = [ { diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx index 2907aa72aeb..26a9cf0649b 100644 --- a/src/DataTable/index.tsx +++ b/src/DataTable/index.tsx @@ -1,5 +1,6 @@ import React from 'react' import styled from 'styled-components' +import Box from '../Box' import {get} from '../constants' // ---------------------------------------------------------------------------- @@ -233,6 +234,12 @@ const StyledTable = styled.table>` font-weight: 600; text-align: start; } + + /* Spacing if table details are present */ + .TableTitle + &, + .TableSubtitle + & { + margin-top: ${get('space.2')}; + } ` interface TableProps extends React.ComponentPropsWithoutRef<'table'> { @@ -331,4 +338,98 @@ function TableCell({children, scope}: TableCellProps) { ) } -export {DataTable, Table, TableHead, TableBody, TableRow, TableHeader, TableCell} +// ---------------------------------------------------------------------------- +// TableContainer +// ---------------------------------------------------------------------------- +interface TableContainerProps { + children?: React.ReactNode | undefined +} + +function TableContainer({children}: TableContainerProps) { + return {children} +} + +interface TableTitleProps { + /** + * Provide an alternate element or component to use as the container for + * `TableSubtitle`. This is useful when specifying markup that is more + * semantic for your use-case, such as a heading tag. + */ + as?: keyof JSX.IntrinsicElements | React.ComponentType + + children?: React.ReactNode | undefined + + /** + * Provide a unique id for the table subtitle. This should be used along with + * `aria-labelledby` on `DataTable` + */ + id: string +} + +function TableTitle({as, children, id}: TableTitleProps) { + return ( + + {children} + + ) +} + +interface TableSubtitleProps { + /** + * Provide an alternate element or component to use as the container for + * `TableSubtitle`. This is useful when specifying markup that is more + * semantic for your use-case + */ + as?: keyof JSX.IntrinsicElements | React.ComponentType + + children?: React.ReactNode | undefined + + /** + * Provide a unique id for the table subtitle. This should be used along with + * `aria-describedby` on `DataTable` + */ + id: string +} + +function TableSubtitle({as, children, id}: TableSubtitleProps) { + return ( + + {children} + + ) +} + +export { + DataTable, + Table, + TableHead, + TableBody, + TableRow, + TableHeader, + TableCell, + TableContainer, + TableTitle, + TableSubtitle, +} From 7e2e14c042281f7eab5536ac70c76d48b6270d41 Mon Sep 17 00:00:00 2001 From: joshblack Date: Mon, 30 Jan 2023 20:16:58 +0000 Subject: [PATCH 13/14] Update generated/components.json --- generated/components.json | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/generated/components.json b/generated/components.json index a3d39309c7d..4f74d6c345d 100644 --- a/generated/components.json +++ b/generated/components.json @@ -2693,6 +2693,43 @@ "description": "Provide the scope for a table cell, useful for defining a row header using `scope=\"row\"`" } ] + }, + { + "name": "TableContainer", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + } + ] + }, + { + "name": "TableTitle", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "id", + "type": "string", + "required": true + } + ] + }, + { + "name": "TableSubtitle", + "props": [ + { + "name": "children", + "type": "React.ReactNode" + }, + { + "name": "id", + "type": "string", + "required": true + } + ] } ] }, From c921bd84401cb4ea932947df4d1b4ac500bdd2db Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 6 Feb 2023 11:34:59 -0600 Subject: [PATCH 14/14] chore(data-table): update to use design token values --- src/DataTable/index.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/DataTable/index.tsx b/src/DataTable/index.tsx index 26a9cf0649b..85547bfcafa 100644 --- a/src/DataTable/index.tsx +++ b/src/DataTable/index.tsx @@ -374,8 +374,8 @@ function TableTitle({as, children, id}: TableTitleProps) { id={id} sx={{ color: 'fg.default', - fontWeight: 600, - fontSize: '0.875rem', + fontWeight: 'bold', + fontSize: 1, lineHeight: 'calc(20 / 14)', margin: 0, }} @@ -410,9 +410,9 @@ function TableSubtitle({as, children, id}: TableSubtitleProps) { id={id} sx={{ color: 'fg.default', - fontWeight: 400, - fontSize: '0.75rem', - lineHeight: 'calc(18 / 12)', + fontWeight: 'normal', + fontSize: 0, + lineHeight: 'default', margin: 0, }} >