@digigov/ui 0.33.0 → 0.34.0
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 +12 -1
- package/admin/Dropdown/index.js +34 -27
- package/core/Table/Table.stories.d.ts +1 -0
- package/core/Table/Table.stories.js +14 -0
- package/core/Table/__stories__/Stacked.d.ts +2 -0
- package/core/Table/__stories__/Stacked.js +42 -0
- package/core/Table/__stories__/WithSortFilters.js +34 -54
- package/core/Table/index.d.ts +11 -19
- package/core/Table/index.js +85 -174
- package/es/admin/Dropdown/index.js +34 -27
- package/es/core/Table/Table.stories.js +1 -0
- package/es/core/Table/__stories__/Stacked.js +29 -0
- package/es/core/Table/__stories__/WithSortFilters.js +34 -54
- package/es/core/Table/index.js +70 -154
- package/es/hooks/useSort.js +54 -0
- package/esm/admin/Dropdown/index.js +34 -27
- package/esm/core/Table/Table.stories.js +1 -0
- package/esm/core/Table/__stories__/Stacked.js +29 -0
- package/esm/core/Table/__stories__/WithSortFilters.js +34 -54
- package/esm/core/Table/index.js +70 -154
- package/esm/hooks/useSort.js +54 -0
- package/esm/index.js +1 -1
- package/hooks/useSort.d.ts +9 -0
- package/hooks/useSort.js +67 -0
- package/package.json +3 -3
- package/src/admin/Dropdown/index.tsx +79 -63
- package/src/core/Table/Table.stories.js +1 -0
- package/src/core/Table/__stories__/Stacked.tsx +59 -0
- package/src/core/Table/__stories__/WithSortFilters.tsx +34 -71
- package/src/core/Table/index.tsx +92 -175
- package/src/hooks/useSort.tsx +53 -0
|
@@ -7,76 +7,45 @@ import {
|
|
|
7
7
|
TableHead,
|
|
8
8
|
TableRow,
|
|
9
9
|
TableBody,
|
|
10
|
-
|
|
10
|
+
TableHeadCell,
|
|
11
11
|
TableDataCell,
|
|
12
12
|
TableNoDataRow,
|
|
13
|
-
|
|
13
|
+
TableSortLabel,
|
|
14
14
|
} from '@digigov/ui/core/Table';
|
|
15
|
+
import { useSort } from '@digigov/ui/hooks/useSort';
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const headerConfig = [
|
|
17
18
|
{
|
|
18
19
|
name: 'product',
|
|
19
20
|
label: 'Προϊόν',
|
|
20
|
-
sortLabels:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
value: 1,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
label: 'Αλφαβητική σειρά (Ω -> Α)',
|
|
28
|
-
direction: 'desc',
|
|
29
|
-
value: -1,
|
|
30
|
-
},
|
|
31
|
-
],
|
|
21
|
+
sortLabels: {
|
|
22
|
+
asc: 'Αλφαβητική σειρά (Α -> Ω)',
|
|
23
|
+
desc: 'Αλφαβητική σειρά (Ω -> Α)',
|
|
24
|
+
},
|
|
32
25
|
},
|
|
33
26
|
{
|
|
34
27
|
name: 'price',
|
|
35
28
|
label: 'Τιμή',
|
|
36
|
-
sortLabels:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
value: 1,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
label: 'Φθίνουσα σειρά',
|
|
44
|
-
direction: 'descending',
|
|
45
|
-
value: -1,
|
|
46
|
-
},
|
|
47
|
-
],
|
|
29
|
+
sortLabels: {
|
|
30
|
+
asc: 'Αύξουσα σειρά',
|
|
31
|
+
desc: 'Φθίνουσα σειρά',
|
|
32
|
+
},
|
|
48
33
|
},
|
|
49
34
|
{
|
|
50
35
|
name: 'stock',
|
|
51
36
|
label: 'Στοκ',
|
|
52
|
-
sortLabels:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
value: 1,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
label: 'Φθίνουσα σειρά',
|
|
60
|
-
direction: 'descending',
|
|
61
|
-
value: -1,
|
|
62
|
-
},
|
|
63
|
-
],
|
|
37
|
+
sortLabels: {
|
|
38
|
+
asc: 'Αύξουσα σειρά',
|
|
39
|
+
desc: 'Φθίνουσα σειρά',
|
|
40
|
+
},
|
|
64
41
|
},
|
|
65
42
|
{
|
|
66
43
|
name: 'date',
|
|
67
44
|
label: 'Ημερομηνία',
|
|
68
|
-
sortLabels:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
value: 1,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
label: 'Φθίνουσα σειρά',
|
|
76
|
-
direction: 'descending',
|
|
77
|
-
value: -1,
|
|
78
|
-
},
|
|
79
|
-
],
|
|
45
|
+
sortLabels: {
|
|
46
|
+
asc: 'Αύξουσα σειρά',
|
|
47
|
+
desc: 'Φθίνουσα σειρά',
|
|
48
|
+
},
|
|
80
49
|
},
|
|
81
50
|
];
|
|
82
51
|
|
|
@@ -133,38 +102,32 @@ const data = [
|
|
|
133
102
|
];
|
|
134
103
|
|
|
135
104
|
export const WithSortFilters = () => {
|
|
136
|
-
const { sortedData,
|
|
105
|
+
const { sortedData, setSortedField, field, direction } = useSort(data);
|
|
137
106
|
|
|
138
107
|
return (
|
|
139
108
|
<div className="example">
|
|
140
|
-
<TableContainer
|
|
141
|
-
/* Possible style solution in order to display dropdown properly when we have only one table row
|
|
142
|
-
* style={{
|
|
143
|
-
* overflow: 'inherit',
|
|
144
|
-
* }}
|
|
145
|
-
*/
|
|
146
|
-
>
|
|
109
|
+
<TableContainer>
|
|
147
110
|
<Table>
|
|
148
111
|
<TableCaption>Table of products</TableCaption>
|
|
149
112
|
<TableHead>
|
|
150
113
|
<TableRow>
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
headerItems={header}
|
|
114
|
+
{headerConfig.map((header, index) => {
|
|
115
|
+
return (
|
|
116
|
+
<TableHeadCell key={index}>
|
|
117
|
+
<TableSortLabel
|
|
118
|
+
labels={header.sortLabels}
|
|
119
|
+
onSort={(dir) => setSortedField(header.name, dir)}
|
|
120
|
+
direction={header.name === field ? direction : 0}
|
|
159
121
|
>
|
|
160
122
|
{header.label}
|
|
161
|
-
</
|
|
162
|
-
|
|
163
|
-
|
|
123
|
+
</TableSortLabel>
|
|
124
|
+
</TableHeadCell>
|
|
125
|
+
);
|
|
126
|
+
})}
|
|
164
127
|
</TableRow>
|
|
165
128
|
</TableHead>
|
|
166
129
|
<TableBody>
|
|
167
|
-
{sortedData
|
|
130
|
+
{sortedData.length > 0 ? (
|
|
168
131
|
sortedData.map((d, index) => {
|
|
169
132
|
return (
|
|
170
133
|
<TableRow key={index}>
|
package/src/core/Table/index.tsx
CHANGED
|
@@ -1,194 +1,112 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useRef } from 'react';
|
|
2
2
|
import Table from '@digigov/react-core/Table';
|
|
3
|
-
import { useMemo } from 'react';
|
|
4
3
|
import {
|
|
5
4
|
Dropdown,
|
|
6
5
|
DropdownButton,
|
|
7
6
|
DropdownContent,
|
|
7
|
+
DropdownProps,
|
|
8
8
|
} from '@digigov/ui/admin/Dropdown';
|
|
9
9
|
import { NavVertical } from '@digigov/ui/core/NavList/NavVertical';
|
|
10
10
|
import { NavVerticalItem } from '@digigov/ui/core/NavList/NavVerticalItem';
|
|
11
11
|
import { CaretIcon } from '@digigov/ui/core/SvgIcon';
|
|
12
|
-
import TableHeadCellBase, {
|
|
13
|
-
TableHeadCellProps as TableHeadCellSortProps,
|
|
14
|
-
} from '@digigov/react-core/TableHeadCell';
|
|
15
12
|
import CaretContainer from '@digigov/ui/core/CaretContainer';
|
|
16
13
|
|
|
17
|
-
export
|
|
18
|
-
sortedAscDesc?: (x: any, y: number) => void;
|
|
19
|
-
sortedField?: string;
|
|
20
|
-
sortedDirectionValue?: number;
|
|
21
|
-
headerItems?: any;
|
|
22
|
-
sortedData?: any[];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const useSortableData = (items) => {
|
|
26
|
-
const [field, setField] = useState('');
|
|
27
|
-
const [directionValue, setDirectionValue] = useState(0);
|
|
28
|
-
const sortedItems = useMemo(() => {
|
|
29
|
-
const sortableItems = [...items];
|
|
30
|
-
if (sortableItems.length === 0) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (field) {
|
|
34
|
-
sortableItems.sort((a, b) => {
|
|
35
|
-
if (a[field] === null) return 1;
|
|
36
|
-
if (b[field] === null) return -1;
|
|
37
|
-
if (a[field] === null && b[field] === null) return 0;
|
|
38
|
-
return (
|
|
39
|
-
a[field].toString().localeCompare(b[field].toString(), undefined, {
|
|
40
|
-
numeric: true,
|
|
41
|
-
}) * (directionValue > 0 ? 1 : -1)
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
return sortableItems;
|
|
46
|
-
}, [items, directionValue, field]);
|
|
14
|
+
export type SortDirection = 1 | -1 | 0;
|
|
47
15
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
16
|
+
export interface TableSortLabelProps extends DropdownProps {
|
|
17
|
+
labels: {
|
|
18
|
+
asc: string;
|
|
19
|
+
desc: string;
|
|
51
20
|
};
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
direction?: SortDirection;
|
|
23
|
+
onSort: (direction: SortDirection) => void;
|
|
24
|
+
}
|
|
52
25
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
sortedField: field,
|
|
59
|
-
sortedAscDesc: sortAscDesc,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
const TableHeadCellSort = React.forwardRef<HTMLTableCellElement, sortedProps>(
|
|
64
|
-
function TableHeadCellSort({ ...props }, ref) {
|
|
65
|
-
return <TableHeadCellSortBase ref={ref} {...props} />;
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
export const TableHeadCellSortBase = React.forwardRef<
|
|
70
|
-
HTMLTableCellElement,
|
|
71
|
-
sortedProps
|
|
72
|
-
>(function TableHeadCellSortBase(
|
|
73
|
-
{
|
|
74
|
-
sortedAscDesc,
|
|
75
|
-
sortedField,
|
|
76
|
-
sortedDirectionValue,
|
|
77
|
-
headerItems,
|
|
78
|
-
sortedData,
|
|
79
|
-
children,
|
|
80
|
-
...props
|
|
81
|
-
},
|
|
26
|
+
export const TableSortLabel = React.forwardRef<
|
|
27
|
+
HTMLDetailsElement,
|
|
28
|
+
TableSortLabelProps
|
|
29
|
+
>(function TableSortLabel(
|
|
30
|
+
{ labels, disabled, children, direction = 0, onSort, ...props },
|
|
82
31
|
ref
|
|
83
32
|
) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
(name, value) => {
|
|
89
|
-
if (sortedAscDesc) {
|
|
90
|
-
if (sortedField !== name) {
|
|
91
|
-
sortedAscDesc('', 0);
|
|
92
|
-
|
|
93
|
-
setSortDirection(value);
|
|
94
|
-
sortedAscDesc(name, value);
|
|
95
|
-
} else {
|
|
96
|
-
if (value !== sortDirection) {
|
|
97
|
-
setSortDirection(value);
|
|
98
|
-
sortedAscDesc(name, value);
|
|
99
|
-
} else {
|
|
100
|
-
setSortDirection(0);
|
|
101
|
-
sortedAscDesc('', 0);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
[sortedField, sortedDirectionValue, sortDirection]
|
|
107
|
-
);
|
|
108
|
-
const onKeyDown = useCallback(
|
|
109
|
-
(e, name, value) => {
|
|
110
|
-
if (e.key === 'Enter' && innerRef.current.contains(e.target)) {
|
|
111
|
-
if (innerRef.current.open) {
|
|
112
|
-
setSortAscDesc(name, value);
|
|
113
|
-
innerRef.current.open = false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
[name, sortDirection]
|
|
118
|
-
);
|
|
119
|
-
const setDisabled = useCallback(
|
|
120
|
-
(sortedData) => {
|
|
121
|
-
return sortedData === undefined || sortedData.length === 0 ? true : false;
|
|
122
|
-
},
|
|
123
|
-
[sortedData]
|
|
124
|
-
);
|
|
125
|
-
const setCaretColor = useCallback(
|
|
126
|
-
(value: number) => {
|
|
127
|
-
if (sortedField === name) {
|
|
128
|
-
return sortedDirectionValue === value ? 'dark' : 'gray';
|
|
129
|
-
} else {
|
|
130
|
-
return 'gray';
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
|
-
[sortedDirectionValue, sortedField]
|
|
134
|
-
);
|
|
33
|
+
// TODO: this is a workaround for the dropdown component
|
|
34
|
+
// https://itnext.io/reusing-the-ref-from-forwardref-with-react-hooks-4ce9df693dd
|
|
35
|
+
const dropdownRef = useRef<HTMLDetailsElement | null>(null);
|
|
36
|
+
const active = [-1, 1].includes(direction);
|
|
135
37
|
return (
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
</
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
38
|
+
<Dropdown
|
|
39
|
+
ref={(el) => {
|
|
40
|
+
dropdownRef.current = el;
|
|
41
|
+
typeof ref === 'function' && ref(el);
|
|
42
|
+
}}
|
|
43
|
+
disabled={disabled}
|
|
44
|
+
{...props}
|
|
45
|
+
>
|
|
46
|
+
<DropdownButton variant="link" tabIndex={0} underline={active}>
|
|
47
|
+
{children}
|
|
48
|
+
<CaretContainer marginLeft={1}>
|
|
49
|
+
<CaretIcon
|
|
50
|
+
direction={'up'}
|
|
51
|
+
size="m"
|
|
52
|
+
variant={direction === 1 ? 'dark' : 'gray'}
|
|
53
|
+
/>
|
|
54
|
+
<CaretIcon
|
|
55
|
+
direction={'down'}
|
|
56
|
+
size="m"
|
|
57
|
+
variant={direction === -1 ? 'dark' : 'gray'}
|
|
58
|
+
/>
|
|
59
|
+
</CaretContainer>
|
|
60
|
+
</DropdownButton>
|
|
61
|
+
<DropdownContent>
|
|
62
|
+
<NavVertical>
|
|
63
|
+
<NavVerticalItem
|
|
64
|
+
active={direction === 1}
|
|
65
|
+
tabIndex={0}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
onSort(1);
|
|
68
|
+
dropdownRef.current && (dropdownRef.current.open = false);
|
|
69
|
+
}}
|
|
70
|
+
onKeyDown={(e) => {
|
|
71
|
+
if (e.key === 'Enter') {
|
|
72
|
+
onSort(1);
|
|
73
|
+
dropdownRef.current && (dropdownRef.current.open = false);
|
|
74
|
+
}
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<CaretIcon
|
|
78
|
+
direction={'up'}
|
|
79
|
+
marginRight={1}
|
|
80
|
+
marginTop={1}
|
|
81
|
+
size="m"
|
|
82
|
+
/>
|
|
83
|
+
{labels.asc}
|
|
84
|
+
</NavVerticalItem>
|
|
85
|
+
<NavVerticalItem
|
|
86
|
+
active={direction === -1}
|
|
87
|
+
tabIndex={0}
|
|
88
|
+
onClick={() => {
|
|
89
|
+
onSort(-1);
|
|
90
|
+
dropdownRef.current && (dropdownRef.current.open = false);
|
|
91
|
+
}}
|
|
92
|
+
onKeyDown={(e) => {
|
|
93
|
+
if (e.key === 'Enter') {
|
|
94
|
+
onSort(-1);
|
|
95
|
+
dropdownRef.current && (dropdownRef.current.open = false);
|
|
96
|
+
}
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<CaretIcon
|
|
100
|
+
direction={'down'}
|
|
101
|
+
marginRight={1}
|
|
102
|
+
marginTop={1}
|
|
103
|
+
size="m"
|
|
104
|
+
/>
|
|
105
|
+
{labels.desc}
|
|
106
|
+
</NavVerticalItem>
|
|
107
|
+
</NavVertical>
|
|
108
|
+
</DropdownContent>
|
|
109
|
+
</Dropdown>
|
|
192
110
|
);
|
|
193
111
|
});
|
|
194
112
|
|
|
@@ -202,5 +120,4 @@ export * from '@digigov/react-core/TableHeadCell';
|
|
|
202
120
|
export * from '@digigov/react-core/TableRow';
|
|
203
121
|
export * from '@digigov/react-core/TableNoDataRow';
|
|
204
122
|
export * from '@digigov/ui/core/Table/TableFloatingScroll';
|
|
205
|
-
export { TableHeadCellSort, useSortableData };
|
|
206
123
|
export default Table;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { SortDirection } from '@digigov/ui/core/Table';
|
|
4
|
+
|
|
5
|
+
export interface UseSortResult {
|
|
6
|
+
sortedData: any[];
|
|
7
|
+
setSortedField: (item: string, value: SortDirection) => void;
|
|
8
|
+
field: string;
|
|
9
|
+
direction: SortDirection;
|
|
10
|
+
}
|
|
11
|
+
export type SortData = Record<string, any>[];
|
|
12
|
+
export const useSort = (data: SortData): UseSortResult => {
|
|
13
|
+
const [sortDataBy, setSortDataBy] = useState('');
|
|
14
|
+
const [direction, setDirection] = useState<-1 | 1 | 0>(0);
|
|
15
|
+
const sortedItems = useMemo(() => {
|
|
16
|
+
const sortableItems = [...data];
|
|
17
|
+
if (sortableItems.length === 0) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
if (sortDataBy) {
|
|
21
|
+
sortableItems.sort((a, b) => {
|
|
22
|
+
if (a[sortDataBy] === null) return 1;
|
|
23
|
+
if (b[sortDataBy] === null) return -1;
|
|
24
|
+
if (a[sortDataBy] === null && b[sortDataBy] === null) return 0;
|
|
25
|
+
return (
|
|
26
|
+
a[sortDataBy]
|
|
27
|
+
.toString()
|
|
28
|
+
.localeCompare(b[sortDataBy].toString(), undefined, {
|
|
29
|
+
numeric: true,
|
|
30
|
+
}) * (direction > 0 ? 1 : -1)
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return sortableItems;
|
|
35
|
+
}, [data, direction, sortDataBy]);
|
|
36
|
+
|
|
37
|
+
const setSortedField = (item, value) => {
|
|
38
|
+
if (item === sortDataBy && value === direction) {
|
|
39
|
+
setDirection(0);
|
|
40
|
+
setSortDataBy('');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
setDirection(value);
|
|
44
|
+
setSortDataBy(item);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
sortedData: sortedItems,
|
|
49
|
+
setSortedField,
|
|
50
|
+
field: sortDataBy,
|
|
51
|
+
direction,
|
|
52
|
+
};
|
|
53
|
+
};
|