@dbcdk/react-components 0.0.41 → 0.0.43
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/dist/components/circle/Circle.module.css +1 -0
- package/dist/components/copy-button/CopyButton.js +5 -1
- package/dist/components/filter-field/FilterField.d.ts +2 -1
- package/dist/components/filter-field/FilterField.js +49 -22
- package/dist/components/pagination/Pagination.js +1 -1
- package/dist/components/table/Table.module.css +56 -2
- package/dist/components/table/Table.types.d.ts +1 -0
- package/dist/components/table/components/TableRow.js +3 -2
- package/dist/components/table/tanstackTable.utils.js +2 -1
- package/dist/hooks/usePagination.d.ts +6 -1
- package/dist/hooks/usePagination.js +77 -16
- package/package.json +1 -1
- package/dist/components/table/components/TableCell.d.ts +0 -9
- package/dist/components/table/components/TableCell.js +0 -7
|
@@ -47,5 +47,9 @@ export function CopyButton(props) {
|
|
|
47
47
|
handleCopy();
|
|
48
48
|
}, className: `${styles.link} ${copied ? styles.copied : ''}`, children: [children, copied ? _jsx(Check, {}) : _jsx(Copy, {})] }) }));
|
|
49
49
|
}
|
|
50
|
-
return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder'), onClick:
|
|
50
|
+
return (_jsxs(Button, { ...rest, "aria-label": children ? '' : ((_b = rest['aria-label']) !== null && _b !== void 0 ? _b : 'Kopier til udklipsholder'), onClick: e => {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
handleCopy();
|
|
54
|
+
}, variant: variant, size: size, children: [_jsx("span", { className: `${styles.container} ${copied ? styles.copied : ''}`, children: copied ? _jsx(Check, {}) : _jsx(Copy, {}) }), children] }));
|
|
51
55
|
}
|
|
@@ -25,9 +25,10 @@ export interface FilterFieldProps extends Omit<React.InputHTMLAttributes<HTMLInp
|
|
|
25
25
|
placeholder?: string;
|
|
26
26
|
disabled?: boolean;
|
|
27
27
|
width?: string;
|
|
28
|
+
debounceTime?: number;
|
|
28
29
|
}
|
|
29
30
|
export declare const NUMBER_OPERATORS: Operator[];
|
|
30
|
-
export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, width, ...inputProps }: FilterFieldProps & {
|
|
31
|
+
export declare function FilterField({ field, control, operator, value, onChange, operators, options, single, size, variant, label, placeholder, disabled, 'data-cy': dataCy, width, debounceTime, ...inputProps }: FilterFieldProps & {
|
|
31
32
|
'data-cy'?: string;
|
|
32
33
|
}): React.ReactElement;
|
|
33
34
|
export {};
|
|
@@ -44,6 +44,8 @@ export const NUMBER_OPERATORS = [
|
|
|
44
44
|
'isEmpty',
|
|
45
45
|
'isNotEmpty',
|
|
46
46
|
];
|
|
47
|
+
const VALUELESS_OPERATORS = ['isEmpty', 'isNotEmpty'];
|
|
48
|
+
const INPUT_DEBOUNCE_MS = 500;
|
|
47
49
|
function OperatorDropdown({ value, onChange, operators, size = 'sm', disabled, }) {
|
|
48
50
|
const popRef = useRef(null);
|
|
49
51
|
const [activeIndex, setActiveIndex] = useState(() => Math.max(0, operators.indexOf(value)));
|
|
@@ -68,8 +70,7 @@ function isFilterActive(value) {
|
|
|
68
70
|
return value.trim().length > 0;
|
|
69
71
|
return value != null;
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, ...inputProps }) {
|
|
73
|
+
export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', variant = 'surface', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, debounceTime = INPUT_DEBOUNCE_MS, ...inputProps }) {
|
|
73
74
|
var _a, _b;
|
|
74
75
|
const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
|
|
75
76
|
const [selectedOperator, setSelectedOperator] = useState(operator);
|
|
@@ -81,66 +82,92 @@ export function FilterField({ field, control, operator, value, onChange, operato
|
|
|
81
82
|
}, [operator, ops]);
|
|
82
83
|
const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
|
|
83
84
|
const debounceRef = useRef(null);
|
|
84
|
-
|
|
85
|
+
/**
|
|
86
|
+
* Holds the exact text value we most recently sent upward.
|
|
87
|
+
* While waiting for that value to round-trip back through URL/provider state,
|
|
88
|
+
* we ignore intermediate external churn so the input does not visually flicker.
|
|
89
|
+
*/
|
|
90
|
+
const pendingValueRef = useRef(null);
|
|
85
91
|
const emit = (next) => {
|
|
86
92
|
var _a, _b;
|
|
87
93
|
const nextOperator = (_a = next.operator) !== null && _a !== void 0 ? _a : selectedOperator;
|
|
88
94
|
const nextValue = (_b = next.value) !== null && _b !== void 0 ? _b : value;
|
|
89
|
-
if (next.operator)
|
|
95
|
+
if (next.operator) {
|
|
90
96
|
setSelectedOperator(nextOperator);
|
|
97
|
+
}
|
|
91
98
|
onChange({
|
|
92
99
|
field,
|
|
93
100
|
operator: nextOperator,
|
|
94
101
|
value: nextValue,
|
|
95
102
|
});
|
|
96
103
|
};
|
|
104
|
+
const clearDebounce = () => {
|
|
105
|
+
if (debounceRef.current) {
|
|
106
|
+
clearTimeout(debounceRef.current);
|
|
107
|
+
debounceRef.current = null;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const scheduleEmitValue = (nextVal) => {
|
|
111
|
+
clearDebounce();
|
|
112
|
+
pendingValueRef.current = nextVal;
|
|
113
|
+
debounceRef.current = setTimeout(() => {
|
|
114
|
+
emit({ value: nextVal });
|
|
115
|
+
}, debounceTime);
|
|
116
|
+
};
|
|
117
|
+
const flushPendingValue = () => {
|
|
118
|
+
if (control !== 'input')
|
|
119
|
+
return;
|
|
120
|
+
clearDebounce();
|
|
121
|
+
pendingValueRef.current = localValue;
|
|
122
|
+
emit({ value: localValue });
|
|
123
|
+
};
|
|
97
124
|
const handleOperatorChange = (op) => {
|
|
98
125
|
setSelectedOperator(op);
|
|
99
126
|
if (!active && !VALUELESS_OPERATORS.includes(op))
|
|
100
127
|
return;
|
|
101
128
|
if (VALUELESS_OPERATORS.includes(op)) {
|
|
129
|
+
clearDebounce();
|
|
130
|
+
pendingValueRef.current = null;
|
|
102
131
|
emit({ operator: op, value: null });
|
|
103
132
|
return;
|
|
104
133
|
}
|
|
134
|
+
clearDebounce();
|
|
135
|
+
pendingValueRef.current =
|
|
136
|
+
control === 'input' ? localValue : typeof value === 'string' ? value : null;
|
|
105
137
|
emit({ operator: op });
|
|
106
138
|
};
|
|
107
|
-
const scheduleEmitValue = (nextVal) => {
|
|
108
|
-
if (debounceRef.current)
|
|
109
|
-
clearTimeout(debounceRef.current);
|
|
110
|
-
debounceRef.current = setTimeout(() => {
|
|
111
|
-
isTypingRef.current = false;
|
|
112
|
-
emit({ value: nextVal });
|
|
113
|
-
}, 250);
|
|
114
|
-
};
|
|
115
139
|
useEffect(() => {
|
|
116
140
|
var _a;
|
|
117
141
|
if (control !== 'input')
|
|
118
142
|
return;
|
|
119
143
|
const incoming = (_a = value) !== null && _a !== void 0 ? _a : '';
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
const pending = pendingValueRef.current;
|
|
145
|
+
if (pending !== null) {
|
|
146
|
+
if (incoming === pending) {
|
|
147
|
+
pendingValueRef.current = null;
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
122
150
|
}
|
|
123
|
-
if (incoming
|
|
124
|
-
|
|
151
|
+
if (incoming !== localValue) {
|
|
152
|
+
setLocalValue(incoming);
|
|
125
153
|
}
|
|
126
154
|
}, [value, control, localValue]);
|
|
127
155
|
useEffect(() => {
|
|
128
156
|
return () => {
|
|
129
|
-
|
|
130
|
-
clearTimeout(debounceRef.current);
|
|
157
|
+
clearDebounce();
|
|
131
158
|
};
|
|
132
159
|
}, []);
|
|
133
160
|
return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: [styles.filterField, styles[size], styles[variant], active ? styles.active : '']
|
|
134
161
|
.filter(Boolean)
|
|
135
162
|
.join(' '), children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: handleOperatorChange, operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, style: { width }, children: control === 'input' ? (_jsx(Input, { variant: "embedded", ...inputProps, value: localValue, onChange: e => {
|
|
136
163
|
const next = e.currentTarget.value;
|
|
137
|
-
isTypingRef.current = true;
|
|
138
164
|
setLocalValue(next);
|
|
139
165
|
scheduleEmitValue(next);
|
|
166
|
+
}, onBlur: () => {
|
|
167
|
+
flushPendingValue();
|
|
140
168
|
}, fullWidth: true, inputSize: size, placeholder: placeholder, disabled: disabled, onClear: () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
clearTimeout(debounceRef.current);
|
|
169
|
+
clearDebounce();
|
|
170
|
+
pendingValueRef.current = '';
|
|
144
171
|
setLocalValue('');
|
|
145
172
|
emit({ value: '' });
|
|
146
173
|
} })) : single ? (_jsx(Select, { options: options, selectedValue: (_b = value) !== null && _b !== void 0 ? _b : null, onChange: v => emit({ value: v }), placeholder: placeholder, size: size, variant: "inline", onClear: () => emit({ value: '' }), disabled: disabled })) : (_jsx(MultiSelect, { options: options, size: size, variant: "inline", selectedValues: (Array.isArray(value) ? value : []), onChange: nextSelectedValues => emit({ value: nextSelectedValues }), onClear: () => emit({ value: [] }), fullWidth: true, disabled: disabled, children: placeholder })) })] }));
|
|
@@ -6,7 +6,7 @@ import styles from './Pagination.module.css';
|
|
|
6
6
|
import { Button } from '../button/Button';
|
|
7
7
|
import { Menu } from '../menu/Menu';
|
|
8
8
|
import { Popover } from '../popover/Popover';
|
|
9
|
-
const DEFAULT_PAGE_SIZE_OPTIONS = [
|
|
9
|
+
const DEFAULT_PAGE_SIZE_OPTIONS = [20, 50, 100];
|
|
10
10
|
const NUMBER_LOCALE = 'da-DK';
|
|
11
11
|
export function Pagination({ itemsCount = 0, skip = 0, take = DEFAULT_PAGE_SIZE_OPTIONS[1], onPageChange, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, showFirstLast = true, showGoToPage = true, }) {
|
|
12
12
|
const popoverRef = useRef(null);
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
|
|
83
83
|
.header {
|
|
84
84
|
position: relative;
|
|
85
|
-
z-index:
|
|
85
|
+
z-index: 10;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
.headerRow,
|
|
@@ -148,6 +148,18 @@
|
|
|
148
148
|
line-height: 20px;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
.cell[data-vertical-align='top'] {
|
|
152
|
+
vertical-align: top;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.cell[data-vertical-align='middle'] {
|
|
156
|
+
vertical-align: middle;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.cell[data-vertical-align='bottom'] {
|
|
160
|
+
vertical-align: bottom;
|
|
161
|
+
}
|
|
162
|
+
|
|
151
163
|
.headerCell[data-align='right'],
|
|
152
164
|
.cell[data-align='right'] {
|
|
153
165
|
text-align: end;
|
|
@@ -368,17 +380,47 @@
|
|
|
368
380
|
}
|
|
369
381
|
|
|
370
382
|
.cellContent {
|
|
371
|
-
display:
|
|
383
|
+
display: flex;
|
|
372
384
|
inline-size: 100%;
|
|
373
385
|
min-width: 0;
|
|
374
386
|
max-inline-size: 100%;
|
|
375
387
|
}
|
|
376
388
|
|
|
389
|
+
/* horizontal alignment inside content wrapper */
|
|
390
|
+
.cellContent[data-align='left'] {
|
|
391
|
+
justify-content: flex-start;
|
|
392
|
+
text-align: left;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.cellContent[data-align='center'] {
|
|
396
|
+
justify-content: center;
|
|
397
|
+
text-align: center;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
.cellContent[data-align='right'] {
|
|
401
|
+
justify-content: flex-end;
|
|
402
|
+
text-align: right;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* vertical alignment inside content wrapper */
|
|
406
|
+
.cellContent[data-vertical-align='top'] {
|
|
407
|
+
align-items: flex-start;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.cellContent[data-vertical-align='middle'] {
|
|
411
|
+
align-items: center;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.cellContent[data-vertical-align='bottom'] {
|
|
415
|
+
align-items: flex-end;
|
|
416
|
+
}
|
|
417
|
+
|
|
377
418
|
.cellContent > * {
|
|
378
419
|
min-width: 0;
|
|
379
420
|
max-inline-size: 100%;
|
|
380
421
|
}
|
|
381
422
|
|
|
423
|
+
/* keep truncation + text alignment working */
|
|
382
424
|
.cellValueEllipsis {
|
|
383
425
|
display: block;
|
|
384
426
|
inline-size: 100%;
|
|
@@ -389,6 +431,18 @@
|
|
|
389
431
|
text-overflow: ellipsis;
|
|
390
432
|
}
|
|
391
433
|
|
|
434
|
+
.cellContent[data-align='left'] .cellValueEllipsis {
|
|
435
|
+
text-align: left;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.cellContent[data-align='center'] .cellValueEllipsis {
|
|
439
|
+
text-align: center;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.cellContent[data-align='right'] .cellValueEllipsis {
|
|
443
|
+
text-align: right;
|
|
444
|
+
}
|
|
445
|
+
|
|
392
446
|
.allowWrap .cellContent,
|
|
393
447
|
.allowWrap .cellValueEllipsis {
|
|
394
448
|
white-space: normal;
|
|
@@ -45,9 +45,10 @@ export function TableRow({ row, rowId, columns, selectedRows, hasSelection, sele
|
|
|
45
45
|
e.stopPropagation();
|
|
46
46
|
onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
|
|
47
47
|
} })) }) }) })) : null, columns.map(column => {
|
|
48
|
-
var _a;
|
|
48
|
+
var _a, _b, _c, _d;
|
|
49
49
|
const allowWrap = shouldAllowWrap(column.allowWrap, isSelected, viewMode);
|
|
50
|
+
const allowOverflow = column.allowOverflow;
|
|
50
51
|
const cellValue = getCellDisplayValue(row, column);
|
|
51
|
-
return (_jsx("td", { className: cx(styles.cell, allowWrap ? styles.allowWrap : styles.nowrap, column.divider === 'left' && styles.dividerLeft, column.divider === 'right' && styles.dividerRight), "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', "data-divider": column.divider, children: _jsx("div", { className: styles.cellContent, children: allowWrap ? cellValue : _jsx("div", { className: styles.cellValueEllipsis, children: cellValue }) }) }, column.id));
|
|
52
|
+
return (_jsx("td", { className: cx(styles.cell, allowWrap ? styles.allowWrap : styles.nowrap, column.divider === 'left' && styles.dividerLeft, column.divider === 'right' && styles.dividerRight), "data-align": (_a = column.align) !== null && _a !== void 0 ? _a : 'left', "data-vertical-align": (_b = column.verticalAlign) !== null && _b !== void 0 ? _b : 'top', "data-divider": column.divider, children: _jsx("div", { className: styles.cellContent, "data-align": (_c = column.align) !== null && _c !== void 0 ? _c : 'left', "data-vertical-align": (_d = column.verticalAlign) !== null && _d !== void 0 ? _d : 'top', children: allowWrap || allowOverflow ? (cellValue) : (_jsx("div", { className: styles.cellValueEllipsis, children: cellValue })) }) }, column.id));
|
|
52
53
|
})] }));
|
|
53
54
|
}
|
|
@@ -20,7 +20,7 @@ export function buildColumnVisibilityFromVisibleIds(defs, visibleColumnIds) {
|
|
|
20
20
|
}
|
|
21
21
|
export function mapDefsToColumnItems(defs, columnVisibility, resolvedLayout = {}) {
|
|
22
22
|
return defs.map((def, index) => {
|
|
23
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
23
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
24
24
|
const id = getColumnId(def, index);
|
|
25
25
|
const accessorKey = def.accessorKey;
|
|
26
26
|
const accessorFn = def.accessorFn;
|
|
@@ -61,6 +61,7 @@ export function mapDefsToColumnItems(defs, columnVisibility, resolvedLayout = {}
|
|
|
61
61
|
allowWrap: (_g = meta.allowWrap) !== null && _g !== void 0 ? _g : false,
|
|
62
62
|
severity: meta.severity,
|
|
63
63
|
divider: meta.divider,
|
|
64
|
+
allowOverflow: (_h = meta.allowOverflow) !== null && _h !== void 0 ? _h : false,
|
|
64
65
|
};
|
|
65
66
|
});
|
|
66
67
|
}
|
|
@@ -20,6 +20,11 @@ export interface UsePaginationProps<T> {
|
|
|
20
20
|
* Useful after filtering/sorting changes upstream.
|
|
21
21
|
*/
|
|
22
22
|
resetOnDataChange?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Optional localStorage key for persisting pagination state.
|
|
25
|
+
* Only used in uncontrolled mode.
|
|
26
|
+
*/
|
|
27
|
+
storageKey?: string;
|
|
23
28
|
}
|
|
24
29
|
export interface UsePaginationResult<T> {
|
|
25
30
|
paginatedData: T[];
|
|
@@ -30,4 +35,4 @@ export interface UsePaginationResult<T> {
|
|
|
30
35
|
page: number;
|
|
31
36
|
totalCount: number;
|
|
32
37
|
}
|
|
33
|
-
export declare function usePagination<T>({ data, skip, take, state, onStateChange, resetOnDataChange, }: UsePaginationProps<T>): UsePaginationResult<T>;
|
|
38
|
+
export declare function usePagination<T>({ data, skip, take, state, onStateChange, resetOnDataChange, storageKey, }: UsePaginationProps<T>): UsePaginationResult<T>;
|
|
@@ -3,18 +3,67 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
function clamp(n, min, max) {
|
|
4
4
|
return Math.max(min, Math.min(max, n));
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
function normalizePaginationState(next) {
|
|
7
|
+
return {
|
|
8
|
+
skip: Math.max(0, next.skip),
|
|
9
|
+
take: Math.max(1, next.take),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function safeParsePaginationState(raw) {
|
|
13
|
+
if (!raw)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (typeof parsed !== 'object' ||
|
|
18
|
+
parsed == null ||
|
|
19
|
+
typeof parsed.skip !== 'number' ||
|
|
20
|
+
typeof parsed.take !== 'number') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return normalizePaginationState({
|
|
24
|
+
skip: parsed.skip,
|
|
25
|
+
take: parsed.take,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function usePagination({ data = [], skip = 0, take = 10, state, onStateChange, resetOnDataChange = false, storageKey, }) {
|
|
7
33
|
const isControlled = state != null;
|
|
8
|
-
const [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
34
|
+
const [hydrated, setHydrated] = useState(() => !storageKey || isControlled);
|
|
35
|
+
const [uncontrolled, setUncontrolled] = useState(() => {
|
|
36
|
+
var _a;
|
|
37
|
+
const initial = normalizePaginationState({
|
|
38
|
+
skip,
|
|
39
|
+
take,
|
|
40
|
+
});
|
|
41
|
+
if (typeof window === 'undefined' || !storageKey || isControlled) {
|
|
42
|
+
return initial;
|
|
43
|
+
}
|
|
44
|
+
return (_a = safeParsePaginationState(window.localStorage.getItem(storageKey))) !== null && _a !== void 0 ? _a : initial;
|
|
45
|
+
});
|
|
46
|
+
// Hydrate from localStorage when key changes.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (typeof window === 'undefined')
|
|
49
|
+
return;
|
|
50
|
+
if (isControlled || !storageKey) {
|
|
51
|
+
setHydrated(true);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const fallback = normalizePaginationState({ skip, take });
|
|
55
|
+
const stored = safeParsePaginationState(window.localStorage.getItem(storageKey));
|
|
56
|
+
setUncontrolled(stored !== null && stored !== void 0 ? stored : fallback);
|
|
57
|
+
setHydrated(true);
|
|
58
|
+
}, [isControlled, storageKey, skip, take]);
|
|
12
59
|
// Keep initial props in sync ONLY for uncontrolled if props change.
|
|
13
|
-
//
|
|
60
|
+
// Do not overwrite localStorage-hydrated state when storageKey is provided.
|
|
14
61
|
const didInitRef = useRef(false);
|
|
15
62
|
useEffect(() => {
|
|
16
63
|
if (isControlled)
|
|
17
64
|
return;
|
|
65
|
+
if (storageKey)
|
|
66
|
+
return;
|
|
18
67
|
if (!didInitRef.current) {
|
|
19
68
|
didInitRef.current = true;
|
|
20
69
|
return;
|
|
@@ -23,21 +72,19 @@ export function usePagination({ data = [], skip = 0, take = 10, state, onStateCh
|
|
|
23
72
|
skip: prev.skip,
|
|
24
73
|
take: Math.max(1, take),
|
|
25
74
|
}));
|
|
26
|
-
}, [isControlled, take]);
|
|
75
|
+
}, [isControlled, storageKey, take]);
|
|
27
76
|
const paginationState = (isControlled ? state : uncontrolled);
|
|
28
77
|
const totalCount = data.length;
|
|
29
78
|
const safeTake = Math.max(1, paginationState.take);
|
|
30
79
|
const maxSkip = Math.max(0, totalCount === 0 ? 0 : Math.floor((totalCount - 1) / safeTake) * safeTake);
|
|
31
80
|
const safeSkip = clamp(Math.max(0, paginationState.skip), 0, maxSkip);
|
|
32
81
|
const setPagination = useCallback((next) => {
|
|
33
|
-
const normalized =
|
|
34
|
-
|
|
35
|
-
skip: Math.max(0, next.skip),
|
|
36
|
-
};
|
|
37
|
-
if (isControlled)
|
|
82
|
+
const normalized = normalizePaginationState(next);
|
|
83
|
+
if (isControlled) {
|
|
38
84
|
onStateChange === null || onStateChange === void 0 ? void 0 : onStateChange(normalized);
|
|
39
|
-
|
|
40
|
-
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
setUncontrolled(normalized);
|
|
41
88
|
}, [isControlled, onStateChange]);
|
|
42
89
|
const onPageChange = useCallback((pageEvent) => {
|
|
43
90
|
const nextTake = Math.max(1, pageEvent.take);
|
|
@@ -51,11 +98,12 @@ export function usePagination({ data = [], skip = 0, take = 10, state, onStateCh
|
|
|
51
98
|
useEffect(() => {
|
|
52
99
|
if (!resetOnDataChange)
|
|
53
100
|
return;
|
|
101
|
+
if (!hydrated)
|
|
102
|
+
return;
|
|
54
103
|
resetPage();
|
|
55
104
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
|
-
}, [resetOnDataChange, data]);
|
|
105
|
+
}, [resetOnDataChange, data, hydrated]);
|
|
57
106
|
const paginatedData = useMemo(() => {
|
|
58
|
-
// Use safeSkip to avoid slicing past end if data shrinks.
|
|
59
107
|
return data.slice(safeSkip, safeSkip + safeTake);
|
|
60
108
|
}, [data, safeSkip, safeTake]);
|
|
61
109
|
const page = useMemo(() => Math.floor(safeSkip / safeTake) + 1, [safeSkip, safeTake]);
|
|
@@ -67,6 +115,19 @@ export function usePagination({ data = [], skip = 0, take = 10, state, onStateCh
|
|
|
67
115
|
setUncontrolled(prev => ({ ...prev, skip: safeSkip }));
|
|
68
116
|
}
|
|
69
117
|
}, [isControlled, safeSkip, paginationState.skip]);
|
|
118
|
+
// Persist uncontrolled state to localStorage when enabled.
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (typeof window === 'undefined')
|
|
121
|
+
return;
|
|
122
|
+
if (isControlled || !storageKey)
|
|
123
|
+
return;
|
|
124
|
+
if (!hydrated)
|
|
125
|
+
return;
|
|
126
|
+
window.localStorage.setItem(storageKey, JSON.stringify({
|
|
127
|
+
skip: paginationState.skip,
|
|
128
|
+
take: paginationState.take,
|
|
129
|
+
}));
|
|
130
|
+
}, [hydrated, isControlled, paginationState.skip, paginationState.take, storageKey]);
|
|
70
131
|
return {
|
|
71
132
|
paginatedData,
|
|
72
133
|
paginationState: { skip: safeSkip, take: safeTake },
|
package/package.json
CHANGED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from 'react';
|
|
2
|
-
type Props = {
|
|
3
|
-
align?: 'left' | 'right' | 'center';
|
|
4
|
-
divider?: 'left' | 'right';
|
|
5
|
-
allowWrap?: boolean;
|
|
6
|
-
children: ReactNode;
|
|
7
|
-
};
|
|
8
|
-
export declare function TableCell({ align, divider, allowWrap, children }: Props): ReactNode;
|
|
9
|
-
export {};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { cx } from '../table.classes';
|
|
3
|
-
import styles from '../Table.module.css';
|
|
4
|
-
export function TableCell({ align = 'left', divider, allowWrap, children }) {
|
|
5
|
-
const dividerClass = divider === 'left' ? styles.dividerLeft : divider === 'right' ? styles.dividerRight : '';
|
|
6
|
-
return (_jsx("div", { className: cx(styles.cell, allowWrap ? styles.allowWrap : styles.nowrap, dividerClass), role: "cell", "data-align": align, "data-divider": divider, children: _jsx("div", { className: styles.cellContent, children: allowWrap ? children : _jsx("div", { className: styles.cellValueEllipsis, children: children }) }) }));
|
|
7
|
-
}
|