Skip to content

Commit 09e1133

Browse files
authored
Add horizontal alignment to column configuration (#3068)
* adds horizontal alignment to column configuration * adds changeset * adds classname to sort button
1 parent cfe099d commit 09e1133

File tree

7 files changed

+155
-15
lines changed

7 files changed

+155
-15
lines changed

.changeset/pink-tables-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Adds text alignment option to columns

src/DataTable/DataTable.features.stories.tsx

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import {DownloadIcon, KebabHorizontalIcon, PencilIcon, PlusIcon, TrashIcon} from '@primer/octicons-react'
1+
import {DownloadIcon, KebabHorizontalIcon, PencilIcon, PlusIcon, RepoIcon, TrashIcon} from '@primer/octicons-react'
22
import {action} from '@storybook/addon-actions'
33
import {Meta} from '@storybook/react'
44
import React from 'react'
55
import {ActionList} from '../ActionList'
66
import {ActionMenu} from '../ActionMenu'
7+
import Box from '../Box'
78
import {Button, IconButton} from '../Button'
89
import {DataTable, Table} from '../DataTable'
910
import Heading from '../Heading'
@@ -1130,3 +1131,85 @@ export const WithOverflow = () => (
11301131
</Table.Container>
11311132
</div>
11321133
)
1134+
1135+
export const WithRightAlignedColumns = () => {
1136+
const rows = Array.from(data).sort((a, b) => {
1137+
return b.updatedAt - a.updatedAt
1138+
})
1139+
return (
1140+
<Table.Container>
1141+
<Table.Title as="h2" id="repositories">
1142+
Repositories
1143+
</Table.Title>
1144+
<Table.Subtitle as="p" id="repositories-subtitle">
1145+
A subtitle could appear here to give extra context to the data.
1146+
</Table.Subtitle>
1147+
<DataTable
1148+
aria-labelledby="repositories"
1149+
aria-describedby="repositories-subtitle"
1150+
data={rows}
1151+
columns={[
1152+
{
1153+
header: () => (
1154+
<Box display="flex" alignItems="center" sx={{gap: 1}}>
1155+
<RepoIcon size={16} />
1156+
Repository
1157+
</Box>
1158+
),
1159+
field: 'name',
1160+
rowHeader: true,
1161+
sortBy: 'alphanumeric',
1162+
align: 'end',
1163+
},
1164+
{
1165+
header: 'Type',
1166+
field: 'type',
1167+
renderCell: row => {
1168+
return <Label>{uppercase(row.type)}</Label>
1169+
},
1170+
align: 'end',
1171+
},
1172+
{
1173+
header: 'Updated',
1174+
field: 'updatedAt',
1175+
sortBy: 'datetime',
1176+
renderCell: row => {
1177+
return <RelativeTime date={new Date(row.updatedAt)} />
1178+
},
1179+
align: 'end',
1180+
},
1181+
{
1182+
header: 'Dependabot',
1183+
field: 'securityFeatures.dependabot',
1184+
renderCell: row => {
1185+
return row.securityFeatures.dependabot.length > 0 ? (
1186+
<LabelGroup>
1187+
{row.securityFeatures.dependabot.map(feature => {
1188+
return <Label key={feature}>{uppercase(feature)}</Label>
1189+
})}
1190+
</LabelGroup>
1191+
) : null
1192+
},
1193+
align: 'end',
1194+
},
1195+
{
1196+
header: 'Code scanning',
1197+
field: 'securityFeatures.codeScanning',
1198+
renderCell: row => {
1199+
return row.securityFeatures.codeScanning.length > 0 ? (
1200+
<LabelGroup>
1201+
{row.securityFeatures.codeScanning.map(feature => {
1202+
return <Label key={feature}>{uppercase(feature)}</Label>
1203+
})}
1204+
</LabelGroup>
1205+
) : null
1206+
},
1207+
align: 'end',
1208+
},
1209+
]}
1210+
initialSortColumn="updatedAt"
1211+
initialSortDirection="DESC"
1212+
/>
1213+
</Table.Container>
1214+
)
1215+
}

src/DataTable/DataTable.stories.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {DataTable, DataTableProps, Table} from '../DataTable'
44
import Label from '../Label'
55
import LabelGroup from '../LabelGroup'
66
import RelativeTime from '../RelativeTime'
7+
import {CellAlignment} from './column'
78
import {UniqueRow} from './row'
89
import {getColumnWidthArgTypes, ColWidthArgTypes} from './storyHelpers'
910

@@ -190,6 +191,9 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
190191
? args[`explicitColWidth${colIndex}`]
191192
: 'grow'
192193
}
194+
195+
const align = args.align as CellAlignment
196+
193197
return (
194198
<Table.Container>
195199
<Table.Title as="h2" id="repositories">
@@ -211,6 +215,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
211215
width: getColWidth(0),
212216
minWidth: args.minColWidth0,
213217
maxWidth: args.maxColWidth0,
218+
align,
214219
},
215220
{
216221
header: 'Type',
@@ -221,6 +226,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
221226
width: getColWidth(1),
222227
minWidth: args.minColWidth1,
223228
maxWidth: args.maxColWidth1,
229+
align,
224230
},
225231
{
226232
header: 'Updated',
@@ -231,6 +237,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
231237
width: getColWidth(2),
232238
minWidth: args.minColWidth2,
233239
maxWidth: args.maxColWidth2,
240+
align,
234241
},
235242
{
236243
header: 'Dependabot',
@@ -247,6 +254,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
247254
width: getColWidth(3),
248255
minWidth: args.minColWidth3,
249256
maxWidth: args.maxColWidth3,
257+
align,
250258
},
251259
{
252260
header: 'Code scanning',
@@ -263,6 +271,7 @@ export const Playground = (args: DataTableProps<UniqueRow> & ColWidthArgTypes) =
263271
width: getColWidth(4),
264272
minWidth: args.minColWidth4,
265273
maxWidth: args.maxColWidth4,
274+
align,
266275
},
267276
]}
268277
/>
@@ -275,6 +284,15 @@ Playground.args = {
275284
}
276285

