Skip to content

Commit

Permalink
Merge pull request #225 from MetroStar/table-sorting-enhancements
Browse files Browse the repository at this point in the history
Table Sorting Enhancements
  • Loading branch information
jbouder authored Aug 6, 2024
2 parents 635ed4d + a7905f0 commit 85c0a25
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const StepIndicator = ({
}

return (
<li {...attributes} key={`usa-step-label-${id}`}>
<li {...attributes} key={`usa-step-label-${stepIndex}`}>
<span className="usa-step-indicator__segment-label">
{step}
{segmentSrLabel !== '' && <span className="usa-sr-only">{segmentSrLabel}</span>}
Expand Down
18 changes: 18 additions & 0 deletions packages/comet-uswds/src/components/table/table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Template: StoryFn<typeof Table> = (args: TableProps) => {
{
id: 'estimatedPopulation',
name: 'Estimated population at time of admission',
sortable: false,
},
];

Expand Down Expand Up @@ -84,6 +85,7 @@ const Template: StoryFn<typeof Table> = (args: TableProps) => {
sortable={args.sortable}
sortDir={args.sortDir}
sortIndex={args.sortIndex}
onSort={args.onSort}
/>
);
};
Expand All @@ -100,3 +102,19 @@ Default.args = {
sortDir: 'ascending',
sortIndex: 0,
};

export const OnSort = Template.bind({});
OnSort.args = {
id: 'table-1',
tabIndex: 1,
caption: 'Voter Data',
borderless: false,
striped: false,
scrollable: false,
sortable: true,
sortDir: 'ascending',
sortIndex: 0,
onSort: () => {
console.log('Sorting...');
},
};
30 changes: 29 additions & 1 deletion packages/comet-uswds/src/components/table/table.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fireEvent, render } from '@testing-library/react';
import { act, fireEvent, render } from '@testing-library/react';
import { axe } from 'jest-axe';
import Table, { TableColumn } from './table';

Expand Down Expand Up @@ -142,6 +142,19 @@ describe('Table', () => {
expect(th[0].getAttribute('aria-sort')).toBeNull();
});

test('should render a custom sortable table successfully', () => {
const customColumns = columns.map((column, index) => ({
...column,
sortable: index === 0 ? false : true,
}));

const { baseElement } = render(
<Table id="table1" columns={customColumns} data={sortableData} sortable={false} />,
);
const th = baseElement.querySelectorAll('th');
expect(th[0].getAttribute('aria-sort')).toBeNull();
});

test('should render a descending sortable table successfully', () => {
const { baseElement } = render(
<Table
Expand All @@ -157,6 +170,21 @@ describe('Table', () => {
expect(th[0].getAttribute('aria-sort')).not.toBeNull();
});

test('should call onSort function when sorting', async () => {
const onSort = vi.fn();
const { baseElement } = render(
<Table id="table1" columns={columns} data={sortableData} sortable={true} onSort={onSort} />,
);
expect(onSort).not.toHaveBeenCalled();

const th = baseElement.querySelectorAll('th')[0];
const thButton = th?.querySelector('button') as Element;
await act(async () => {
fireEvent.click(thButton);
});
expect(onSort).toHaveBeenCalled();
});

describe('sorted table', () => {
test('should match data before sorting', () => {
const { baseElement } = render(
Expand Down
57 changes: 45 additions & 12 deletions packages/comet-uswds/src/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export interface TableProps<T = any> {
* The default sort direction if sortIndex is provided
*/
sortDir?: 'ascending' | 'descending';
/**
* A function to call when the table is sorted
*/
onSort?: () => void;
/**
* A boolean indicating if the table is scrollable or not
*/
Expand All @@ -56,6 +60,7 @@ export interface TableProps<T = any> {
export interface TableColumn {
id: string;
name: string;
sortable?: boolean;
}

export interface TableCell {
Expand All @@ -74,6 +79,7 @@ export const Table = ({
sortable = false,
sortIndex = 0,
sortDir = 'ascending',
onSort,
scrollable = false,
borderless = false,
striped = false,
Expand All @@ -99,6 +105,26 @@ export const Table = ({
};
});

// If onSort, add a MutationObserver to listen for changes in aria-sort
useEffect(() => {
const callback = (mutationsList: MutationRecord[]) => {
for (const mutation of mutationsList) {
if (onSort && mutation.attributeName === 'aria-sort') {
const currentSortValue = (mutation.target as HTMLElement).getAttribute('aria-sort');
if (currentSortValue) {
onSort();
}
}
}
};

const observer = new MutationObserver(callback);
if (tableRef.current && onSort) {
const tableHeaders = tableRef.current.querySelectorAll('th');
tableHeaders.forEach((th) => observer.observe(th, { attributes: true }));
}
}, [columns]);

return (
<div
id={id}
Expand All @@ -114,18 +140,25 @@ export const Table = ({
<caption hidden={!!caption}>{caption}</caption>
<thead>
<tr>
{columns.map((column: TableColumn, index: number) => (
<th
id={column.id}
key={column.id}
data-sortable={sortable || null}
scope="col"
role="columnheader"
aria-sort={sortable && sortIndex === index ? sortDir : undefined}
>
{column.name}
</th>
))}
{columns
.map((obj) => ({
...obj,
sortable: obj.sortable !== undefined ? obj.sortable : true,
}))
.map((column: TableColumn, index: number) => (
<th
id={column.id}
key={column.id}
data-sortable={(sortable && column.sortable) || null}
scope="col"
role="columnheader"
aria-sort={
sortable && column.sortable && sortIndex === index ? sortDir : undefined
}
>
{column.name}
</th>
))}
</tr>
</thead>
<tbody>
Expand Down

0 comments on commit 85c0a25

Please sign in to comment.