Skip to content

Commit

Permalink
feat: QUV-758 Create column sorting and OrderIndicator
Browse files Browse the repository at this point in the history
- add orderindicator component
- add example order table
- add hook useOrder
  • Loading branch information
santiago.trigo committed Dec 28, 2023
1 parent bbb669b commit c940c60
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 1 deletion.
7 changes: 7 additions & 0 deletions packages/table/src/core/HeaderCell/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { ColDef } from '../../declarations';
import { StyledHeaderCell } from './StyledHeaderCell';
import { StyledHeaderCellResizer } from './StyledHeaderCellResizer';
import { TableContext } from '../../context/TableContext';
import { OrderIndicator } from './OrderIndicator';

interface HeaderCellProps {
colDef: ColDef;
width: React.CSSProperties['width'];
offsetX: number;
children: React.ReactNode;
resizable?: boolean;
onSort?: (colDef) => void;
}

export const HeaderCell: React.FC<HeaderCellProps> = ({
Expand All @@ -20,6 +22,7 @@ export const HeaderCell: React.FC<HeaderCellProps> = ({
offsetX,
children,
resizable = true,
onSort,
}) => {
const { density } = React.useContext(TableContext);
return (
Expand All @@ -32,8 +35,12 @@ export const HeaderCell: React.FC<HeaderCellProps> = ({
offsetX={offsetX}
density={density}
title={colDef.headerName}
onClick={() => {
onSort(colDef);
}}
>
{children}
<OrderIndicator colDef={colDef} />
{resizable && (
<StyledHeaderCellResizer
density={density}
Expand Down
18 changes: 18 additions & 0 deletions packages/table/src/core/HeaderCell/OrderIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { ColDef } from '../../declarations';
import { StyledSortIndicator } from './StyledSortIndicator';
import { StyledSortIndex } from './StyledSortIndex';
import { Flex } from '@devoinc/genesys-ui';

interface OrderIndicatorProps {
colDef: ColDef;
}

export const OrderIndicator: React.FC<OrderIndicatorProps> = ({ colDef }) => {
return (
<Flex flexDirection="row" gap="cmp-xs" justifyContent={'flex-end'}>
<StyledSortIndicator sort={colDef.sort} />
<StyledSortIndex>{colDef.sortIndex}</StyledSortIndex>
</Flex>
);
};
9 changes: 9 additions & 0 deletions packages/table/src/core/HeaderCell/StyledSortIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styled from 'styled-components';

export const StyledSortIndex = styled.span`
position: relative;
display: block;
font-family: 'gi', sans-serif;
font-size: 1rem;
line-height: 1.5;
`;
26 changes: 26 additions & 0 deletions packages/table/src/core/HeaderCell/StyledSortIndicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled, { css } from 'styled-components';
import icons from '@devoinc/genesys-icons/dist/icon-variables.js';

const iconsSort = {
asc: icons.sort_asc_carets,
desc: icons.sort_desc_carets,
};

interface StyledSortIndicatorProps {
sort: 'asc' | 'desc' | 'none';
}

export const StyledSortIndicator = styled.span<StyledSortIndicatorProps>`
${({ sort }) => {
return css`
&::before {
content: '${iconsSort[sort]}';
position: relative;
display: block;
font-family: 'gi', sans-serif;
font-size: 1rem;
line-height: 1.5;
}
`;
}}
`;
3 changes: 3 additions & 0 deletions packages/table/src/core/Table/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type TableProps = {
highlightColumnsOnHover?: boolean;
texts?: TextsType;
showFilters?: boolean;
onSort?: (columnDef) => void;
};

export const Table: React.FC<TableProps> = ({
Expand All @@ -40,6 +41,7 @@ export const Table: React.FC<TableProps> = ({
maxHeight = 'none',
minHeight,
showFilters,
onSort,
data,
highlightColumnsOnHover = true,
resizableColumns = false,
Expand Down Expand Up @@ -67,6 +69,7 @@ export const Table: React.FC<TableProps> = ({
density,
highlightColumnsOnHover,
resizableColumns,
onSort,
}}
>
<TableWrapper />
Expand Down
3 changes: 2 additions & 1 deletion packages/table/src/core/TableHead/TableHead.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const TableHead: React.FC<TableHeadProps> = ({
data,
width,
}) => {
const { showFilters, resizableColumns, density } =
const { showFilters, resizableColumns, density, onSort } =
React.useContext(TableContext);
const items = columnVirtualizer?.getVirtualItems() ?? [];
return (
Expand All @@ -40,6 +40,7 @@ export const TableHead: React.FC<TableHeadProps> = ({
width={`${virtualColumn.size}px`}
offsetX={virtualColumn.start}
resizable={colDef?.resizable ?? resizableColumns ?? false}
onSort={onSort}
>
<Typography.Heading size="h6" truncateLine={1}>
{colDef.headerName}
Expand Down
21 changes: 21 additions & 0 deletions packages/table/src/core/hooks/useOrderStruct.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { OrderColumn, useOrderStruct } from './useOrderStruct';
import { renderHook } from '@testing-library/react';

describe('getOptionsFromData', () => {
const cases: [string, OrderColumn[], string, OrderColumn[]][] = [
['Changed desc - none', [{ id: 'id', sort: 'desc' }], 'id', []],
[
'Changed asc - desc',
[{ id: 'id', sort: 'asc' }],
'id',
[{ id: 'id', sort: 'desc' }],
],
['sort none', [], 'id', [{ id: 'id', sort: 'asc' }]],
];

it.each(cases)('%s', (_title, initial, id, expected) => {
const { result } = renderHook(() => useOrderStruct(initial));
renderHook(() => result.current.onSort(id));
expect(result.current.orderStruct).toEqual(expected);
});
});
31 changes: 31 additions & 0 deletions packages/table/src/core/hooks/useOrderStruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';

export type OrderColumn = {
id: string;
sort: 'asc' | 'desc';
};

export const useOrderStruct = (initial: OrderColumn[] = []) => {
const [orderStruct, setOrderStruct] = React.useState<OrderColumn[]>(initial);

const onSort = (id: string) => {
let newOrderStruct = orderStruct.map((col) => ({ ...col }));

const col = orderStruct.find((col) => col.id === id);
if (col) {
if (col.sort === 'asc') {
newOrderStruct = newOrderStruct.map((col) =>
col.id === id ? { ...col, sort: 'desc' } : col,
);
} else {
newOrderStruct = newOrderStruct.filter((col) => col.id !== id);
}
} else {
newOrderStruct = newOrderStruct.concat({ id, sort: 'asc' });
}

setOrderStruct(newOrderStruct);
};

return { orderStruct, onSort };
};
4 changes: 4 additions & 0 deletions packages/table/src/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export type ColDef = {
[key: string]: unknown;
};

sortable?: boolean;
sort?: 'asc' | 'desc';
sortIndex?: React.ReactNode;

cellStyle?: ColumnCellStyleProps;
expandedRow?: boolean;
isDragging?: boolean;
Expand Down
106 changes: 106 additions & 0 deletions packages/table/src/helpers/orderDataByOrderStruct.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { OrderColumn } from '../core/hooks/useOrderStruct';
import { Data } from '../declarations';
import { orderDataByOrderStruct } from './orderDataByOrderStruct';

describe('getOptionsFromData', () => {
const cases: [string, Data, OrderColumn[], Data][] = [
[
'Order number desc',
[
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
],
[{ id: 'id', sort: 'desc' }],
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
],
[
'Order number asc',
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
[{ id: 'id', sort: 'asc' }],
[
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
],
],
[
'Order none',
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
[],
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
],
[
'Order multi number',
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 3, name: 'Jimmy Hogan', age: 50 },
],
[
{ id: 'id', sort: 'desc' },
{ id: 'age', sort: 'desc' },
],
[
{ id: 3, name: 'Jimmy Hogan', age: 50 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
],
[
'Order multi number-string',
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 3, name: 'Jimmy Hogan', age: 50 },
],
[
{ id: 'age', sort: 'desc' },
{ id: 'name', sort: 'asc' },
],
[
{ id: 3, name: 'Jimmy Hogan', age: 50 },
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
],
],
[
'Order stirng desc',
[
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
],
[{ id: 'name', sort: 'desc' }],
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
],
[
'Order string asc',
[
{ id: 2, name: 'Jimmy Hogan', age: 20 },
{ id: 1, name: 'Ina Osborne', age: 20 },
],
[{ id: 'name', sort: 'asc' }],
[
{ id: 1, name: 'Ina Osborne', age: 20 },
{ id: 2, name: 'Jimmy Hogan', age: 20 },
],
],
];

it.each(cases)('%s', (_title, data, orderStruct, expected) => {
expect(orderDataByOrderStruct(data, orderStruct)).toEqual(expected);
});
});
30 changes: 30 additions & 0 deletions packages/table/src/helpers/orderDataByOrderStruct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { OrderColumn } from '../core/hooks/useOrderStruct';

const order = (orderStruct, a, b) => {
for (const { sort, id } of orderStruct) {
if (typeof a[id] === 'number') {
if (sort === 'asc' && a[id] - b[id] !== 0) {
return a[id] - b[id];
}
if (sort === 'desc' && b[id] - a[id] !== 0) {
return b[id] - a[id];
}
}

if (typeof a[id] === 'string') {
if (sort === 'asc' && a[id].localeCompare(b[id]) !== 0) {
return a[id].localeCompare(b[id]);
}
if (sort === 'desc' && b[id].localeCompare(a[id]) !== 0) {
return b[id].localeCompare(a[id]);
}
}
}
};
export const orderDataByOrderStruct = (data, orderStruct: OrderColumn[]) => {
if (orderStruct.length > 0) {
const newData = data;
return newData.sort((a, b) => order(orderStruct, a, b));
}
return data;
};
Loading

0 comments on commit c940c60

Please sign in to comment.