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/pink-tables-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Adds text alignment option to columns
85 changes: 84 additions & 1 deletion src/DataTable/DataTable.features.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {DownloadIcon, KebabHorizontalIcon, PencilIcon, PlusIcon, TrashIcon} from '@primer/octicons-react'
import {DownloadIcon, KebabHorizontalIcon, PencilIcon, PlusIcon, RepoIcon, TrashIcon} from '@primer/octicons-react'
import {action} from '@storybook/addon-actions'
import {Meta} from '@storybook/react'
import React from 'react'
import {ActionList} from '../ActionList'
import {ActionMenu} from '../ActionMenu'
import Box from '../Box'
import {Button, IconButton} from '../Button'
import {DataTable, Table} from '../DataTable'
import Heading from '../Heading'
Expand Down Expand Up @@ -1130,3 +1131,85 @@ export const WithOverflow = () => (
</Table.Container>
</div>
)

export const WithRightAlignedColumns = () => {
const rows = Array.from(data).sort((a, b) => {
return b.updatedAt - a.updatedAt
})
return (
<Table.Container>
<Table.Title as="h2" id="repositories">
Repositories
</Table.Title>
<Table.Subtitle as="p" id="repositories-subtitle">
A subtitle could appear here to give extra context to the data.
</Table.Subtitle>
<DataTable
aria-labelledby="repositories"
aria-describedby="repositories-subtitle"
data={rows}
columns={[
{
header: () => (
<Box display="flex" alignItems="center" sx={{gap: 1}}>
<RepoIcon size={16} />
Repository
</Box>
),
field: 'name',
rowHeader: true,
sortBy: 'alphanumeric',
align: 'end',
},
{
header: 'Type',
field: 'type',
renderCell: row => {
return <Label>{uppercase(row.type)}</Label>
},
align: 'end',
},
{
header: 'Updated',
field: 'updatedAt',
sortBy: 'datetime',
renderCell: row => {
return <RelativeTime date={new Date(row.updatedAt)} />
},
align: 'end',
},
{
header: 'Dependabot',
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
},
align: 'end',
},
{
header: 'Code scanning',
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
},
align: 'end',
},
]}
initialSortColumn="updatedAt"
initialSortDirection="DESC"
/>
</Table.Container>
)
}
18 changes: 18 additions & 0 deletions src/DataTable/DataTable.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {DataTable, DataTableProps, Table} from '../DataTable'
import Label from '../Label'
import LabelGroup from '../LabelGroup'
import RelativeTime from '../RelativeTime'
import {CellAlignment} from './column'
import {UniqueRow} from './row'
import {getColumnWidthArgTypes, ColWidthArgTypes} from './storyHelpers'

Expand Down Expand Up @@ -190,6 +191,9 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
? args[`explicitColWidth${colIndex}`]
: 'grow'
}

const align = args.align as CellAlignment

return (
<Table.Container>
<Table.Title as="h2" id="repositories">
Expand All @@ -211,6 +215,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
width: getColWidth(0),
minWidth: args.minColWidth0,
maxWidth: args.maxColWidth0,
align,
},
{
header: 'Type',
Expand All @@ -221,6 +226,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
width: getColWidth(1),
minWidth: args.minColWidth1,
maxWidth: args.maxColWidth1,
align,
},
{
header: 'Updated',
Expand All @@ -231,6 +237,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
width: getColWidth(2),
minWidth: args.minColWidth2,
maxWidth: args.maxColWidth2,
align,
},
{
header: 'Dependabot',
Expand All @@ -247,6 +254,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
width: getColWidth(3),
minWidth: args.minColWidth3,
maxWidth: args.maxColWidth3,
align,
},
{
header: 'Code scanning',
Expand All @@ -263,6 +271,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
width: getColWidth(4),
minWidth: args.minColWidth4,
maxWidth: args.maxColWidth4,
align,
},
]}
/>
Expand All @@ -275,6 +284,15 @@ Playground.args = {
}

