@chumsinc/sortable-tables 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/babel.config.mjs +13 -0
- package/changelog-template.hbs +45 -0
- package/dist/DataTable.d.ts +6 -0
- package/dist/DataTable.js +15 -0
- package/dist/DataTable.js.map +1 -0
- package/dist/DataTableCell.d.ts +293 -0
- package/dist/DataTableCell.js +17 -0
- package/dist/DataTableCell.js.map +1 -0
- package/dist/DataTableHead.d.ts +6 -0
- package/dist/DataTableHead.js +11 -0
- package/dist/DataTableHead.js.map +1 -0
- package/dist/DataTableRow.d.ts +6 -0
- package/dist/DataTableRow.js +17 -0
- package/dist/DataTableRow.js.map +1 -0
- package/dist/DataTableTBody.d.ts +6 -0
- package/dist/DataTableTBody.js +16 -0
- package/dist/DataTableTBody.js.map +1 -0
- package/dist/DataTableTH.d.ts +6 -0
- package/dist/DataTableTH.js +9 -0
- package/dist/DataTableTH.js.map +1 -0
- package/dist/RowsPerPage.d.ts +7 -0
- package/dist/RowsPerPage.js +16 -0
- package/dist/RowsPerPage.js.map +1 -0
- package/dist/SortableTable.d.ts +6 -0
- package/dist/SortableTable.js +15 -0
- package/dist/SortableTable.js.map +1 -0
- package/dist/SortableTableHead.d.ts +6 -0
- package/dist/SortableTableHead.js +12 -0
- package/dist/SortableTableHead.js.map +1 -0
- package/dist/SortableTableTH.d.ts +6 -0
- package/dist/SortableTableTH.js +48 -0
- package/dist/SortableTableTH.js.map +1 -0
- package/dist/Table.d.ts +5 -0
- package/dist/Table.js +28 -0
- package/dist/Table.js.map +1 -0
- package/dist/TablePagination.d.ts +6 -0
- package/dist/TablePagination.js +13 -0
- package/dist/TablePagination.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +104 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +2 -0
- package/dist/utils.js.map +1 -0
- package/eslint.config.mjs +14 -0
- package/package.json +91 -0
- package/public/index.html +28 -0
- package/src/DataTable.tsx +47 -0
- package/src/DataTableCell.tsx +28 -0
- package/src/DataTableHead.tsx +27 -0
- package/src/DataTableRow.tsx +38 -0
- package/src/DataTableTBody.tsx +40 -0
- package/src/DataTableTH.tsx +20 -0
- package/src/RowsPerPage.tsx +38 -0
- package/src/SortableTable.tsx +46 -0
- package/src/SortableTableHead.tsx +31 -0
- package/src/SortableTableTH.tsx +77 -0
- package/src/Table.tsx +43 -0
- package/src/TablePagination.tsx +72 -0
- package/src/index.tsx +14 -0
- package/src/types.ts +127 -0
- package/src/utils.ts +1 -0
- package/test/TestTable.tsx +67 -0
- package/test/data.ts +232 -0
- package/test/index.tsx +11 -0
- package/tsconfig.json +29 -0
- package/webpack.common.mjs +72 -0
- package/webpack.dev.mjs +35 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import DataTableHead from "./DataTableHead";
|
|
4
|
+
import DataTableTBody from "./DataTableTBody";
|
|
5
|
+
import {DataTableProps} from "./types";
|
|
6
|
+
import {noop} from "./utils";
|
|
7
|
+
import Table from "./Table";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function DataTable<T = unknown>({
|
|
11
|
+
fields,
|
|
12
|
+
data,
|
|
13
|
+
keyField,
|
|
14
|
+
size = '',
|
|
15
|
+
sticky,
|
|
16
|
+
responsive,
|
|
17
|
+
rowClassName,
|
|
18
|
+
renderRow,
|
|
19
|
+
onSelectRow = noop,
|
|
20
|
+
selected = '',
|
|
21
|
+
className = '',
|
|
22
|
+
tfoot,
|
|
23
|
+
children,
|
|
24
|
+
tableHeadProps,
|
|
25
|
+
...rest
|
|
26
|
+
}: DataTableProps<T>) {
|
|
27
|
+
|
|
28
|
+
const tableClassName = classNames('table', className, {
|
|
29
|
+
[`table-${size}`]: !!size,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Table sticky={sticky} responsive={responsive} className={tableClassName} {...rest}>
|
|
34
|
+
<DataTableHead {...tableHeadProps} fields={fields}/>
|
|
35
|
+
{!!data.length && (
|
|
36
|
+
<DataTableTBody fields={fields} data={data} keyField={keyField} rowClassName={rowClassName}
|
|
37
|
+
renderRow={renderRow}
|
|
38
|
+
onSelectRow={onSelectRow} selected={selected}/>
|
|
39
|
+
)}
|
|
40
|
+
{children}
|
|
41
|
+
{tfoot}
|
|
42
|
+
</Table>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
DataTable.displayName = 'DataTable';
|
|
47
|
+
export default DataTable;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React, {ReactNode} from 'react';
|
|
2
|
+
import {DataTableCellProps} from "./types";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
|
|
5
|
+
export default function DataTableCell<T = unknown>({field, row, className, as, ...rest}:DataTableCellProps<T>) {
|
|
6
|
+
const cellClassName = classNames(
|
|
7
|
+
{[`text-${field.align}`]: !!field.align},
|
|
8
|
+
className,
|
|
9
|
+
typeof field.className === 'function' ? field.className(row) : field.className
|
|
10
|
+
);
|
|
11
|
+
return React.createElement(
|
|
12
|
+
(as ?? field.as) ?? 'td',
|
|
13
|
+
{
|
|
14
|
+
className: cellClassName,
|
|
15
|
+
scope: (as ?? field.as) === 'th' ? 'row' : undefined,
|
|
16
|
+
colSpan: field.colSpan,
|
|
17
|
+
...field.cellProps,
|
|
18
|
+
...rest
|
|
19
|
+
},
|
|
20
|
+
row[field.field] === undefined
|
|
21
|
+
? null
|
|
22
|
+
: (
|
|
23
|
+
typeof field.render === 'function'
|
|
24
|
+
? field.render(row)
|
|
25
|
+
: row[field.field] as ReactNode
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import DataTableTH from "./DataTableTH";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import {DataTableHeadProps} from "./types";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function DataTableHead<T = unknown>({fields, ...rest}: DataTableHeadProps<T>) {
|
|
8
|
+
return (
|
|
9
|
+
<thead {...rest}>
|
|
10
|
+
<tr>
|
|
11
|
+
{fields.map((field, index) => (
|
|
12
|
+
<DataTableTH key={field.id ?? index}
|
|
13
|
+
{...field.thProps}
|
|
14
|
+
field={field}
|
|
15
|
+
className={classNames(
|
|
16
|
+
typeof field.className === 'function'
|
|
17
|
+
? {[`text-${field.align}`]: !!field.align}
|
|
18
|
+
: field.className
|
|
19
|
+
)}/>
|
|
20
|
+
))}
|
|
21
|
+
</tr>
|
|
22
|
+
</thead>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
DataTableHead.displayName = 'DataTableHead';
|
|
27
|
+
export default DataTableHead;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, {ReactNode} from 'react';
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import {noop} from "./utils";
|
|
4
|
+
import {DataTableRowProps} from "./types";
|
|
5
|
+
import DataTableCell from "./DataTableCell";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function DataTableRow<T = unknown>({
|
|
9
|
+
className,
|
|
10
|
+
rowClassName,
|
|
11
|
+
selected,
|
|
12
|
+
fields,
|
|
13
|
+
row,
|
|
14
|
+
trRef,
|
|
15
|
+
onClick = noop,
|
|
16
|
+
...rest
|
|
17
|
+
}: DataTableRowProps<T>) {
|
|
18
|
+
const clickHandler = () => {
|
|
19
|
+
return onClick ? onClick() : noop();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const _className = typeof rowClassName === 'function' ? rowClassName(row) : rowClassName;
|
|
23
|
+
if (!row) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<tr ref={trRef}
|
|
29
|
+
className={classNames({'table-active': selected}, className, _className)}
|
|
30
|
+
onClick={clickHandler}
|
|
31
|
+
{...rest}>
|
|
32
|
+
{fields.map((field, index) => (<DataTableCell key={index} field={field} row={row} />))}
|
|
33
|
+
</tr>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
DataTableRow.displayName = 'DataTableRow';
|
|
38
|
+
export default DataTableRow;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import DataTableRow from "./DataTableRow";
|
|
3
|
+
import {noop} from "./utils";
|
|
4
|
+
import {DataTableTBodyProps} from "./types";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function DataTableTBody<T = unknown>({
|
|
9
|
+
fields,
|
|
10
|
+
data,
|
|
11
|
+
keyField,
|
|
12
|
+
rowClassName,
|
|
13
|
+
renderRow,
|
|
14
|
+
onSelectRow = noop,
|
|
15
|
+
selected = '',
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}: DataTableTBodyProps<T>) {
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<tbody {...rest}>
|
|
22
|
+
{data.map(row => {
|
|
23
|
+
const keyValue = String(typeof keyField === "function" ? keyField(row) : row[keyField]);
|
|
24
|
+
const isSelected = typeof selected === 'function' ? selected(row) : keyValue === selected;
|
|
25
|
+
if (renderRow) {
|
|
26
|
+
return renderRow(row);
|
|
27
|
+
}
|
|
28
|
+
return (
|
|
29
|
+
<DataTableRow key={keyValue} onClick={() => onSelectRow(row)}
|
|
30
|
+
rowClassName={rowClassName}
|
|
31
|
+
fields={fields}
|
|
32
|
+
row={row} selected={isSelected}/>
|
|
33
|
+
)
|
|
34
|
+
})}
|
|
35
|
+
{children}
|
|
36
|
+
</tbody>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
DataTableTBody.displayName = 'DataTableTBody';
|
|
40
|
+
export default DataTableTBody
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import {DataTableTHProps} from "./types";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function DataTableTH<T = unknown>({
|
|
7
|
+
field,
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
...rest
|
|
11
|
+
}: DataTableTHProps<T>) {
|
|
12
|
+
const thClassName = classNames({[`text-${field.align}`]: !!field.align}, className);
|
|
13
|
+
return (
|
|
14
|
+
<th className={thClassName} scope="col" {...rest}>
|
|
15
|
+
{children ?? field.title}
|
|
16
|
+
</th>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
DataTableTH.displayName = 'DataTableTH';
|
|
20
|
+
export default DataTableTH;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React, {ChangeEvent, useId} from 'react';
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import {RowsPerPageProps} from "./types";
|
|
4
|
+
|
|
5
|
+
export const defaultRowsPerPageValues: number[] = [10, 25, 50, 100, 250, 500, 1000];
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
function RowsPerPage({
|
|
9
|
+
value,
|
|
10
|
+
pageValues = defaultRowsPerPageValues,
|
|
11
|
+
size,
|
|
12
|
+
label,
|
|
13
|
+
className,
|
|
14
|
+
onChange,
|
|
15
|
+
...rest
|
|
16
|
+
}: RowsPerPageProps) {
|
|
17
|
+
const id = useId();
|
|
18
|
+
const changeHandler = (ev: ChangeEvent<HTMLSelectElement>) => onChange(Number(ev.target.value));
|
|
19
|
+
const selectClassName = className ?? classNames('form-select', {[`form-select-${size}`]: !!size});
|
|
20
|
+
const inputGroupClassName = classNames('input-group', {
|
|
21
|
+
[`input-group-${size}`]: !!size,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div className={inputGroupClassName} key={value}>
|
|
26
|
+
<label className="input-group-text" htmlFor={id}>{label ?? 'Rows'}</label>
|
|
27
|
+
<select className={selectClassName} id={id}
|
|
28
|
+
value={value} onChange={changeHandler} {...rest}>
|
|
29
|
+
{pageValues.map(value => (
|
|
30
|
+
<option key={value} value={value}>{value}</option>
|
|
31
|
+
))}
|
|
32
|
+
</select>
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
RowsPerPage.displayName = 'RowsPerPage';
|
|
38
|
+
export default RowsPerPage;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import SortableTableHead from "./SortableTableHead";
|
|
4
|
+
import DataTableTBody from "./DataTableTBody";
|
|
5
|
+
import {SortableTableProps} from "./types";
|
|
6
|
+
import {noop} from "./utils";
|
|
7
|
+
import Table from "./Table";
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
function SortableTable<T = unknown>({
|
|
11
|
+
fields,
|
|
12
|
+
data,
|
|
13
|
+
currentSort,
|
|
14
|
+
onChangeSort,
|
|
15
|
+
keyField,
|
|
16
|
+
size = '',
|
|
17
|
+
sticky,
|
|
18
|
+
rowClassName,
|
|
19
|
+
renderRow,
|
|
20
|
+
onSelectRow = noop,
|
|
21
|
+
selected = '',
|
|
22
|
+
className = '',
|
|
23
|
+
tfoot,
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}: SortableTableProps<T>) {
|
|
27
|
+
const tableClassName = classNames('table', className, {
|
|
28
|
+
[`table-${size}`]: !!size,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Table className={tableClassName} sticky={sticky} {...rest}>
|
|
33
|
+
<SortableTableHead currentSort={currentSort} fields={fields} onChangeSort={onChangeSort}/>
|
|
34
|
+
{!!data.length && (
|
|
35
|
+
<DataTableTBody fields={fields} data={data} keyField={keyField} rowClassName={rowClassName}
|
|
36
|
+
renderRow={renderRow}
|
|
37
|
+
onSelectRow={onSelectRow} selected={selected}/>
|
|
38
|
+
)}
|
|
39
|
+
{children}
|
|
40
|
+
{tfoot}
|
|
41
|
+
</Table>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
SortableTable.displayName = 'SortableTable';
|
|
46
|
+
export default SortableTable;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import SortableTableTH from "./SortableTableTH";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import {SortableTableHeadProps} from "./types";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function SortableTableHead<T = unknown>({
|
|
8
|
+
currentSort,
|
|
9
|
+
fields,
|
|
10
|
+
onChangeSort,
|
|
11
|
+
}: SortableTableHeadProps<T>) {
|
|
12
|
+
const {field, ascending} = currentSort;
|
|
13
|
+
return (
|
|
14
|
+
<thead>
|
|
15
|
+
<tr>
|
|
16
|
+
{fields.map((tableField, index) => (
|
|
17
|
+
<SortableTableTH<T> key={index} field={tableField}
|
|
18
|
+
sorted={field === tableField.field} ascending={ascending}
|
|
19
|
+
className={classNames(
|
|
20
|
+
typeof tableField.className === 'function'
|
|
21
|
+
? {[`text-${tableField.align}`]: !!tableField.align}
|
|
22
|
+
: tableField.className
|
|
23
|
+
)} onClick={onChangeSort}/>
|
|
24
|
+
))}
|
|
25
|
+
</tr>
|
|
26
|
+
</thead>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
SortableTableHead.displayName = 'SortableTableHead';
|
|
31
|
+
export default SortableTableHead;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import DataTableTH from "./DataTableTH";
|
|
4
|
+
import {SortableTableTHProps, UIFlexAlign} from "./types";
|
|
5
|
+
import styled from '@emotion/styled';
|
|
6
|
+
|
|
7
|
+
const flexJustifyContent = (align?: UIFlexAlign) => {
|
|
8
|
+
if (!align) {
|
|
9
|
+
return 'flex-start';
|
|
10
|
+
}
|
|
11
|
+
switch (align) {
|
|
12
|
+
case 'end':
|
|
13
|
+
return 'flex-end';
|
|
14
|
+
default:
|
|
15
|
+
return 'center';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type FieldTitleProps = {
|
|
20
|
+
sorted?: boolean;
|
|
21
|
+
align?: UIFlexAlign;
|
|
22
|
+
};
|
|
23
|
+
const FieldTitle = styled.div<FieldTitleProps>`
|
|
24
|
+
display: flex;
|
|
25
|
+
width: 100%;
|
|
26
|
+
flex-direction: ${props => props.align === 'end' ? 'row-reverse' : 'row'};
|
|
27
|
+
justify-content: ${props => flexJustifyContent(props.align)};
|
|
28
|
+
.sort-icon {
|
|
29
|
+
flex-grow: ${props => props.align === 'end' ? '1' : '0'};
|
|
30
|
+
opacity: ${props => props.sorted ? 1 : 0};
|
|
31
|
+
}
|
|
32
|
+
&:hover .sort-icon {
|
|
33
|
+
color: ${props => props.sorted ? 'unset' : 'var(--bs-primary)'} ;
|
|
34
|
+
opacity: 0.75;
|
|
35
|
+
transition: opacity 0.2s;
|
|
36
|
+
}
|
|
37
|
+
`
|
|
38
|
+
|
|
39
|
+
function SortableTableTH<T = unknown>({
|
|
40
|
+
field,
|
|
41
|
+
sorted,
|
|
42
|
+
ascending,
|
|
43
|
+
className,
|
|
44
|
+
onClick
|
|
45
|
+
}: SortableTableTHProps<T>) {
|
|
46
|
+
if (!field.sortable) {
|
|
47
|
+
return (<DataTableTH field={field} className={className}/>)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const {className: _thClassName, ...thProps} = field.thProps ?? {};
|
|
51
|
+
const thClassName = classNames(
|
|
52
|
+
className,
|
|
53
|
+
_thClassName,
|
|
54
|
+
{[`text-${field.align}`]: !!field.align}
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const clickHandler = () => {
|
|
58
|
+
onClick({field: field.field, ascending: !sorted ? true : !ascending});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const iconClassName = {
|
|
62
|
+
'bi-arrow-down': ascending,
|
|
63
|
+
'bi-arrow-up': !ascending,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<th {...thProps} className={classNames("sortable", thClassName)} scope="col" onClick={clickHandler}>
|
|
68
|
+
<FieldTitle sorted={sorted} align={field.align}>
|
|
69
|
+
<div className="field-title">{field.title}</div>
|
|
70
|
+
<div className={classNames('me-1 sort-icon', iconClassName)}/>
|
|
71
|
+
</FieldTitle>
|
|
72
|
+
</th>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
SortableTableTH.displayName = 'SortableTableTH';
|
|
77
|
+
export default SortableTableTH;
|
package/src/Table.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React, {TableHTMLAttributes} from 'react';
|
|
2
|
+
import styled from "@emotion/styled";
|
|
3
|
+
import {DataTableProps} from "./types";
|
|
4
|
+
import classNames from "classnames";
|
|
5
|
+
|
|
6
|
+
export type StyledTableProps = TableHTMLAttributes<HTMLTableElement> & Pick<DataTableProps, 'sticky'|'responsive'>
|
|
7
|
+
|
|
8
|
+
const StyledTable = styled.table<StyledTableProps>`
|
|
9
|
+
--table-sticky-top: ${props => props.sticky ? '0' : undefined};
|
|
10
|
+
|
|
11
|
+
thead {
|
|
12
|
+
tr:nth-of-type(1) td,
|
|
13
|
+
tr:nth-of-type(1) th {
|
|
14
|
+
top: var(--table-sticky-top, unset);
|
|
15
|
+
position: ${props => props.sticky ? "sticky" : "unset"};
|
|
16
|
+
z-index: ${props => props.sticky ? 10 : "unset"};
|
|
17
|
+
background: ${props => props.sticky ? "linear-gradient(var(--bs-table-bg) 75%, rgba(var(--bs-secondary-bg-rgb), 0.9))" : "unset"};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
`
|
|
21
|
+
|
|
22
|
+
export default React.forwardRef<HTMLTableElement, StyledTableProps>(
|
|
23
|
+
function Table({
|
|
24
|
+
sticky,
|
|
25
|
+
responsive,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}, ref) {
|
|
29
|
+
if (responsive) {
|
|
30
|
+
const className = classNames({
|
|
31
|
+
'table-responsive': responsive === true,
|
|
32
|
+
[`table-responsive-${responsive}`]: responsive !== true,
|
|
33
|
+
})
|
|
34
|
+
return (
|
|
35
|
+
<div className={className}>
|
|
36
|
+
<StyledTable ref={ref} {...rest}>{children}</StyledTable>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
return (
|
|
41
|
+
<StyledTable sticky={sticky} ref={ref} {...rest}>{children}</StyledTable>
|
|
42
|
+
)
|
|
43
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import RowsPerPage from "./RowsPerPage";
|
|
3
|
+
import classNames from "classnames";
|
|
4
|
+
import {TablePaginationProps} from "./types";
|
|
5
|
+
|
|
6
|
+
function TablePagination({
|
|
7
|
+
page,
|
|
8
|
+
rowsPerPage,
|
|
9
|
+
onChangePage,
|
|
10
|
+
count,
|
|
11
|
+
size,
|
|
12
|
+
showFirst,
|
|
13
|
+
showLast,
|
|
14
|
+
className,
|
|
15
|
+
rowsPerPageProps,
|
|
16
|
+
...rest
|
|
17
|
+
}: TablePaginationProps) {
|
|
18
|
+
|
|
19
|
+
const first = count === 0 ? 0 : (page * rowsPerPage) + 1;
|
|
20
|
+
const last = Math.min(page * rowsPerPage + rowsPerPage, count);
|
|
21
|
+
const lastPage = rowsPerPage === 0 ? 0 : Math.floor((count - 1) / rowsPerPage);
|
|
22
|
+
|
|
23
|
+
const buttonClassName = classNames("btn btn-link", {[`btn-${size}`]: !!size});
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={classNames("row g-3 justify-content-end", className)} {...rest}>
|
|
27
|
+
{!!rowsPerPageProps && (
|
|
28
|
+
<div className="col-auto">
|
|
29
|
+
<RowsPerPage {...rowsPerPageProps} value={rowsPerPage} size={size}/>
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
<div className="col-auto">
|
|
33
|
+
<div className="row g-3 flex-nowrap align-items-baseline">
|
|
34
|
+
<div className="col-auto">
|
|
35
|
+
{first}-{last} of {count}
|
|
36
|
+
</div>
|
|
37
|
+
{showFirst && (
|
|
38
|
+
<div className="col-auto">
|
|
39
|
+
<button className={buttonClassName} disabled={page === 0}
|
|
40
|
+
onClick={() => onChangePage(0)} aria-label="First page">
|
|
41
|
+
<span className="bi-chevron-bar-left" aria-hidden="true"/>
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
<div className="col-auto">
|
|
46
|
+
<button className={buttonClassName} disabled={page === 0}
|
|
47
|
+
onClick={() => onChangePage(page - 1)} aria-label="Previous page">
|
|
48
|
+
<span className="bi-chevron-left" aria-hidden="true"/>
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="col-auto">
|
|
52
|
+
<button className={buttonClassName} disabled={page >= lastPage}
|
|
53
|
+
onClick={() => onChangePage(page + 1)} aria-label="Next page">
|
|
54
|
+
<span className="bi-chevron-right" aria-hidden="true"/>
|
|
55
|
+
</button>
|
|
56
|
+
</div>
|
|
57
|
+
{showLast && (
|
|
58
|
+
<div className="col-auto">
|
|
59
|
+
<button className={buttonClassName} disabled={page >= lastPage}
|
|
60
|
+
onClick={() => onChangePage(lastPage)} aria-label="Last page">
|
|
61
|
+
<span className="bi-chevron-bar-right" aria-hidden="true"/>
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
TablePagination.displayname = 'TablePagination';
|
|
72
|
+
export default TablePagination
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export {default as DataTable} from './DataTable';
|
|
2
|
+
export {default as DataTableRow} from './DataTableRow';
|
|
3
|
+
export {default as DataTableTBody} from './DataTableTBody';
|
|
4
|
+
export {default as DataTableTH} from './DataTableTH';
|
|
5
|
+
export {default as SortableTable} from './SortableTable';
|
|
6
|
+
export {default as SortableTableHead} from './SortableTableHead';
|
|
7
|
+
export {default as SortableTableTH} from './SortableTableTH';
|
|
8
|
+
export {default as RowsPerPage, defaultRowsPerPageValues} from './RowsPerPage';
|
|
9
|
+
export {default as TablePagination} from './TablePagination';
|
|
10
|
+
export type {
|
|
11
|
+
DataTableHeadProps, DataTableField, DataTableTHProps, DataTableClassNames, DataTableProps,
|
|
12
|
+
SortableTableField, DataTableRowProps, DataTableTBodyProps, SortableTableTHProps, SortableTableHeadProps,
|
|
13
|
+
SortProps, SortableTableProps, UISize, UIFlexAlign, TablePaginationProps, RowsPerPageProps
|
|
14
|
+
} from './types';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, {HTMLAttributes, ReactNode, TableHTMLAttributes} from 'react'
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export interface SortProps<T = unknown> {
|
|
6
|
+
field: keyof T;
|
|
7
|
+
ascending: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type UISize = 'sm'|'lg'|'';
|
|
11
|
+
export type UITableSize = UISize|'xs';
|
|
12
|
+
|
|
13
|
+
export type DataTableClassNames<T = unknown> =
|
|
14
|
+
string
|
|
15
|
+
| classNames.Argument
|
|
16
|
+
| ((row: T) => (string | classNames.Argument));
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export type UIFlexAlign = 'start' | 'end' | 'center' | 'baseline' | 'stretch';
|
|
20
|
+
|
|
21
|
+
export interface DataTableField<T = unknown> {
|
|
22
|
+
id?: number | string;
|
|
23
|
+
field: keyof T;
|
|
24
|
+
title: ReactNode;
|
|
25
|
+
as?: 'td'|'th';
|
|
26
|
+
align?: 'start' | 'center' | 'end';
|
|
27
|
+
render?: (row: T) => ReactNode;
|
|
28
|
+
className?: DataTableClassNames<T>;
|
|
29
|
+
colSpan?: number;
|
|
30
|
+
thProps?: Omit<DataTableTHProps<T>, 'field'>;
|
|
31
|
+
cellProps?: TableHTMLAttributes<HTMLTableCellElement>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SortableTableField<T = unknown> extends DataTableField<T> {
|
|
35
|
+
sortable?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DataTableProps<T = unknown> extends TableHTMLAttributes<HTMLTableElement> {
|
|
39
|
+
fields: DataTableField<T>[];
|
|
40
|
+
data: T[];
|
|
41
|
+
keyField: keyof T | ((row: T) => string|number);
|
|
42
|
+
size?: UITableSize;
|
|
43
|
+
sticky?: boolean;
|
|
44
|
+
responsive?: boolean|"sm" | "md" | "lg" | "xl" | 'xxl';
|
|
45
|
+
rowClassName?: DataTableClassNames<T>;
|
|
46
|
+
renderRow?: (row: T) => React.ReactNode;
|
|
47
|
+
onSelectRow?: (row: T) => T | void;
|
|
48
|
+
selected?: string | number | ((row: T) => boolean);
|
|
49
|
+
tfoot?: React.ReactElement<HTMLTableSectionElement>;
|
|
50
|
+
tableHeadProps?: DataTableHeadProps<T>;
|
|
51
|
+
children?: ReactNode;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface DataTableCellProps<T = unknown> extends Omit<TableHTMLAttributes<HTMLTableCellElement>, 'className'> {
|
|
55
|
+
field: DataTableField<T>;
|
|
56
|
+
row: T;
|
|
57
|
+
as?: 'td'|'th',
|
|
58
|
+
className?: string | classNames.Argument;
|
|
59
|
+
children?: React.ReactNode;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface DataTableTHProps<T = unknown> extends Omit<DataTableCellProps<T>, 'row'> {
|
|
63
|
+
as?: 'th',
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface DataTableHeadProps<T = unknown> extends TableHTMLAttributes<HTMLTableSectionElement> {
|
|
67
|
+
fields: DataTableField<T>[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface DataTableTBodyProps<T = unknown> extends TableHTMLAttributes<HTMLTableSectionElement> {
|
|
71
|
+
fields: DataTableField<T>[];
|
|
72
|
+
data: T[];
|
|
73
|
+
keyField: keyof T | ((row: T) => string|number);
|
|
74
|
+
rowClassName?: DataTableClassNames<T>;
|
|
75
|
+
renderRow?: (row: T) => React.ReactNode;
|
|
76
|
+
onSelectRow?: (row: T) => T | void;
|
|
77
|
+
selected?: string | number | ((row: T) => boolean);
|
|
78
|
+
children?: ReactNode;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface DataTableRowProps<T = unknown> extends Omit <TableHTMLAttributes<HTMLTableRowElement>, 'onClick'> {
|
|
82
|
+
rowClassName?: string | classNames.Argument | ((row: T) => string | classNames.Argument);
|
|
83
|
+
selected?: boolean;
|
|
84
|
+
fields: DataTableField<T>[];
|
|
85
|
+
row: T;
|
|
86
|
+
trRef?: React.Ref<HTMLTableRowElement>;
|
|
87
|
+
onClick?: (row?: T) => T | void;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface SortableTableProps<T = unknown> extends DataTableProps<T> {
|
|
91
|
+
currentSort: SortProps<T>;
|
|
92
|
+
onChangeSort: (sort: SortProps<T>) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface SortableTableHeadProps<T = unknown> extends DataTableHeadProps<T> {
|
|
96
|
+
currentSort: SortProps<T>;
|
|
97
|
+
fields: SortableTableField<T>[];
|
|
98
|
+
onChangeSort: (sort: SortProps<T>) => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface SortableTableTHProps<T = unknown> extends Omit<DataTableTHProps<T>, 'onClick'> {
|
|
102
|
+
field: SortableTableField<T>;
|
|
103
|
+
sorted?: boolean;
|
|
104
|
+
ascending?: boolean;
|
|
105
|
+
onClick: (sort: SortProps<T>) => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface RowsPerPageProps extends Omit<HTMLAttributes<HTMLSelectElement>, 'onChange'> {
|
|
109
|
+
value: number;
|
|
110
|
+
pageValues?: number[];
|
|
111
|
+
label?: string;
|
|
112
|
+
size?: UISize;
|
|
113
|
+
className?: string;
|
|
114
|
+
onChange: (value: number) => void;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface TablePaginationProps extends HTMLAttributes<HTMLDivElement> {
|
|
118
|
+
page: number;
|
|
119
|
+
rowsPerPage: number;
|
|
120
|
+
onChangePage: (page: number) => void;
|
|
121
|
+
count: number;
|
|
122
|
+
size?: UISize;
|
|
123
|
+
showFirst?: boolean;
|
|
124
|
+
showLast?: boolean;
|
|
125
|
+
rowsPerPageProps?: Omit<RowsPerPageProps, 'value'>;
|
|
126
|
+
}
|
|
127
|
+
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function noop(){}
|