Skip to content
This repository was archived by the owner on Aug 31, 2022. It is now read-only.

Commit a70ef06

Browse files
authored
Merge pull request #107 from Synthetixio/feat/table
feat: add table component
2 parents db03d17 + 3ef3866 commit a70ef06

8 files changed

Lines changed: 416 additions & 37 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"typescript.tsdk": "node_modules/typescript/lib"
3+
}

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"@synthetixio/transaction-notifier": "^2.70.1",
2727
"clsx": "^1.1.1",
2828
"lodash": "^4.17.21",
29+
"react-table": "^7.8.0",
2930
"react-transition-group": "^4.4.2"
3031
},
3132
"devDependencies": {
@@ -52,6 +53,7 @@
5253
"@types/node": "^16.11.13",
5354
"@types/react": "^18.0.0",
5455
"@types/react-dom": "^18.0.0",
56+
"@types/react-table": "^7.7.12",
5557
"@types/react-transition-group": "^4.4.4",
5658
"@typescript-eslint/eslint-plugin": "^5.7.0",
5759
"@typescript-eslint/parser": "^5.7.0",
@@ -71,8 +73,8 @@
7173
"lint-staged": "^12.1.2",
7274
"postcss": "^8.4.5",
7375
"prettier": "^2.5.1",
74-
"react": "^18.0.0",
75-
"react-dom": "^18.0.0",
76+
"react": "^18.2.0",
77+
"react-dom": "^18.2.0",
7678
"react-scripts": "5.0.0",
7779
"rollup": "^2.61.1",
7880
"rollup-plugin-copy": "^3.4.0",
@@ -83,7 +85,7 @@
8385
"semantic-release": "19.0.2",
8486
"storybook-dark-mode": "^1.1.0",
8587
"tailwindcss": "^3.0.3",
86-
"typescript": "^4.5.4",
88+
"typescript": "^4.7.4",
8789
"web-vitals": "^2.1.2",
8890
"webpack": "5"
8991
},

