Skip to content

Commit f1fedf3

Browse files
authored
feat: Add support for onToggleSort to DataTable (#5991)
1 parent 159d124 commit f1fedf3

File tree

5 files changed

+121
-0
lines changed

5 files changed

+121
-0
lines changed

.changeset/salty-bikes-learn.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+
Add support for the `onToggleSort` prop to `DataTable`

packages/react/src/DataTable/DataTable.docs.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@
9898
"required": false,
9999
"description": "Provide the sort direction that the table should be sorted by on the\ncurrently sorted column",
100100
"defaultValue": ""
101+
},
102+
{
103+
"name": "onToggleSort",
104+
"type": "(columnId: ObjectPaths<Data> | string | number, direction: 'ASC' | 'DESC') => void",
105+
"required": false,
106+
"description": "Fires every time the user clicks a sortable column header. It reports the column id that is now sorted and the direction after the toggle (never 'NONE').",
107+
"defaultValue": ""
101108
}
102109
],
103110
"subcomponents": [

packages/react/src/DataTable/DataTable.features.stories.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,69 @@ export const WithRightAlignedColumns = () => {
14461446
)
14471447
}
14481448

1449+
export const WithSortEvents = () => (
1450+
<Table.Container>
1451+
<Table.Title as="h2" id="repositories">
1452+
Repositories
1453+
</Table.Title>
1454+
<Table.Subtitle as="p" id="repositories-subtitle">
1455+
Click any sortable header and watch the Actions panel.
1456+
</Table.Subtitle>
1457+
1458+
<DataTable
1459+
aria-labelledby="repositories"
1460+
aria-describedby="repositories-subtitle"
1461+
data={data}
1462+
onToggleSort={(columnId, direction) => action('onToggleSort')({columnId, direction})}
1463+
columns={[
1464+
{
1465+
header: 'Repository',
1466+
field: 'name',
1467+
rowHeader: true,
1468+
sortBy: 'alphanumeric',
1469+
},
1470+
{
1471+
header: 'Type',
1472+
field: 'type',
1473+
renderCell: row => <Label>{uppercase(row.type)}</Label>,
1474+
},
1475+
{
1476+
header: 'Updated',
1477+
field: 'updatedAt',
1478+
sortBy: 'datetime',
1479+
renderCell: row => <RelativeTime date={new Date(row.updatedAt)} />,
1480+
},
1481+
{
1482+
header: 'Dependabot',
1483+
field: 'securityFeatures.dependabot',
1484+
renderCell: row =>
1485+
row.securityFeatures.dependabot.length ? (
1486+
<LabelGroup>
1487+
{row.securityFeatures.dependabot.map(feature => (
1488+
<Label key={feature}>{uppercase(feature)}</Label>
1489+
))}
1490+
</LabelGroup>
1491+
) : null,
1492+
},
1493+
{
1494+
header: 'Code scanning',
1495+
field: 'securityFeatures.codeScanning',
1496+
renderCell: row =>
1497+
row.securityFeatures.codeScanning.length ? (
1498+
<LabelGroup>
1499+
{row.securityFeatures.codeScanning.map(feature => (
1500+
<Label key={feature}>{uppercase(feature)}</Label>
1501+
))}
1502+
</LabelGroup>
1503+
) : null,
1504+
},
1505+
]}
1506+
initialSortColumn="updatedAt"
1507+
initialSortDirection="DESC"
1508+
/>
1509+
</Table.Container>
1510+
)
1511+
14491512
export const WithPagination = () => {
14501513
const pageSize = 10
14511514
const [pageIndex, setPageIndex] = React.useState(0)

packages/react/src/DataTable/DataTable.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ export type DataTableProps<Data extends UniqueRow> = {
5959
* @returns The unique identifier for the row, which can be a string or number.
6060
*/
6161
getRowId?: (rowData: Data) => string | number
62+
63+
/**
64+
* Fires every time the user clicks a sortable column header. It reports
65+
* the column id that is now sorted and the direction after the toggle
66+
* (never `"NONE"`).
67+
*/
68+
onToggleSort?: (columnId: ObjectPaths<Data> | string | number, direction: Exclude<SortDirection, 'NONE'>) => void
6269
}
6370

6471
function defaultGetRowId<D extends UniqueRow>(row: D) {
@@ -74,6 +81,7 @@ function DataTable<Data extends UniqueRow>({
7481
initialSortColumn,
7582
initialSortDirection,
7683
getRowId = defaultGetRowId,
84+
onToggleSort,
7785
}: DataTableProps<Data>) {
7886
const {headers, rows, actions, gridTemplateColumns} = useTable({
7987
data,
@@ -100,7 +108,10 @@ function DataTable<Data extends UniqueRow>({
100108
align={header.column.align}
101109
direction={header.getSortDirection()}
102110
onToggleSort={() => {
111+
const nextDirection: Exclude<SortDirection, 'NONE'> =
112+
header.getSortDirection() === 'ASC' ? 'DESC' : 'ASC'
103113
actions.sortBy(header)
114+
onToggleSort?.(header.id, nextDirection)
104115
}}
105116
>
106117
{typeof header.column.header === 'string' ? header.column.header : header.column.header()}

packages/react/src/DataTable/__tests__/DataTable.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,41 @@ describe('DataTable', () => {
886886
expect(customSortFn).toHaveBeenCalled()
887887
expect(getRowOrder()).toEqual(['3', '2', '1'])
888888
})
889+
890+
it('invokes onToggleSort with column id and next direction', async () => {
891+
const user = userEvent.setup()
892+
const handler = vi.fn()
893+
894+
render(
895+
<DataTable
896+
data={[
897+
{id: 1, first: 'a', second: 'c'},
898+
{id: 2, first: 'b', second: 'b'},
899+
{id: 3, first: 'c', second: 'a'},
900+
]}
901+
columns={[
902+
{header: 'First', field: 'first', sortBy: true},
903+
{header: 'Second', field: 'second', sortBy: true},
904+
]}
905+
initialSortColumn="first"
906+
initialSortDirection="ASC"
907+
onToggleSort={handler}
908+
/>,
909+
)
910+
911+
// No calls on initial render
912+
expect(handler).not.toHaveBeenCalled()
913+
914+
// Same column, flips ASC to DESC
915+
await user.click(screen.getByText('First'))
916+
expect(handler).toHaveBeenLastCalledWith('first', 'DESC')
917+
918+
// Different column, resets to ASC on that column
919+
await user.click(screen.getByText('Second'))
920+
expect(handler).toHaveBeenLastCalledWith('second', 'ASC')
921+
922+
expect(handler).toHaveBeenCalledTimes(2)
923+
})
889924
})
890925

891926
describe('column widths', () => {

0 commit comments

Comments
 (0)