Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
2 changes: 1 addition & 1 deletion generated/components.json
Original file line number Diff line number Diff line change
Expand Up @@ -1854,7 +1854,7 @@
},
{
"id": "drafts-components-datatable--with-sorting",
"code": "() => {\n const rows = Array.from(data).sort((a, b) => {\n return b.updatedAt - a.updatedAt\n })\n return (\n <Table.Container>\n <Table.Title as=\"h2\" id=\"repositories\">\n Repositories\n </Table.Title>\n <Table.Subtitle as=\"p\" id=\"repositories-subtitle\">\n A subtitle could appear here to give extra context to the data.\n </Table.Subtitle>\n <DataTable\n aria-labelledby=\"repositories\"\n aria-describedby=\"repositories-subtitle\"\n data={rows}\n columns={[\n {\n header: 'Repository',\n field: 'name',\n rowHeader: true,\n sortBy: true,\n },\n {\n header: 'Type',\n field: 'type',\n renderCell: (row) => {\n return <Label>{uppercase(row.type)}</Label>\n },\n },\n {\n header: 'Updated',\n field: 'updatedAt',\n sortBy: true,\n renderCell: (row) => {\n return <RelativeTime date={new Date(row.updatedAt)} />\n },\n },\n {\n header: 'Dependabot',\n field: 'securityFeatures.dependabot',\n renderCell: (row) => {\n return row.securityFeatures.dependabot.length > 0 ? (\n <LabelGroup>\n {row.securityFeatures.dependabot.map((feature) => {\n return <Label key={feature}>{uppercase(feature)}</Label>\n })}\n </LabelGroup>\n ) : null\n },\n },\n {\n header: 'Code scanning',\n field: 'securityFeatures.codeScanning',\n renderCell: (row) => {\n return row.securityFeatures.codeScanning.length > 0 ? (\n <LabelGroup>\n {row.securityFeatures.codeScanning.map((feature) => {\n return <Label key={feature}>{uppercase(feature)}</Label>\n })}\n </LabelGroup>\n ) : null\n },\n },\n ]}\n initialSortColumn=\"updatedAt\"\n initialSortDirection=\"DESC\"\n />\n </Table.Container>\n )\n}"
"code": "() => {\n const rows = Array.from(data).sort((a, b) => {\n return b.updatedAt - a.updatedAt\n })\n return (\n <Table.Container>\n <Table.Title as=\"h2\" id=\"repositories\">\n Repositories\n </Table.Title>\n <Table.Subtitle as=\"p\" id=\"repositories-subtitle\">\n A subtitle could appear here to give extra context to the data.\n </Table.Subtitle>\n <DataTable\n aria-labelledby=\"repositories\"\n aria-describedby=\"repositories-subtitle\"\n data={rows}\n columns={[\n {\n header: 'Repository',\n field: 'name',\n rowHeader: true,\n sortBy: 'alphanumeric',\n },\n {\n header: 'Type',\n field: 'type',\n renderCell: (row) => {\n return <Label>{uppercase(row.type)}</Label>\n },\n },\n {\n header: 'Updated',\n field: 'updatedAt',\n sortBy: 'datetime',\n renderCell: (row) => {\n return <RelativeTime date={new Date(row.updatedAt)} />\n },\n },\n {\n header: 'Dependabot',\n field: 'securityFeatures.dependabot',\n renderCell: (row) => {\n return row.securityFeatures.dependabot.length > 0 ? (\n <LabelGroup>\n {row.securityFeatures.dependabot.map((feature) => {\n return <Label key={feature}>{uppercase(feature)}</Label>\n })}\n </LabelGroup>\n ) : null\n },\n },\n {\n header: 'Code scanning',\n field: 'securityFeatures.codeScanning',\n renderCell: (row) => {\n return row.securityFeatures.codeScanning.length > 0 ? (\n <LabelGroup>\n {row.securityFeatures.codeScanning.map((feature) => {\n return <Label key={feature}>{uppercase(feature)}</Label>\n })}\n </LabelGroup>\n ) : null\n },\n },\n ]}\n initialSortColumn=\"updatedAt\"\n initialSortDirection=\"DESC\"\n />\n </Table.Container>\n )\n}"
},
{
"id": "drafts-components-datatable--with-actions",
Expand Down
85 changes: 83 additions & 2 deletions src/DataTable/DataTable.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ export const WithSorting = () => {
header: 'Repository',
field: 'name',
rowHeader: true,
sortBy: true,
sortBy: 'alphanumeric',
},
{
header: 'Type',
Expand All @@ -275,7 +275,7 @@ export const WithSorting = () => {
{
header: 'Updated',
field: 'updatedAt',
sortBy: true,
sortBy: 'datetime',
renderCell: row => {
return <RelativeTime date={new Date(row.updatedAt)} />
},
Expand Down Expand Up @@ -314,6 +314,87 @@ export const WithSorting = () => {
)
}

export const WithCustomSorting = () => {
const rows = Array.from(data).sort((a, b) => {
return b.updatedAt - a.updatedAt
})
const sortByDependabotFeatures = (a: Repo, b: Repo): number => {
if (a.securityFeatures.dependabot.length > b.securityFeatures.dependabot.length) {
return -1
} else if (b.securityFeatures.dependabot.length < a.securityFeatures.dependabot.length) {
return 1
}
return 0
}
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: 'Repository',
field: 'name',
rowHeader: true,
sortBy: 'alphanumeric',
},
{
header: 'Type',
field: 'type',
renderCell: row => {
return <Label>{uppercase(row.type)}</Label>
},
},
{
header: 'Updated',
field: 'updatedAt',
sortBy: 'datetime',
renderCell: row => {
return <RelativeTime date={new Date(row.updatedAt)} />
},
},
{
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
},
sortBy: sortByDependabotFeatures,
},
{
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
},
},
]}
initialSortColumn="updatedAt"
initialSortDirection="DESC"
/>
</Table.Container>
)
}