277286
Playground.argTypes = {
287+
align: {
288+
control: {
289+
type: 'radio',
290+
},
291+
type: {
292+
name: 'enum',
293+
value: ['start', 'end'],
294+
},
295+
},
278296
'aria-describedby': {
279297
control: false,
280298
table: {

src/DataTable/DataTable.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ function DataTable<Data extends UniqueRow>({
6767
initialSortColumn,
6868
initialSortDirection,
6969
})
70+
7071
return (
7172
<Table
7273
aria-labelledby={labelledby}
@@ -81,6 +82,7 @@ function DataTable<Data extends UniqueRow>({
8182
return (
8283
<TableSortHeader
8384
key={header.id}
85+
align={header.column.align}
8486
direction={header.getSortDirection()}
8587
onToggleSort={() => {
8688
actions.sortBy(header)
@@ -91,7 +93,7 @@ function DataTable<Data extends UniqueRow>({
9193
)
9294
}
9395
return (
94-
<TableHeader key={header.id}>
96+
<TableHeader key={header.id} align={header.column.align}>
9597
{typeof header.column.header === 'string' ? header.column.header : header.column.header()}
9698
</TableHeader>
9799
)
@@ -104,7 +106,7 @@ function DataTable<Data extends UniqueRow>({
104106
<TableRow key={row.id}>
105107
{row.getCells().map(cell => {
106108
return (
107-
<TableCell key={cell.id} scope={cell.rowHeader ? 'row' : undefined}>
109+
<TableCell key={cell.id} scope={cell.rowHeader ? 'row' : undefined} align={cell.column.align}>
108110
{cell.column.renderCell
109111
? cell.column.renderCell(row.getValue())
110112
: (cell.getValue() as React.ReactNode)}

src/DataTable/Table.tsx

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {get} from '../constants'
66
import sx, {SxProp} from '../sx'
77
import {SortDirection} from './sorting'
88
import {useOverflow} from '../hooks/useOverflow'
9+
import {CellAlignment} from './column'
910

1011
// ----------------------------------------------------------------------------
1112
// Table
@@ -55,9 +56,22 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
5556
5657
.TableHeader,
5758
.TableCell {
59+
text-align: start;
5860
border-bottom: 1px solid ${get('colors.border.default')};
5961
}
6062
63+
.TableHeader[data-cell-align='end'],
64+
.TableCell[data-cell-align='end'] {
65+
text-align: end;
66+
display: flex;
67+
justify-content: flex-end;
68+
}
69+
70+
.TableHeader[data-cell-align='end'] .TableSortButton {
71+
display: flex;
72+
flex-direction: row-reverse;
73+
}
74+
6175
.TableHead .TableRow:first-of-type .TableHeader {
6276
border-top: 1px solid ${get('colors.border.default')};
6377
}
@@ -102,7 +116,6 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
102116
background-color: ${get('colors.canvas.subtle')};
103117
color: ${get('colors.fg.muted')};
104118
font-weight: 600;
105-
text-align: start;
106119
border-top: 1px solid ${get('colors.border.default')};
107120
}
108121
@@ -118,7 +131,7 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
118131
119132
/* The ASC icon is visible if the header is sortable and is hovered or focused */
120133
.TableHeader:hover .TableSortIcon--ascending,
121-
.TableHeader button:focus .TableSortIcon--ascending {
134+
.TableHeader .TableSortButton:focus .TableSortIcon--ascending {
122135
visibility: visible;
123136
}
124137
@@ -138,7 +151,6 @@ const StyledTable = styled.table<React.ComponentPropsWithoutRef<'table'>>`
138151
.TableCell[scope='row'] {
139152
color: ${get('colors.fg.default')};
140153
font-weight: 600;
141-
text-align: start;
142154
}
143155
144156
/* Grid layout */
@@ -238,11 +250,16 @@ function TableBody({children}: TableBodyProps) {
238250
// TableHeader
239251
// ----------------------------------------------------------------------------
240252

241-
export type TableHeaderProps = React.ComponentPropsWithoutRef<'th'>
253+
export type TableHeaderProps = Omit<React.ComponentPropsWithoutRef<'th'>, 'align'> & {
254+
/**
255+
* The horizontal alignment of the cell's content
256+
*/
257+
align?: CellAlignment
258+
}
242259

243-
function TableHeader({children, ...rest}: TableHeaderProps) {
260+
function TableHeader({align, children, ...rest}: TableHeaderProps) {
244261
return (
245-
<th {...rest} className="TableHeader" role="columnheader" scope="col">
262+
<th {...rest} className="TableHeader" role="columnheader" scope="col" data-cell-align={align}>
246263
{children}
247264
</th>
248265
)
@@ -261,12 +278,14 @@ type TableSortHeaderProps = TableHeaderProps & {
261278
onToggleSort: () => void
262279
}
263280

264-
function TableSortHeader({children, direction, onToggleSort, ...rest}: TableSortHeaderProps) {
281+
function TableSortHeader({align, children, direction, onToggleSort, ...rest}: TableSortHeaderProps) {
265282
const ariaSort = direction === 'DESC' ? 'descending' : direction === 'ASC' ? 'ascending' : undefined
283+
266284
return (
267-
<TableHeader {...rest} aria-sort={ariaSort}>
285+
<TableHeader {...rest} aria-sort={ariaSort} align={align}>
268286
<Button
269287
type="button"
288+
className="TableSortButton"
270289
onClick={() => {
271290
onToggleSort()
272291
}}
@@ -299,20 +318,25 @@ function TableRow({children, ...rest}: TableRowProps) {
299318
// TableCell
300319
// ----------------------------------------------------------------------------
301320

302-
export type TableCellProps = React.ComponentPropsWithoutRef<'td'> & {
321+
export type TableCellProps = Omit<React.ComponentPropsWithoutRef<'td'>, 'align'> & {
322+
/**
323+
* The horizontal alignment of the cell's content
324+
*/
325+
align?: CellAlignment
326+
303327
/**
304328
* Provide the scope for a table cell, useful for defining a row header using
305329
* `scope="row"`
306330
*/
307331
scope?: 'row'
308332
}
309333

310-
function TableCell({children, scope, ...rest}: TableCellProps) {
334+
function TableCell({align, children, scope, ...rest}: TableCellProps) {
311335
const BaseComponent = scope ? 'th' : 'td'
312336
const role = scope ? 'rowheader' : 'cell'
313337

314338
return (
315-
<BaseComponent {...rest} className="TableCell" scope={scope} role={role}>
339+
<BaseComponent {...rest} className="TableCell" scope={scope} role={role} data-cell-align={align}>
316340
{children}
317341
</BaseComponent>
318342
)

src/DataTable/column.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@ import {UniqueRow} from './row'
33
import {SortStrategy, CustomSortStrategy} from './sorting'
44

55
export type ColumnWidth = 'grow' | 'shrink' | 'auto' | React.CSSProperties['width']
6+
7+
export type CellAlignment = 'start' | 'end' | undefined
68
export interface Column<Data extends UniqueRow> {
79
id?: string
810

11+
/**
12+
* The horizontal alignment of the column's content
13+
*/
14+
align?: CellAlignment
15+
916
/**
1017
* Provide the name of the column. This will be rendered as a table header
1118
* within the table itself

src/ToggleSwitch/ToggleSwitch.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {get} from '../constants'
88
import {useProvidedStateOrCreate} from '../hooks'
99
import sx, {BetterSystemStyleObject, SxProp} from '../sx'
1010
import VisuallyHidden from '../_VisuallyHidden'
11+
import {CellAlignment} from '../DataTable/column'
1112

1213
const TRANSITION_DURATION = '80ms'
1314
const EASE_OUT_QUAD_CURVE = 'cubic-bezier(0.5, 1, 0.89, 1)'
@@ -34,7 +35,7 @@ export type ToggleSwitchProps = {
3435
/** Whether the "on" and "off" labels should appear before or after the switch.
3536
* **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.
3637
*/
37-
statusLabelPosition?: 'start' | 'end'
38+
statusLabelPosition?: CellAlignment
3839
} & SxProp
3940

4041
const sizeVariants = variant({

0 commit comments

Comments
 (0)