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

feat: Generic table pagination #79

Merged
merged 5 commits into from
Mar 2, 2023
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
62 changes: 62 additions & 0 deletions apps/common/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ReactPaginate from 'react-paginate';
import IconPaginationArrow from '@common/icons/IconPaginationArrow';

import type {ReactElement} from 'react';

type TProps = {
range: [from: number, to: number];
pageCount: number;
numberOfItems: number;
onPageChange: (selectedItem: {
selected: number;
}) => void;
}

export function Pagination(props: TProps): ReactElement {
const {range: [from, to], pageCount, numberOfItems, onPageChange} = props;

return (
<>
<div className={'flex flex-1 justify-between sm:hidden'}>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Previous'}
</a>
<a
href={'#'}
className={'border-gray-300 text-gray-700 hover:bg-gray-50 relative ml-3 inline-flex items-center rounded-md border px-4 py-2 text-sm font-medium'}
>
{'Next'}
</a>
</div>
<div className={'sm-border hidden sm:flex sm:items-center sm:justify-center'}>
<div className={'ml-3 flex-1'}>
<p className={'text-sm text-[#5B5B5B]'}>
{'Showing '}<span className={'font-medium'}>{from}</span>{' to '}<span className={'font-medium'}>{to}</span>{' of'}{' '}
<span className={'font-medium'}>{numberOfItems}</span> {'results'}
</p>
</div>
<ReactPaginate
className={'inline-flex align-middle'}
pageLinkClassName={'text-[#5B5B5B] hover:border-b-2 inline-flex items-end mx-1.5 mt-2.5 px-0.5 text-xs'}
previousLinkClassName={'inline-flex items-center m-2 font-medium'}
nextLinkClassName={'inline-flex items-center m-2 font-medium'}
breakLinkClassName={'text-[#5B5B5B] inline-flex items-center mx-2 my-2 px-0.5 font-medium'}
activeLinkClassName={'text-gray-900 font-bold border-b-2 items-center mx-2 my-2 px-0.5 md:inline-flex'}
disabledLinkClassName={'cursor-not-allowed hover:bg-neutral-100'}
disabledClassName={'text-neutral-300'}
renderOnZeroPageCount={(): null => null}
breakLabel={'...'}
onPageChange={onPageChange}
pageRangeDisplayed={3}
pageCount={pageCount}
previousLabel={<IconPaginationArrow className={'h-5 w-5 transition-transform'} />}
nextLabel={<IconPaginationArrow className={'h-5 w-5 -rotate-180 transition-transform'} />}
/>
<div className={'sm:flex-1'}></div>
</div>
</>
);
}
24 changes: 17 additions & 7 deletions apps/common/components/Table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import {useCallback, useMemo, useState} from 'react';
import {sort} from '@veYFI/utils';
import IconChevronPlain from '@common/icons/IconChevronPlain';

import {Pagination} from './Pagination';
import {usePagination} from './usePagination';

import type {ReactElement} from 'react';

type TSortOrder = 'asc' | 'desc';
Expand All @@ -23,7 +26,7 @@ type TMetadata<T> = {
format?: (item: T) => string;
transform?: (item: T) => ReactElement;
}

type TTableProps<T> = {
metadata: TMetadata<T>[];
data: T[];
Expand All @@ -33,17 +36,19 @@ type TTableProps<T> = {
}

function Table<T>({metadata, data, columns, initialSortBy, onRowClick}: TTableProps<T>): ReactElement {
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});
const [{sortedBy, order}, set_state] = useState<TState<T>>({sortedBy: initialSortBy, order: 'desc'});

const sortedData = useMemo((): T[] => {
return sortedBy && order ? sort(data, sortedBy, order) : data;
}, [data, order, sortedBy]);

const {currentItems, paginationProps} = usePagination<T>({data: sortedData, itemsPerPage: 10});

const handleSort = useCallback((key: Extract<keyof T, string>): void => {
const willChangeSortKey = sortedBy !== key;
const newOrder = switchOrder(willChangeSortKey ? 'asc' : order);
set_state({sortedBy: newOrder ? key : undefined, order: newOrder});
}, [order, sortedBy]);

const sortedData = useMemo((): T[] => {
return sortedBy && order ? sort(data, sortedBy, order) : data;
}, [data, order, sortedBy]);

