Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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