Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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