Skip to content

Commit

Permalink
Implements column width features (#2996)
Browse files Browse the repository at this point in the history
* adds styles and feature stories

* explicitly adds roles to table elements

* adds column widths to Playground story

* adds tests

fixes max width with auto

* cleans up feature stories

* Create .changeset/flat-owls-grin.md

* corrects scope of subgrid styles

* fixes type issue caused by CSS custom properties

* manually merge row action stories
  • Loading branch information
mperrotti authored Mar 15, 2023
1 parent bb9f156 commit baa6395
Show file tree
Hide file tree
Showing 9 changed files with 450 additions and 10 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-owls-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Implements column width features for the DataTable
66 changes: 66 additions & 0 deletions src/DataTable/DataTable.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -930,6 +930,72 @@ export const WithRowActionMenu = () => (
</Table.Container>
)

export const MixedColumnWidths = () => (
<Table.Container>
<Table.Title as="h2" id="repositories">
Repositories
</Table.Title>
<DataTable
aria-labelledby="repositories"
aria-describedby="repositories-subtitle"
data={data}
columns={[
{
header: 'grow w/ 200px max',
field: 'name',
rowHeader: true,
width: 'grow',
maxWidth: '200px',
},
{
header: 'shrink w/ 100px min',
field: 'type',
renderCell: row => {
return <Label>{uppercase(row.type)}</Label>
},
width: 'shrink',
minWidth: '100px',
},
{
header: 'auto',
field: 'updatedAt',
renderCell: row => {
return <RelativeTime date={new Date(row.updatedAt)} />
},
width: 'auto',
},
{
header: '200px',
field: 'securityFeatures.dependabot',
renderCell: row => {
return row.securityFeatures.dependabot.length > 0 ? (
<LabelGroup>
{row.securityFeatures.dependabot.map(feature => {
return <Label key={feature}>{uppercase(feature)}</Label>
})}
</LabelGroup>
) : null
},
width: '200px',
},
{
header: 'undefined (defaults to grow)',
field: 'securityFeatures.codeScanning',
renderCell: row => {
return row.securityFeatures.codeScanning.length > 0 ? (
<LabelGroup>
{row.securityFeatures.codeScanning.map(feature => {
return <Label key={feature}>{uppercase(feature)}</Label>
})}
</LabelGroup>
) : null
},
},
]}
/>
</Table.Container>
)

export const WithCustomHeading = () => (
<>
<Heading as="h2" id="repositories">
Expand Down
31 changes: 28 additions & 3 deletions src/DataTable/DataTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import {Meta, ComponentStory} from '@storybook/react'
import {Meta} from '@storybook/react'
import React from 'react'
import {DataTable, Table} from '../DataTable'
import {DataTable, DataTableProps, Table} from '../DataTable'
import Label from '../Label'
import LabelGroup from '../LabelGroup'
import RelativeTime from '../RelativeTime'
import {UniqueRow} from './row'
import {getColumnWidthArgTypes, ColWidthArgTypes} from './storyHelpers'

export default {
title: 'Components/DataTable',
component: DataTable,
argTypes: getColumnWidthArgTypes(5),
} as Meta<typeof DataTable>

const now = Date.now()
Expand Down Expand Up @@ -179,7 +182,14 @@ export const Default = () => (
</Table.Container>
)

export const Playground: ComponentStory<typeof DataTable> = args => {
export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) => {
const getColWidth = (colIndex: number) => {
return args[`colWidth${colIndex}`] !== 'explicit width'
? args[`colWidth${colIndex}`]
: args[`explicitColWidth${colIndex}`]
? args[`explicitColWidth${colIndex}`]
: 'grow'
}
return (
<Table.Container>
<Table.Title as="h2" id="repositories">
Expand All @@ -198,20 +208,29 @@ export const Playground: ComponentStory<typeof DataTable> = args => {
header: 'Repository',
field: 'name',
rowHeader: true,
width: getColWidth(0),
minWidth: args.minColWidth0,
maxWidth: args.maxColWidth0,
},
{
header: 'Type',
field: 'type',
renderCell: row => {
return <Label>{uppercase(row.type)}</Label>
},
width: getColWidth(1),
minWidth: args.minColWidth1,
maxWidth: args.maxColWidth1,
},
{
header: 'Updated',
field: 'updatedAt',
renderCell: row => {
return <RelativeTime date={new Date(row.updatedAt)} />
},
width: getColWidth(2),
minWidth: args.minColWidth2,
maxWidth: args.maxColWidth2,
},
{
header: 'Dependabot',
Expand All @@ -225,6 +244,9 @@ export const Playground: ComponentStory<typeof DataTable> = args => {
</LabelGroup>
) : null
},
width: getColWidth(3),
minWidth: args.minColWidth3,
maxWidth: args.maxColWidth3,
},
{
header: 'Code scanning',
Expand All @@ -238,6 +260,9 @@ export const Playground: ComponentStory<typeof DataTable> = args => {
</LabelGroup>
) : null
},
width: getColWidth(4),
minWidth: args.minColWidth4,
maxWidth: args.maxColWidth4,
},
]}
/>
Expand Down
9 changes: 7 additions & 2 deletions src/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,19 @@ function DataTable<Data extends UniqueRow>({
initialSortColumn,
initialSortDirection,
}: DataTableProps<Data>) {
const {headers, rows, actions} = useTable({
const {headers, rows, actions, gridTemplateColumns} = useTable({
data,
columns,
initialSortColumn,
initialSortDirection,
})
return (
<Table aria-labelledby={labelledby} aria-describedby={describedby} cellPadding={cellPadding}>
<Table
aria-labelledby={labelledby}
aria-describedby={describedby}
cellPadding={cellPadding}
gridTemplateColumns={gridTemplateColumns}
>
<TableHead>
<TableRow>
{headers.map(header => {
Expand Down
57 changes: 53 additions & 4 deletions src/DataTable/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
background-color: ${get('colors.canvas.default')};
border-spacing: 0;
border-collapse: separate;
display: grid;
font-size: var(--table-font-size);
grid-template-columns: var(--grid-template-columns);
line-height: calc(20 / var(--table-font-size));
width: 100%;
overflow-x: auto;
Expand Down Expand Up @@ -138,6 +140,22 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
font-weight: 600;
text-align: start;
}
/* Grid layout */
.TableHead,
.TableBody,
.TableRow {
display: contents;
}
@supports (grid-template-columns: subgrid) {
.TableHead,
.TableBody,
.TableRow {
display: grid;
grid-template-columns: subgrid;
grid-column: -1 /1;
}
`

export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
Expand All @@ -151,15 +169,32 @@ export type TableProps = React.ComponentPropsWithoutRef<'table'> & {
*/
'aria-labelledby'?: string

/**
* Column width definitions
*/
gridTemplateColumns?: React.CSSProperties['gridTemplateColumns']

/**
* Specify the amount of space that should be available around the contents of
* a cell
*/
cellPadding?: 'condensed' | 'normal' | 'spacious'
}

const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table({cellPadding = 'normal', ...rest}, ref) {
return <StyledTable {...rest} data-cell-padding={cellPadding} className="Table" ref={ref} />
const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table(
{cellPadding = 'normal', gridTemplateColumns, ...rest},
ref,
) {
return (
<StyledTable
{...rest}
data-cell-padding={cellPadding}
style={{'--grid-template-columns': gridTemplateColumns} as React.CSSProperties}
className="Table"
role="table"
ref={ref}
/>
)
})

// ----------------------------------------------------------------------------
Expand All @@ -169,7 +204,14 @@ const Table = React.forwardRef<HTMLTableElement, TableProps>(function Table({cel
export type TableHeadProps = React.ComponentPropsWithoutRef<'thead'>

function TableHead({children}: TableHeadProps) {
return <thead className="TableHead">{children}</thead>
return (
// We need to explicitly pass this role because some ATs and browsers drop table semantics
// when we use `display: contents` or `display: grid` in the table
// eslint-disable-next-line jsx-a11y/no-redundant-roles
<thead className="TableHead" role="rowgroup">
{children}
</thead>
)
}

// ----------------------------------------------------------------------------
Expand All @@ -179,7 +221,14 @@ function TableHead({children}: TableHeadProps) {
export type TableBodyProps = React.ComponentPropsWithoutRef<'tbody'>

function TableBody({children}: TableBodyProps) {
return <tbody className="TableBody">{children}</tbody>
return (
// We need to explicitly pass this role because some ATs and browsers drop table semantics
// when we use `display: contents` or `display: grid` in the table
// eslint-disable-next-line jsx-a11y/no-redundant-roles
<tbody className="TableBody" role="rowgroup">
{children}
</tbody>
)
}

// ----------------------------------------------------------------------------
Expand Down
Loading

0 comments on commit baa6395

Please sign in to comment.