Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export useOrderedRows hook #1523

Merged
merged 1 commit into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 5 additions & 7 deletions src/components/data/DataTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useContext, useEffect, useMemo } from 'preact/hooks';
import { useArrowKeyNavigation } from '../../hooks/use-arrow-key-navigation';
import { useStableCallback } from '../../hooks/use-stable-callback';
import { useSyncedRef } from '../../hooks/use-synced-ref';
import type { CompositeProps } from '../../types';
import type { CompositeProps, Order } from '../../types';
import { downcastRef } from '../../util/typing';
import { ArrowDownIcon, ArrowUpIcon, SpinnerSpokesIcon } from '../icons';
import { Button } from '../input';
Expand All @@ -22,11 +22,6 @@ export type TableColumn<Field> = {
classes?: string;
};

export type Order<Field> = {
field: Field;
direction: 'ascending' | 'descending';
};

type ComponentProps<Row> = {
rows: Row[];
columns: TableColumn<keyof Row>[];
Expand Down Expand Up @@ -100,7 +95,10 @@ function defaultRenderItem<Row>(r: Row, field: keyof Row): ComponentChildren {
return r[field] as ComponentChildren;
}

function calculateNewOrder<T>(newField: T, prevOrder?: Order<T>): Order<T> {
function calculateNewOrder<T extends string | number | symbol>(
newField: T,
prevOrder?: Order<T>,
): Order<T> {
if (newField !== prevOrder?.field) {
return { field: newField, direction: 'ascending' };
}
Expand Down
129 changes: 129 additions & 0 deletions src/hooks/test/use-ordered-rows-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { mount } from 'enzyme';
import { useState } from 'preact/hooks';

import { useOrderedRows } from '../use-ordered-rows';

const starWarsCharacters = [
{ name: 'Luke Skywalker', age: 20 },
{ name: 'Princess Leia Organa', age: 20 },
{ name: 'Han Solo', age: 25 },
];

describe('useOrderedRows', () => {
function FakeComponent() {
const [order, setOrder] = useState();
const orderedRows = useOrderedRows(starWarsCharacters, order);

return (
<div>
{orderedRows.map((character, index) => (
<div key={`${character.name}${index}`}>
<span data-testid={`name-${index}`}>{character.name}</span>
<span data-testid={`age-${index}`}>{character.age}</span>
</div>
))}
<button
data-testid="button-order-by-name-asc"
onClick={() => setOrder({ field: 'name', direction: 'ascending' })}
>
By name ASC
</button>
<button
data-testid="button-order-by-name-desc"
onClick={() => setOrder({ field: 'name', direction: 'descending' })}
>
By name DES
</button>
<button
data-testid="button-order-by-age-asc"
onClick={() => setOrder({ field: 'age', direction: 'ascending' })}
>
By age ASC
</button>
<button
data-testid="button-order-by-age-desc"
onClick={() => setOrder({ field: 'age', direction: 'descending' })}
>
By age DES
</button>
<button
data-testid="button-reset-order"
onClick={() => setOrder(undefined)}
>
Reset order
</button>
</div>
);
}

function createComponent() {
return mount(<FakeComponent />);
}

function assertDefaultOrder(wrapper) {
assertOrder(wrapper, starWarsCharacters);
}

function assertOrder(wrapper, expectedRows) {
expectedRows.forEach((character, index) => {
assert.equal(
wrapper.find(`[data-testid="name-${index}"]`).text(),
character.name,
);
assert.equal(
wrapper.find(`[data-testid="age-${index}"]`).text(),
character.age,
);
});
}

[
{
orderId: 'button-order-by-name-asc',
expectedRows: [
{ name: 'Han Solo', age: 25 },
{ name: 'Luke Skywalker', age: 20 },
{ name: 'Princess Leia Organa', age: 20 },
],
},
{
orderId: 'button-order-by-name-desc',
expectedRows: [
{ name: 'Princess Leia Organa', age: 20 },
{ name: 'Luke Skywalker', age: 20 },
{ name: 'Han Solo', age: 25 },
],
},
{
orderId: 'button-order-by-age-asc',
expectedRows: [
{ name: 'Luke Skywalker', age: 20 },
{ name: 'Princess Leia Organa', age: 20 },
{ name: 'Han Solo', age: 25 },
],
},
{
orderId: 'button-order-by-age-desc',
expectedRows: [
{ name: 'Han Solo', age: 25 },
{ name: 'Luke Skywalker', age: 20 },
{ name: 'Princess Leia Organa', age: 20 },
],
},
].forEach(({ orderId, expectedRows }) => {
it('orders rows based on field and direction', () => {
const wrapper = createComponent();

// Rows are initially not ordered
assertDefaultOrder(wrapper);

// Click button to order
wrapper.find(`[data-testid="${orderId}"]`).simulate('click');
assertOrder(wrapper, expectedRows);

// Order can be reset
wrapper.find('[data-testid="button-reset-order"]').simulate('click');
assertDefaultOrder(wrapper);
});
});
});
30 changes: 30 additions & 0 deletions src/hooks/use-ordered-rows.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useMemo } from 'preact/hooks';

import type { Order } from '../types';

/**
* Orders a list of rows based on provided order options.
* Provided rows are not mutated, but a copy is returned instead.
*/
export function useOrderedRows<Row>(
rows: Row[],
order?: Order<keyof Row>,
): Row[] {
return useMemo(() => {
if (!order) {
return rows;
}

return [...rows].sort((a, b) => {
if (a[order.field] === b[order.field]) {
return 0;
}

if (order.direction === 'ascending') {
return a[order.field] > b[order.field] ? 1 : -1;
}

return a[order.field] > b[order.field] ? -1 : 1;
});
}, [order, rows]);
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export { useArrowKeyNavigation } from './hooks/use-arrow-key-navigation';
export { useClickAway } from './hooks/use-click-away';
export { useFocusAway } from './hooks/use-focus-away';
export { useKeyPress } from './hooks/use-key-press';
export { useOrderedRows } from './hooks/use-ordered-rows';
export { useStableCallback } from './hooks/use-stable-callback';
export { useSyncedRef } from './hooks/use-synced-ref';
export { useToastMessages } from './hooks/use-toast-messages';
Expand Down Expand Up @@ -74,6 +75,7 @@ export type {
BaseProps,
CompositeProps,
IconComponent,
Order,
PresentationalProps,
TransitionComponent,
} from './types';
Expand Down
30 changes: 4 additions & 26 deletions src/pattern-library/components/patterns/data/DataTablePage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useCallback, useMemo, useRef, useState } from 'preact/hooks';
import { useCallback, useRef, useState } from 'preact/hooks';

import { Button, DataTable, type DataTableProps, Scroll } from '../../../../';
import type { Order } from '../../../../components/data/DataTable';
import { Button, DataTable, Scroll } from '../../../../';
import type { DataTableProps, Order } from '../../../../';
import { useOrderedRows } from '../../../../hooks/use-ordered-rows';
import Library from '../../Library';
import { nabokovNovels } from '../samples';
import type { NabokovNovel } from '../samples';
Expand All @@ -15,29 +16,6 @@ const nabokovColumns = [

type SimpleNabokovNovel = Omit<NabokovNovel, 'translatedTitle'>;

function useOrderedRows(
rows: SimpleNabokovNovel[],
order?: Order<keyof SimpleNabokovNovel>,
) {
return useMemo(() => {
if (!order) {
return rows;
}

return [...rows].sort((a, b) => {
if (a[order.field] === b[order.field]) {
return 0;
}

if (order.direction === 'ascending') {
return a[order.field] > b[order.field] ? 1 : -1;
}

return a[order.field] > b[order.field] ? -1 : 1;
});
}, [order, rows]);
}

function ClientOrderableDataTable({
rows,
// By default, all columns are orderable
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ export type TransitionComponent = FunctionComponent<{
direction?: 'in' | 'out';
onTransitionEnd?: (direction: 'in' | 'out') => void;
}>;

export type Order<Field extends string | number | symbol> = {
field: Field;
direction: 'ascending' | 'descending';
};