Skip to content

Commit

Permalink
feat: Show sorting in <Table> (#2113)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebald committed May 30, 2022
1 parent 5659da1 commit 5a32c4b
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changeset/late-glasses-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@marigold/components": patch
---

feat: Show sorting in `<Table>`
116 changes: 116 additions & 0 deletions docs/content/components/table.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,122 @@ import { Table } from '@marigold/components';
</Table>
```

### Sorting

```tsx
() => {
const data = [
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
birth_year: '19BBY',
},
{
name: 'C-3PO',
height: '167',
mass: '75',
birth_year: '112BBY',
},
{
name: 'R2-D2',
height: '96',
mass: '32',
birth_year: '33BBY',
},
{
name: 'Darth Vader',
height: '202',
mass: '136',
birth_year: '41.9BBY',
},
{
name: 'Leia Organa',
height: '150',
mass: '49',
birth_year: '19BBY',
},
{
name: 'Owen Lars',
height: '178',
mass: '120',
birth_year: '52BBY',
},
{
name: 'Beru Whitesun lars',
height: '165',
mass: '75',
birth_year: '47BBY',
},
{
name: 'R5-D4',
height: '97',
mass: '32',
birth_year: 'unknown',
},
{
name: 'Biggs Darklighter',
height: '183',
mass: '84',
birth_year: '24BBY',
},
{
name: 'Obi-Wan Kenobi',
height: '182',
mass: '77',
birth_year: '57BBY',
},
];

const [list, setList] = React.useState(data);
const [descriptor, setDescriptor] = React.useState({});
const sort = ({ column, direction }) => {
const result = list.sort((a, b) => {
const first = a[column];
const second = b[column];
let cmp =
(parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (direction === 'descending') {
cmp *= -1;
}
return cmp;
});
setDescriptor({ column, direction });
setList(result);
};

return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor={descriptor}
onSortChange={sort}
>
<Table.Header>
<Table.Column key="name" allowsSorting>
Name
</Table.Column>
<Table.Column key="height" allowsSorting>
Height
</Table.Column>
<Table.Column key="mass" allowsSorting>
Mass
</Table.Column>
<Table.Column key="birth_year" allowsSorting>
Birth Year
</Table.Column>
</Table.Header>
<Table.Body items={list}>
{item => (
<Table.Row key={item.name}>
{columnKey => <Table.Cell>{item[columnKey]}</Table.Cell>}
</Table.Row>
)}
</Table.Body>
</Table>
);
};
```

### Nested Columns

Columns can be nested, which will result in more than one header row to be created. Note the usage of `isRowHeader` in the example below. By default, only the first column is included in the accessibility name for each row.
Expand Down
122 changes: 121 additions & 1 deletion packages/components/src/Table/Table.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import React, { useState } from 'react';
import type { Meta, ComponentStory } from '@storybook/react';
import { SortDescriptor } from '@react-types/shared';

import { Stack } from '../Stack';
import { Table } from './Table';

Expand Down Expand Up @@ -166,3 +168,121 @@ export const NestedColumns: ComponentStory<typeof Table> = () => (
</Table.Body>
</Table>
);

const data = [
{
name: 'Luke Skywalker',
height: '172',
mass: '77',
birth_year: '19BBY',
},
{
name: 'C-3PO',
height: '167',
mass: '75',
birth_year: '112BBY',
},
{
name: 'R2-D2',
height: '96',
mass: '32',
birth_year: '33BBY',
},
{
name: 'Darth Vader',
height: '202',
mass: '136',
birth_year: '41.9BBY',
},
{
name: 'Leia Organa',
height: '150',
mass: '49',
birth_year: '19BBY',
},
{
name: 'Owen Lars',
height: '178',
mass: '120',
birth_year: '52BBY',
},
{
name: 'Beru Whitesun lars',
height: '165',
mass: '75',
birth_year: '47BBY',
},
{
name: 'R5-D4',
height: '97',
mass: '32',
birth_year: 'unknown',
},
{
name: 'Biggs Darklighter',
height: '183',
mass: '84',
birth_year: '24BBY',
},
{
name: 'Obi-Wan Kenobi',
height: '182',
mass: '77',
birth_year: '57BBY',
},
];

export const Sorting: ComponentStory<typeof Table> = () => {
const [list, setList] = useState(data);
const [descriptor, setDescriptor] = useState<SortDescriptor>({});
const sort = ({ column, direction }: SortDescriptor) => {
const result = list.sort((a: any, b: any) => {
const first = a[column!];
const second = b[column!];
let cmp =
(parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (direction === 'descending') {
cmp *= -1;
}
return cmp;
});
setDescriptor({ column, direction });
setList(result);
};

return (
<>
<Table
aria-label="Example table with client side sorting"
sortDescriptor={descriptor}
onSortChange={sort}
>
<Table.Header>
<Table.Column key="name" allowsSorting>
Name
</Table.Column>
<Table.Column key="height" allowsSorting>
Height
</Table.Column>
<Table.Column key="mass" allowsSorting>
Mass
</Table.Column>
<Table.Column key="birth_year" allowsSorting>
Birth Year
</Table.Column>
</Table.Header>
<Table.Body items={list}>
{item => (
<Table.Row key={item.name}>
{columnKey => <Table.Cell>{(item as any)[columnKey]}</Table.Cell>}
</Table.Row>
)}
</Table.Body>
</Table>
<br />
<pre>
Sort: {descriptor.column} / {descriptor.direction}
</pre>
</>
);
};
82 changes: 81 additions & 1 deletion packages/components/src/Table/Table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import React, { useState } from 'react';
import { SortDescriptor } from '@react-types/shared';
import { fireEvent, render, screen } from '@testing-library/react';
import { ThemeProvider } from '@marigold/system';

Expand Down Expand Up @@ -192,3 +193,82 @@ test('supports colspans', () => {
const informationHeader = screen.getByText('Information');
expect(informationHeader).toHaveAttribute('colspan', '2');
});

test('sorting', () => {
const data = [
{
name: 'Apple',
amount: 32,
},
{ name: 'Orange', amount: 11 },
{ name: 'Banana', amount: 24 },
];

const SortingTable = () => {
const [list, setList] = useState(data);
const [descriptor, setDescriptor] = useState<SortDescriptor>({});
const sort = ({ column, direction }: SortDescriptor) => {
const result = list.sort((a: any, b: any) => {
const first = a[column!];
const second = b[column!];
let cmp =
(parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (direction === 'descending') {
cmp *= -1;
}
return cmp;
});
setDescriptor({ column, direction });
setList(result);
};

return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor={descriptor}
onSortChange={sort}
>
<Table.Header>
<Table.Column key="name" allowsSorting>
Name
</Table.Column>
<Table.Column key="amount" allowsSorting>
Amount
</Table.Column>
</Table.Header>
<Table.Body items={list}>
{item => (
<Table.Row key={item.name}>
{columnKey => <Table.Cell>{(item as any)[columnKey]}</Table.Cell>}
</Table.Row>
)}
</Table.Body>
</Table>
);
};

render(<SortingTable />);

const rows = screen.getAllByRole('row');

// Unsorted
expect(rows[1].textContent).toContain('Apple');
expect(rows[2].textContent).toContain('Orange');
expect(rows[3].textContent).toContain('Banana');

// Sort by name
// eslint-disable-next-line testing-library/no-node-access
fireEvent.click(rows[0].firstChild!);
// eslint-disable-next-line testing-library/no-node-access
fireEvent.click(rows[0].firstChild!);

// eslint-disable-next-line testing-library/no-node-access
const header = rows[0].querySelector('[aria-sort]');
expect(header).toBeInTheDocument();
expect(header?.textContent).toContain('Name');

const sortedRows = screen.getAllByRole('row');
expect(sortedRows[1].textContent).toContain('Orange');
expect(sortedRows[2].textContent).toContain('Banana');
expect(sortedRows[3].textContent).toContain('Apple');
});

0 comments on commit 5a32c4b

Please sign in to comment.