Playground.argTypes = {
align: {
control: {
type: 'radio',
},
type: {
name: 'enum',
value: ['start', 'end'],
},
},
'aria-describedby': {
control: false,
table: {
Expand Down
6 changes: 4 additions & 2 deletions src/DataTable/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function DataTable<Data extends UniqueRow>({
initialSortColumn,
initialSortDirection,
})

return (
<Table
aria-labelledby={labelledby}
Expand All @@ -81,6 +82,7 @@ function DataTable<Data extends UniqueRow>({
return (
<TableSortHeader
key={header.id}
align={header.column.align}
direction={header.getSortDirection()}
onToggleSort={() => {
actions.sortBy(header)
Expand All @@ -91,7 +93,7 @@ function DataTable<Data extends UniqueRow>({
)
}
return (
<TableHeader key={header.id}>
<TableHeader key={header.id} align={header.column.align}>
{typeof header.column.header === 'string' ? header.column.header : header.column.header()}
</TableHeader>
)
Expand All @@ -104,7 +106,7 @@ function DataTable<Data extends UniqueRow>({
<TableRow key={row.id}>
{row.getCells().map(cell => {
return (
<TableCell key={cell.id} scope={cell.rowHeader ? 'row' : undefined}>
<TableCell key={cell.id} scope={cell.rowHeader ? 'row' : undefined} align={cell.column.align}>
{cell.column.renderCell
? cell.column.renderCell(row.getValue())
: (cell.getValue() as React.ReactNode)}
Expand Down
46 changes: 35 additions & 11 deletions src/DataTable/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {get} from '../constants'
import sx, {SxProp} from '../sx'
import {SortDirection} from './sorting'
import {useOverflow} from '../hooks/useOverflow'
import {CellAlignment} from './column'

// ----------------------------------------------------------------------------
// Table
Expand Down Expand Up @@ -55,9 +56,22 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`

.TableHeader,
.TableCell {
text-align: start;
border-bottom: 1px solid ${get('colors.border.default')};
}

.TableHeader[data-cell-align='end'],
.TableCell[data-cell-align='end'] {
text-align: end;
display: flex;
justify-content: flex-end;
}

.TableHeader[data-cell-align='end'] .TableSortButton {
display: flex;
flex-direction: row-reverse;
}

.TableHead .TableRow:first-of-type .TableHeader {
border-top: 1px solid ${get('colors.border.default')};
}
Expand Down Expand Up @@ -102,7 +116,6 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
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')};
}

Expand All @@ -118,7 +131,7 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`

/* The ASC icon is visible if the header is sortable and is hovered or focused */
.TableHeader:hover .TableSortIcon--ascending,
.TableHeader button:focus .TableSortIcon--ascending {
.TableHeader .TableSortButton:focus .TableSortIcon--ascending {
visibility: visible;
}

Expand All @@ -138,7 +151,6 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
.TableCell[scope='row'] {
color: ${get('colors.fg.default')};
font-weight: 600;
text-align: start;
}

/* Grid layout */
Expand Down Expand Up @@ -238,11 +250,16 @@ function TableBody({children}: TableBodyProps) {
// TableHeader
// ----------------------------------------------------------------------------

export type TableHeaderProps = React.ComponentPropsWithoutRef<'th'>
export type TableHeaderProps = Omit<React.ComponentPropsWithoutRef<'th'>, 'align'> & {
/**
* The horizontal alignment of the cell's content
*/
align?: CellAlignment
}

function TableHeader({children, ...rest}: TableHeaderProps) {
function TableHeader({align, children, ...rest}: TableHeaderProps) {
return (
<th {...rest} className="TableHeader" role="columnheader" scope="col">
<th {...rest} className="TableHeader" role="columnheader" scope="col" data-cell-align={align}>
{children}
</th>
)
Expand All @@ -261,12 +278,14 @@ type TableSortHeaderProps = TableHeaderProps & {
onToggleSort: () => void
}

function TableSortHeader({children, direction, onToggleSort, ...rest}: TableSortHeaderProps) {
function TableSortHeader({align, children, direction, onToggleSort, ...rest}: TableSortHeaderProps) {
const ariaSort = direction === 'DESC' ? 'descending' : direction === 'ASC' ? 'ascending' : undefined

return (
<TableHeader {...rest} aria-sort={ariaSort}>
<TableHeader {...rest} aria-sort={ariaSort} align={align}>
<Button
type="button"
className="TableSortButton"
onClick={() => {
onToggleSort()
}}
Expand Down Expand Up @@ -299,20 +318,25 @@ function TableRow({children, ...rest}: TableRowProps) {
// TableCell
// ----------------------------------------------------------------------------

export type TableCellProps = React.ComponentPropsWithoutRef<'td'> & {
export type TableCellProps = Omit<React.ComponentPropsWithoutRef<'td'>, 'align'> & {
/**
* The horizontal alignment of the cell's content
*/
align?: CellAlignment

/**
* Provide the scope for a table cell, useful for defining a row header using
* `scope="row"`
*/
scope?: 'row'
}

function TableCell({children, scope, ...rest}: TableCellProps) {
function TableCell({align, children, scope, ...rest}: TableCellProps) {
const BaseComponent = scope ? 'th' : 'td'
const role = scope ? 'rowheader' : 'cell'

return (
<BaseComponent {...rest} className="TableCell" scope={scope} role={role}>
<BaseComponent {...rest} className="TableCell" scope={scope} role={role} data-cell-align={align}>
{children}
</BaseComponent>
)
Expand Down
7 changes: 7 additions & 0 deletions src/DataTable/column.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import {UniqueRow} from './row'
import {SortStrategy, CustomSortStrategy} from './sorting'

export type ColumnWidth = 'grow' | 'shrink' | 'auto' | React.CSSProperties['width']

export type CellAlignment = 'start' | 'end' | undefined
export interface Column<Data extends UniqueRow> {
id?: string

/**
* The horizontal alignment of the column's content
*/
align?: CellAlignment

/**
* Provide the name of the column. This will be rendered as a table header
* within the table itself
Expand Down
3 changes: 2 additions & 1 deletion src/ToggleSwitch/ToggleSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {get} from '../constants'
import {useProvidedStateOrCreate} from '../hooks'
import sx, {BetterSystemStyleObject, SxProp} from '../sx'
import VisuallyHidden from '../_VisuallyHidden'
import {CellAlignment} from '../DataTable/column'

const TRANSITION_DURATION = '80ms'
const EASE_OUT_QUAD_CURVE = 'cubic-bezier(0.5, 1, 0.89, 1)'
Expand All @@ -34,7 +35,7 @@ export type ToggleSwitchProps = {
/** Whether the "on" and "off" labels should appear before or after the switch.
* **This should only be changed when the switch's alignment needs to be adjusted.** For example: It needs to be left-aligned because the label appears above it and the caption appears below it.
*/
statusLabelPosition?: 'start' | 'end'
statusLabelPosition?: CellAlignment
} & SxProp

const sizeVariants = variant({
Expand Down