export const WithAction = () => (
<Table.Container>
<Table.Title as="h2" id="repositories">
Expand Down
51 changes: 51 additions & 0 deletions src/DataTable/__tests__/DataTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -620,5 +620,56 @@ describe('DataTable', () => {
[1, 3],
])
})

it('should support a custom sort function', async () => {
const user = userEvent.setup()
const customSortFn = jest.fn().mockImplementation((a, b) => {
return a.value - b.value
})

render(
<DataTable
data={[
{
id: 1,
value: 1,
},
{
id: 2,
value: 2,
},
{
id: 3,
value: 3,
},
]}
columns={[
{
header: 'Value',
field: 'value',
sortBy: customSortFn,
},
]}
initialSortColumn="value"
initialSortDirection="ASC"
/>,
)

function getRowOrder() {
return screen
.getAllByRole('row')
.filter(row => {
return queryByRole(row, 'cell')
})
.map(row => {
const cell = getByRole(row, 'cell')
return cell.textContent
})
}

await user.click(screen.getByText('Value'))
expect(customSortFn).toHaveBeenCalled()
expect(getRowOrder()).toEqual(['3', '2', '1'])
})
})
})
108 changes: 108 additions & 0 deletions src/DataTable/__tests__/sorting.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {alphanumeric, basic, datetime} from '../sorting'

const Second = 1000
const Minute = 60 * Second
const Hour = 60 * Minute
const Day = 24 * Hour
const today = Date.now()
const yesterday = today - Day

describe('sorting', () => {
describe('alphanumeric', () => {
test.each([
[
'characters only',
{
input: ['c', 'b', 'a'],
sorted: ['a', 'b', 'c'],
},
],
[
'numbers only',
{
input: ['3', '2', '1'],
sorted: ['1', '2', '3'],
},
],
[
'text with numbers',
{
input: ['test456', 'test789', 'test123'],
sorted: ['test123', 'test456', 'test789'],
},
],
[
'text and numbers',
{
input: ['test-c', '1', 'test-b', '2', 'test-a', '3'],
sorted: ['1', '2', '3', 'test-a', 'test-b', 'test-c'],
},
],
[
'text with same base',
{
input: ['test', 'test-123', 'test-123-test-456'],
sorted: ['test', 'test-123', 'test-123-test-456'],
},
],
[
'text case sensitive',
{
input: ['test456', 'Test456', 'test123'],
sorted: ['test123', 'test456', 'Test456'],
},
],
])('%s', (_name, options) => {
expect(options.input.sort(alphanumeric)).toEqual(options.sorted)
})
})

describe('basic', () => {
test.each([
[
'text',
{
input: ['c', 'b', 'a'],
sorted: ['a', 'b', 'c'],
},
],
[
'numbers',
{
input: [3, 2, 1],
sorted: [1, 2, 3],
},
],
])('%s', (_name, options) => {
expect(options.input.sort(basic)).toEqual(options.sorted)
})
})

describe('datetime', () => {
test.each([
[
'only Date objects',
{
input: [new Date(today), new Date(yesterday)],
sorted: [new Date(yesterday), new Date(today)],
},
],
[
'Date and Date.now()',
{
input: [new Date(today), yesterday],
sorted: [yesterday, new Date(today)],
},
],
[
'only Date.now()',
{
input: [today, yesterday],
sorted: [yesterday, today],
},
],
])('%s', (_name, options) => {
expect(options.input.sort(datetime)).toEqual(options.sorted)
})
})
})
6 changes: 3 additions & 3 deletions src/DataTable/column.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ObjectPaths} from './utils'
import {UniqueRow} from './row'
import {SortStrategies} from './sorting'
import {SortStrategy, CustomSortStrategy} from './sorting'

export interface Column<Data extends UniqueRow> {
id?: string
Expand All @@ -14,7 +14,7 @@ export interface Column<Data extends UniqueRow> {
/**
* Optionally provide a field to render for this column. This may be the key
* of the object or a string that accesses nested objects through `.`. For
* exmaple: `field: a.b.c`
* example: `field: a.b.c`
*
* Alternatively, you may provide a `renderCell` for this column to render the
* field in a row
Expand All @@ -37,7 +37,7 @@ export interface Column<Data extends UniqueRow> {
* Specify if the table should sort by this column and, if applicable, a
* specific sort strategy or custom sort strategy
*/
sortBy?: boolean | SortStrategies
sortBy?: boolean | SortStrategy | CustomSortStrategy<Data>
}

export function createColumnHelper<T extends UniqueRow>() {
Expand Down
Loading