src/components/Pagination/Pagination.tsx

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const Pagination: React.FC<PaginationProps> = ({
2222
localization = {
2323
of: 'of'
2424
},
25-
className
25+
className = 'ui-max-w-[350px]'
2626
}) => {
2727
const pageCount = Math.ceil(length / pageSize);
2828

@@ -35,25 +35,27 @@ export const Pagination: React.FC<PaginationProps> = ({
3535
return (
3636
<div
3737
className={clsx(
38-
'ui-flex ui-text-white ui-justify-around ui-items-center ui-gap-5 ui-max-w-[350px]',
38+
'ui-flex ui-text-xl ui-text-gray-650 ui-justify-between ui-items-center ui-gap-5',
3939
className
4040
)}
4141
>
42-
<Icon
43-
className={clsx({
44-
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': pageIndex !== 0
45-
})}
46-
name='Left-3'
47-
onClick={() => gotoPage(0)}
48-
/>
42+
<div>
43+
<Icon
44+
className={clsx('ui-mr-3', {
45+
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': pageIndex !== 0
46+
})}
47+
name='Left-3'
48+
onClick={() => gotoPage(0)}
49+
/>
4950

50-
<Icon
51-
className={clsx({
52-
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': pageIndex !== 0
53-
})}
54-
name='Left-4'
55-
onClick={() => gotoPage(Math.max(pageIndex - 1, 0))}
56-
/>
51+
<Icon
52+
className={clsx({
53+
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': pageIndex !== 0
54+
})}
55+
name='Left-4'
56+
onClick={() => gotoPage(Math.max(pageIndex - 1, 0))}
57+
/>
58+
</div>
5759

5860
<h6 className='ui-tg-caption ui-text-gray-500 ui-select-none'>
5961
{startIndex + 1}-{endIndex}
@@ -62,21 +64,23 @@ export const Pagination: React.FC<PaginationProps> = ({
6264
{length}
6365
</h6>
6466

65-
<Icon
66-
className={clsx({
67-
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': canNextPage
68-
})}
69-
name='Right-4'
70-
onClick={() => canNextPage && gotoPage(pageIndex + 1)}
71-
/>
67+
<div>
68+
<Icon
69+
className={clsx({
70+
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': canNextPage
71+
})}
72+
name='Right-4'
73+
onClick={() => canNextPage && gotoPage(pageIndex + 1)}
74+
/>
7275

73-
<Icon
74-
className={clsx({
75-
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': canNextPage
76-
})}
77-
name='Right-3'
78-
onClick={() => gotoPage(pageCount - 1)}
79-
/>
76+
<Icon
77+
className={clsx({
78+
'ui-cursor-pointer ui-text-primary hover:ui-text-blue-light-2': canNextPage
79+
})}
80+
name='Right-3'
81+
onClick={() => gotoPage(pageCount - 1)}
82+
/>
83+
</div>
8084
</div>
8185
);
8286
};
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { ComponentMeta, ComponentStory } from '@storybook/react';
2+
import { Card } from 'components/Card/Card';
3+
import { Column } from 'react-table';
4+
5+
import { Table } from './Table';
6+
7+
export default {
8+
title: 'Table',
9+
component: Table,
10+
decorators: [(Story) => <Story />]
11+
} as ComponentMeta<typeof Table>;
12+
13+
const Template: ComponentStory<typeof Table> = (args) => (
14+
<Card variant='dark-blue'>
15+
<Table {...args} />
16+
</Card>
17+
);
18+
19+
export const Primary = Template.bind({});
20+
21+
type Crypto = {
22+
coin: string;
23+
symbol: string;
24+
price: number;
25+
percent1H: number;
26+
percent24H: number;
27+
percent7D: number;
28+
last7Days: string;
29+
};
30+
31+
const data: Crypto[] = [
32+
{
33+
coin: 'Synthetix',
34+
symbol: 'sUSD',
35+
price: 11,
36+
percent1H: -1.3,
37+
percent24H: -3.5,
38+
percent7D: -8.6,
39+
last7Days: 'https://picsum.photos/50'
40+
},
41+
{
42+
coin: 'Synthetix',
43+
symbol: 'SNX',
44+
price: 2.71,
45+
percent1H: -5.3,
46+
percent24H: 9.5,
47+
percent7D: 2.6,
48+
last7Days: 'https://picsum.photos/50'
49+
}
50+
];
51+
52+
const columns: Column<Crypto>[] = [
53+
{
54+
disableSortBy: true,
55+
Header: 'Coin',
56+
accessor: 'symbol',
57+
columnClass: 'ui-text-left',
58+
cellClass: 'ui-text-left',
59+
Cell: ({ row }) => (
60+
<div className='ui-flex ui-items-center'>
61+
<img
62+
alt=''
63+
className='ui-w-6 ui-h-6 ui-rounded-full ui-mr-3'
64+
src='https://picsum.photos/50'
65+
/>
66+
<span className='ui-tg-caption-bold lg:ui-tg-content-bold'>{row.original.symbol}</span>
67+
<span className='ui-hidden ui-tg-content ui-ml-3 lg:ui-block'>{row.original.coin}</span>
68+
</div>
69+
)
70+
},
71+
{
72+
Header: 'Price',
73+
accessor: (row) => row.price,
74+
sortType: (a, b) => a.original.price - b.original.price
75+
},
76+
{
77+
Header: '1h',
78+
accessor: 'percent1H',
79+
Cell: (row) => row.value,
80+
sortType: (a, b) => a.original.percent1H - b.original.percent1H
81+
},
82+
{
83+
Header: '7h',
84+
accessor: 'percent7D',
85+
Cell: (row) => row.value,
86+
sortType: (a, b) => a.original.percent7D - b.original.percent7D
87+
}
88+
];
89+
90+
Primary.args = {
91+
className: '',
92+
data: [...data, ...data],
93+
columns: columns,
94+
initialState: { pageSize: 3 }
95+
};

src/components/Table/Table.tsx

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/* eslint-disable react/jsx-key */
2+
import clsx from 'clsx';
3+
import { Icon } from 'components/Icon/Icon';
4+
import { Pagination, PaginationLocalization } from 'components/Pagination/Pagination';
5+
import React, { ReactElement, ReactNode, useMemo } from 'react';
6+
import {
7+
Row,
8+
TableHeaderProps,
9+
TableOptions,
10+
useFlexLayout,
11+
usePagination,
12+
useSortBy,
13+
useTable
14+
} from 'react-table';
15+
16+
export interface TableProps<T extends Record<string, unknown>> extends TableOptions<T> {
17+
className?: string;
18+
paginationLocalization?: PaginationLocalization;
19+
onClick?: (row: Row<T>) => void;
20+
}
21+
22+
export const Table = <T extends Record<string, unknown>>(props: TableProps<T>): ReactElement => {
23+
const { className, paginationLocalization, onClick, ...rest } = props;
24+
25+
const defaultColumn = useMemo(
26+
() => ({
27+
width: 150,
28+
minWidth: 100
29+
}),
30+
[]
31+
);
32+
33+
const tableInstance = useTable(
34+
{ ...rest, defaultColumn },
35+
useSortBy,
36+
useFlexLayout,
37+
usePagination
38+
);
39+
40+
const {
41+
headerGroups,
42+
prepareRow,
43+
getTableProps,
44+
getTableBodyProps,
45+
page,
46+
state,
47+
gotoPage,
48+
rows
49+
} = tableInstance;
50+
51+
const { pageIndex, pageSize } = state;
52+
53+
return (
54+
<>
55+
<div className={clsx('ui-overflow-auto', className)}>
56+
<table {...getTableProps()} className='ui-w-full'>
57+
<thead>
58+
{headerGroups.map((headerGroup) => (
59+
<tr {...headerGroup.getHeaderGroupProps()}>
60+
{headerGroup.headers.map((column) => (
61+
<TableHeader
62+
className={clsx(
63+
'ui-text-gray-500 ui-select-none ui-p-2 ui-tg-caption-bold lg:ui-tg-content-bold ui-transition-colors',
64+
column.columnClass || 'ui-text-right',
65+
{
66+
'hover:ui-text-primary': column.canSort
67+
}
68+
)}
69+
{...column.getHeaderProps(column.getSortByToggleProps())}
70+
>
71+
<>
72+
{column.render('Header')}
73+
<span>
74+
{column.isSorted && (
75+
<Icon
76+
className='ui-text-xs ui-ml-1'
77+
name={column.isSortedDesc ? 'Top' : 'Bottom'}
78+
/>
79+
)}
80+
</span>
81+
</>
82+
</TableHeader>
83+
))}
84+
</tr>
85+
))}
86+
</thead>
87+
88+
<tbody {...getTableBodyProps()}>
89+
{page.map((row) => {
90+
prepareRow(row);
91+
return (
92+
<tr
93+
onClick={() => onClick?.(row)}
94+
{...row.getRowProps()}
95+
className={clsx(
96+
'ui-text-white ui-border-t last:ui-border-b ui-border-solid ui-border-gray-700',
97+
{
98+
'ui-cursor-pointer': !!onClick
99+
}
100+
)}
101+
>
102+
{row.cells.map((cell) => (
103+
<td
104+
className={clsx(
105+
'ui-p-2 ui-tg-caption lg:ui-tg-content',
106+
cell.column.cellClass || 'ui-text-right'
107+
)}
108+
{...cell.getCellProps()}
109+
>
110+
<span className='ui-inline-flex ui-items-center ui-h-full'>
111+
{cell.render('Cell') as ReactNode}
112+
</span>
113+
</td>
114+
))}
115+
</tr>
116+
);
117+
})}
118+
</tbody>
119+
</table>
120+
</div>
121+
<Pagination
122+
className='ui-mt-6 ui-w-full'
123+
gotoPage={gotoPage}
124+
length={rows.length}
125+
localization={paginationLocalization}
126+
pageIndex={pageIndex}
127+
pageSize={pageSize}
128+
/>
129+
</>
130+
);
131+
};
132+
133+
interface HeaderProps extends TableHeaderProps {
134+
children: React.ReactNode;
135+
}
136+
137+
const TableHeader: React.FC<HeaderProps> = ({ children, ...props }) => {
138+
return <th {...props}>{children}</th>;
139+
};

0 commit comments

Comments
 (0)