From 1f676be6bc16cb4ac380af77e8afdeb3e33abb32 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 6 Feb 2023 14:10:48 -0600 Subject: [PATCH 1/4] chore: update storybook to hide no 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 cebc5ba15dbb7f7adbe8a71a80e424fc8a0c84c4 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 6 Feb 2023 14:14:14 -0600 Subject: [PATCH 2/4] feat(DataTable): add TableContainer, Title, 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 | 68 +++- src/DataTable/index.tsx | 109 ++++++- 5 files changed, 585 insertions(+), 48 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..b890bcad723 100644 --- a/src/DataTable/__tests__/DataTable.test.tsx +++ b/src/DataTable/__tests__/DataTable.test.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import {DataTable} from '..' import {render, screen} from '@testing-library/react' +import React from 'react' +import {DataTable, TableContainer, TableTitle, TableSubtitle} from '..' describe('DataTable', () => { it('should render a semantic through `data` and `columns`', () => { @@ -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..4118189c25d 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' // ---------------------------------------------------------------------------- @@ -227,12 +228,24 @@ const StyledTable = styled.table>` border-top: 1px solid ${get('colors.border.default')}; } + /* TableRow */ + .TableRow:hover .TableCell { + /* TODO: update this token when the new primitive tokens are released */ + background-color: ${get('colors.actionListItem.default.hoverBg')}; + } + /* TableCell */ .TableCell[scope='row'] { color: ${get('colors.fg.default')}; 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 +344,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 7272cb380dd7fc0374c08e7c3d7d19f88953d5c2 Mon Sep 17 00:00:00 2001 From: joshblack Date: Mon, 6 Feb 2023 20:16:29 +0000 Subject: [PATCH 3/4] 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 488706874025ef5290c7b8147f38c9fe60bddc85 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 6 Feb 2023 15:02:54 -0600 Subject: [PATCH 4/4] chore: update story imports --- src/DataTable/DataTable.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DataTable/DataTable.stories.tsx b/src/DataTable/DataTable.stories.tsx index d5f757b7e4f..d4403c0619c 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} from '../DataTable' +import {DataTable, TableContainer, TableTitle, TableSubtitle} from '../DataTable' import Label from '../Label' import LabelGroup from '../LabelGroup' import RelativeTime from '../RelativeTime'