const numberOfColumns = Math.min(columns ?? (metadata.length), 12).toString();

Expand All @@ -66,7 +71,7 @@ function Table<T>({metadata, data, columns, initialSortBy, onRowClick}: TTablePr
))}
</div>

{sortedData.map((item, rowIndex): ReactElement => {
{currentItems.map((item, rowIndex): ReactElement => {
return (
<div
key={`row_${rowIndex}`}
Expand All @@ -88,6 +93,11 @@ function Table<T>({metadata, data, columns, initialSortBy, onRowClick}: TTablePr
</div>
);
})}
<div className={'mt-4'}>
<div className={'border-t border-neutral-300 p-4 pb-0'}>
<Pagination {...paginationProps} />
</div>
</div>
</div>

);
Expand Down
40 changes: 40 additions & 0 deletions apps/common/components/usePagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {useState} from 'react';

type TProps<T> = {
data: T[];
itemsPerPage: number;
}

type TUsePaginationReturn<T> = {
currentItems: T[];
paginationProps: {
range: [from: number, to: number];
pageCount: number;
numberOfItems: number;
onPageChange: (selectedItem: {
selected: number;
}) => void;
}
}

export function usePagination<T>({data, itemsPerPage}: TProps<T>): TUsePaginationReturn<T> {
const [itemOffset, set_itemOffset] = useState(0);

const endOffset = itemOffset + itemsPerPage;

const currentItems = data.slice(itemOffset, endOffset);

const handlePageChange = ({selected}: {selected: number}): void => {
set_itemOffset((selected * itemsPerPage) % data.length);
};

return {
currentItems,
paginationProps: {
range: [endOffset - (itemsPerPage - 1), Math.min(endOffset, data.length)],
pageCount: Math.ceil(data.length / itemsPerPage),
numberOfItems: data.length,
onPageChange: handlePageChange
}
};
}
27 changes: 27 additions & 0 deletions apps/common/icons/IconPaginationArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

import type {ReactElement} from 'react';

function IconPaginationArrow(props: React.SVGProps<SVGSVGElement>): ReactElement {
return (
<svg
{...props}
width={'10'}
height={'16'}
viewBox={'0 0 10 16'}
fill={'none'}
xmlns={'http://www.w3.org/2000/svg'}>
<svg
width={'10'}
height={'16'}
viewBox={'0 0 10 16'}
fill={'none'}
xmlns={'http://www.w3.org/2000/svg'}>
<path d={'M8.33716 15.7511L0.340576 8.75503C0.123779 8.5646 -0.000732784 8.29019 -0.000732772 8.0021C-0.000732766 7.86343 0.0270993 7.73062 0.0793454 7.60757C0.137451 7.47085 0.226318 7.34683 0.340576 7.24917L8.34058 0.249175C8.44897 0.154448 8.57202 0.0870652 8.70142 0.0470261C8.73218 0.0382371 8.76343 0.029448 8.79517 0.0226121C8.83647 0.0157997 8.87987 0.00210427 8.92163 0.00210427C8.96069 -0.000825414 9.00024 0.00112771 9.03979 0.00308084C9.17017 0.00796366 9.29419 0.0382371 9.40747 0.0890184C9.52808 0.142729 9.64038 0.221831 9.73511 0.325346C9.76831 0.360503 9.79858 0.398589 9.82593 0.439604C9.86694 0.499175 9.90015 0.562651 9.92651 0.627104C9.97339 0.743315 9.99927 0.869292 9.99927 1.0021L9.99927 14.9992C9.99927 15.132 9.97339 15.258 9.92651 15.3742C9.91382 15.4054 9.89966 15.4357 9.88354 15.466C9.86645 15.4982 9.84741 15.5304 9.82593 15.5617C9.79858 15.6027 9.76831 15.6408 9.73511 15.6759C9.64038 15.7794 9.52808 15.8586 9.40747 15.9123C9.29419 15.963 9.17017 15.9933 9.03979 15.9982C8.97354 16.0015 8.90792 15.9974 8.84253 15.9865C8.79419 15.9796 8.74731 15.9679 8.70142 15.9543C8.64233 15.9357 8.58423 15.9113 8.52856 15.882C8.46265 15.8468 8.39917 15.8039 8.33716 15.7511Z'} fill={'currentColor'}/>
</svg>

</svg>
);
}

export default IconPaginationArrow;