@dbcdk/react-components 0.0.33 → 0.0.35
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/button/Button.module.css +1 -0
- package/dist/components/card/components/CardMeta.js +2 -2
- package/dist/components/card/components/CardMeta.module.css +3 -3
- package/dist/components/datetime-picker/DateTimePicker.d.ts +1 -0
- package/dist/components/datetime-picker/DateTimePicker.js +2 -2
- package/dist/components/forms/checkbox/Checkbox.module.css +8 -2
- package/dist/components/forms/multi-select/MultiSelect.d.ts +4 -1
- package/dist/components/forms/multi-select/MultiSelect.js +69 -23
- package/dist/components/menu/Menu.module.css +10 -2
- package/dist/components/pagination/Pagination.js +7 -5
- package/dist/components/table/Table.d.ts +2 -1
- package/dist/components/table/Table.js +4 -11
- package/dist/components/table/Table.module.css +110 -185
- package/dist/components/table/Table.types.d.ts +4 -2
- package/dist/components/table/TanstackTable.js +42 -33
- package/dist/components/table/components/TableBody.d.ts +2 -3
- package/dist/components/table/components/TableBody.js +3 -3
- package/dist/components/table/components/TableHeader.d.ts +2 -3
- package/dist/components/table/components/TableHeader.js +2 -2
- package/dist/components/table/components/TableHeaderCell.js +2 -2
- package/dist/components/table/components/TableLoadingBody.d.ts +2 -3
- package/dist/components/table/components/TableLoadingBody.js +3 -3
- package/dist/components/table/components/TableRow.d.ts +1 -3
- package/dist/components/table/components/TableRow.js +5 -6
- package/dist/components/table/components/TableSelectionCell.js +3 -2
- package/dist/components/table/components/column-resizer/ColumnResizer.module.css +4 -5
- package/dist/components/table/table.utils.d.ts +1 -4
- package/dist/components/table/table.utils.js +1 -8
- package/dist/components/table/tanstackTable.utils.d.ts +10 -6
- package/dist/components/table/tanstackTable.utils.js +79 -83
- package/dist/src/styles/styles.css +1 -5
- package/dist/styles/styles.css +1 -5
- package/package.json +2 -1
|
@@ -15,10 +15,10 @@ export function CardMeta({ columns = 2, className, children, ...rest }) {
|
|
|
15
15
|
const colsClass = getColsClass(columns);
|
|
16
16
|
return (_jsx("dl", { ...rest, className: [styles.grid, colsClass, className].filter(Boolean).join(' '), children: children }));
|
|
17
17
|
}
|
|
18
|
-
export function CardMetaRow({ label, value, className, nowrapValue, labelWidth, boldValue =
|
|
18
|
+
export function CardMetaRow({ label, value, className, nowrapValue, labelWidth, boldValue = false, }) {
|
|
19
19
|
return (_jsxs("div", { className: [styles.row, className].filter(Boolean).join(' '), style: labelWidth ? { ['--label-width']: labelWidth } : undefined, children: [_jsx("dt", { className: styles.label, children: label }), _jsx("dd", { className: [
|
|
20
20
|
styles.value,
|
|
21
|
-
!boldValue ? styles.valueRegular :
|
|
21
|
+
!boldValue ? styles.valueRegular : styles.valueBold,
|
|
22
22
|
nowrapValue ? styles.nowrap : '',
|
|
23
23
|
]
|
|
24
24
|
.filter(Boolean)
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
margin: 0;
|
|
42
42
|
font-size: var(--font-size-sm);
|
|
43
43
|
color: var(--color-fg-default);
|
|
44
|
-
font-weight: var(--font-weight-
|
|
44
|
+
font-weight: var(--font-weight-default);
|
|
45
45
|
min-inline-size: 0;
|
|
46
46
|
word-break: break-word;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
.
|
|
50
|
-
font-weight: var(--font-weight-
|
|
49
|
+
.valueBold {
|
|
50
|
+
font-weight: var(--font-weight-medium);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
.nowrap {
|
|
@@ -52,7 +52,7 @@ function defaultFormatRange(s, e, opts) {
|
|
|
52
52
|
return '';
|
|
53
53
|
}
|
|
54
54
|
const cx = (...classes) => classes.filter(Boolean).join(' ');
|
|
55
|
-
export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate, formatRange = defaultFormatRange, onOpenChange, }, _ref) {
|
|
55
|
+
export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'single', value, onChange, enableTime = false, showCalendarIcon = true, timeStep = 15, min, max, locale = typeof navigator !== 'undefined' ? navigator.language : 'da-DK', weekStartsOn = 1, presets, inputProps, formatDate = defaultFormatDate, formatRange = defaultFormatRange, onOpenChange, }, _ref) {
|
|
56
56
|
void formatDate;
|
|
57
57
|
void formatRange;
|
|
58
58
|
const popRef = useRef(null);
|
|
@@ -360,7 +360,7 @@ export const DateTimePicker = forwardRef(function DateTimePicker({ mode = 'singl
|
|
|
360
360
|
e.preventDefault();
|
|
361
361
|
commitTypedValue();
|
|
362
362
|
}
|
|
363
|
-
}, icon: _jsx(Calendar, { size: 16 }), onClear: formatted || text ? clear : undefined, "aria-haspopup": "dialog", "aria-expanded": ((_b = popRef.current) === null || _b === void 0 ? void 0 : _b.isOpen()) ? true : false, disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, error: inputProps === null || inputProps === void 0 ? void 0 : inputProps.error }) }));
|
|
363
|
+
}, icon: showCalendarIcon ? _jsx(Calendar, { size: 16 }) : undefined, onClear: formatted || text ? clear : undefined, "aria-haspopup": "dialog", "aria-expanded": ((_b = popRef.current) === null || _b === void 0 ? void 0 : _b.isOpen()) ? true : false, disabled: inputProps === null || inputProps === void 0 ? void 0 : inputProps.disabled, error: inputProps === null || inputProps === void 0 ? void 0 : inputProps.error }) }));
|
|
364
364
|
}, viewportPadding: 8, children: _jsxs("div", { className: cx(styles.panel, !!(presets === null || presets === void 0 ? void 0 : presets.length) && styles.panelWithPresets), children: [(presets === null || presets === void 0 ? void 0 : presets.length) ? (_jsxs("div", { className: styles.presetsCol, children: [_jsx("div", { className: styles.presetsLabel, children: "Forvalg" }), _jsxs("div", { className: styles.presetsList, children: [presets.map(p => (_jsx(Button, { variant: "outlined", size: "sm", onClick: () => {
|
|
365
365
|
var _a;
|
|
366
366
|
const r = p.getRange();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
.container {
|
|
2
2
|
display: inline-flex;
|
|
3
|
-
align-items:
|
|
3
|
+
align-items: flex-start;
|
|
4
4
|
gap: var(--spacing-sm);
|
|
5
5
|
vertical-align: middle;
|
|
6
|
-
line-height:
|
|
6
|
+
line-height: var(--line-height-normal);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
.checkbox {
|
|
@@ -31,6 +31,12 @@
|
|
|
31
31
|
.label {
|
|
32
32
|
display: block;
|
|
33
33
|
font-size: var(--font-size-sm);
|
|
34
|
+
min-width: 0;
|
|
35
|
+
flex: 1 1 auto;
|
|
36
|
+
line-height: var(--line-height-normal);
|
|
37
|
+
white-space: normal;
|
|
38
|
+
overflow-wrap: anywhere;
|
|
39
|
+
word-break: break-word;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
.checkbox:hover {
|
|
@@ -17,6 +17,9 @@ interface MultiSelectProps<T> {
|
|
|
17
17
|
dataCy?: string;
|
|
18
18
|
fullWidth?: boolean;
|
|
19
19
|
disabled?: boolean;
|
|
20
|
+
searchable?: boolean;
|
|
21
|
+
searchPlaceholder?: string;
|
|
22
|
+
emptyMessage?: string;
|
|
20
23
|
}
|
|
21
|
-
export declare function MultiSelect<T extends string | number>({ options, selectedValues, onChange, placeholder, children, variant, size, onClear, dataCy, fullWidth, disabled, }: MultiSelectProps<T>): ReactNode;
|
|
24
|
+
export declare function MultiSelect<T extends string | number>({ options, selectedValues, onChange, placeholder, children, variant, size, onClear, dataCy, fullWidth, disabled, searchable, searchPlaceholder, emptyMessage, }: MultiSelectProps<T>): ReactNode;
|
|
22
25
|
export {};
|
|
@@ -1,28 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Check, Square } from 'lucide-react';
|
|
2
|
+
import { Check, Search, Square } from 'lucide-react';
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { Button } from '../../../components/button/Button';
|
|
5
5
|
import { Chip } from '../../../components/chip/Chip';
|
|
6
6
|
import { ClearButton } from '../../../components/clear-button/ClearButton';
|
|
7
|
+
import { Input } from '../../../components/forms/input/Input';
|
|
7
8
|
import { Menu } from '../../../components/menu/Menu';
|
|
8
9
|
import { Popover } from '../../../components/popover/Popover';
|
|
9
|
-
export function MultiSelect({ options, selectedValues = [], onChange, placeholder = 'Vælg', children, variant = 'outlined', size = 'md', onClear, dataCy, fullWidth = false, disabled = false, }) {
|
|
10
|
+
export function MultiSelect({ options, selectedValues = [], onChange, placeholder = 'Vælg', children, variant = 'outlined', size = 'md', onClear, dataCy, fullWidth = false, disabled = false, searchable = false, searchPlaceholder = 'Søg', emptyMessage = 'Ingen resultater', }) {
|
|
10
11
|
const selectedSet = useMemo(() => new Set(selectedValues), [selectedValues]);
|
|
11
12
|
const popoverRef = useRef(null);
|
|
12
13
|
const optionRefs = useRef([]);
|
|
14
|
+
const searchInputRef = useRef(null);
|
|
13
15
|
const typeaheadRef = useRef('');
|
|
14
16
|
const typeaheadTimeoutRef = useRef(null);
|
|
15
17
|
const [open, setOpen] = useState(false);
|
|
18
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
19
|
+
const filteredOptions = useMemo(() => {
|
|
20
|
+
const normalizedQuery = searchQuery.trim().toLocaleLowerCase();
|
|
21
|
+
if (!normalizedQuery)
|
|
22
|
+
return options;
|
|
23
|
+
return options.filter(option => option.label.toLocaleLowerCase().includes(normalizedQuery));
|
|
24
|
+
}, [options, searchQuery]);
|
|
16
25
|
const [activeIndex, setActiveIndex] = useState(() => {
|
|
17
|
-
const selectedIndex =
|
|
26
|
+
const selectedIndex = filteredOptions.findIndex(option => selectedSet.has(option.value));
|
|
18
27
|
return selectedIndex >= 0 ? selectedIndex : 0;
|
|
19
28
|
});
|
|
20
29
|
useEffect(() => {
|
|
21
30
|
var _a;
|
|
22
31
|
if (!open)
|
|
23
32
|
return;
|
|
33
|
+
if (searchable && document.activeElement === searchInputRef.current)
|
|
34
|
+
return;
|
|
24
35
|
(_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
25
|
-
}, [activeIndex, open]);
|
|
36
|
+
}, [activeIndex, open, searchable]);
|
|
26
37
|
useEffect(() => {
|
|
27
38
|
return () => {
|
|
28
39
|
if (typeaheadTimeoutRef.current)
|
|
@@ -30,9 +41,22 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
30
41
|
};
|
|
31
42
|
}, []);
|
|
32
43
|
const resetActiveIndex = () => {
|
|
33
|
-
const selectedIndex =
|
|
44
|
+
const selectedIndex = filteredOptions.findIndex(option => selectedSet.has(option.value));
|
|
34
45
|
setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
|
|
35
46
|
};
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
setActiveIndex(current => {
|
|
49
|
+
if (filteredOptions.length === 0)
|
|
50
|
+
return 0;
|
|
51
|
+
return Math.min(current, filteredOptions.length - 1);
|
|
52
|
+
});
|
|
53
|
+
}, [filteredOptions]);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
var _a;
|
|
56
|
+
if (!open || !searchable)
|
|
57
|
+
return;
|
|
58
|
+
(_a = searchInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
59
|
+
}, [open, searchable]);
|
|
36
60
|
const toggleValue = (value) => {
|
|
37
61
|
if (disabled)
|
|
38
62
|
return;
|
|
@@ -52,14 +76,14 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
52
76
|
};
|
|
53
77
|
const findTypeaheadMatch = (query, startIndex) => {
|
|
54
78
|
var _a, _b;
|
|
55
|
-
if (!query ||
|
|
79
|
+
if (!query || filteredOptions.length === 0)
|
|
56
80
|
return -1;
|
|
57
81
|
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
58
82
|
if (!normalizedQuery)
|
|
59
83
|
return -1;
|
|
60
|
-
for (let step = 1; step <=
|
|
61
|
-
const index = (startIndex + step +
|
|
62
|
-
const label = (_b = (_a =
|
|
84
|
+
for (let step = 1; step <= filteredOptions.length; step += 1) {
|
|
85
|
+
const index = (startIndex + step + filteredOptions.length) % filteredOptions.length;
|
|
86
|
+
const label = (_b = (_a = filteredOptions[index]) === null || _a === void 0 ? void 0 : _a.label) === null || _b === void 0 ? void 0 : _b.trim().toLocaleLowerCase();
|
|
63
87
|
if (label === null || label === void 0 ? void 0 : label.startsWith(normalizedQuery))
|
|
64
88
|
return index;
|
|
65
89
|
}
|
|
@@ -88,7 +112,21 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
88
112
|
setOpen(true);
|
|
89
113
|
};
|
|
90
114
|
const handleKeyDown = (e) => {
|
|
91
|
-
var _a;
|
|
115
|
+
var _a, _b, _c, _d;
|
|
116
|
+
if (searchable && e.target === searchInputRef.current) {
|
|
117
|
+
switch (e.key) {
|
|
118
|
+
case 'ArrowDown':
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
if (filteredOptions.length > 0)
|
|
121
|
+
(_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
122
|
+
return;
|
|
123
|
+
case 'Escape':
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
(_b = popoverRef.current) === null || _b === void 0 ? void 0 : _b.close();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
92
130
|
if (e.key.length === 1 && !e.altKey && !e.ctrlKey && !e.metaKey && !/\s/.test(e.key)) {
|
|
93
131
|
e.preventDefault();
|
|
94
132
|
handleTypeahead(e.key);
|
|
@@ -101,7 +139,7 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
101
139
|
setOpen(true);
|
|
102
140
|
return;
|
|
103
141
|
}
|
|
104
|
-
setActiveIndex(i => Math.min(i + 1,
|
|
142
|
+
setActiveIndex(i => Math.min(i + 1, filteredOptions.length - 1));
|
|
105
143
|
break;
|
|
106
144
|
case 'ArrowUp':
|
|
107
145
|
e.preventDefault();
|
|
@@ -109,6 +147,12 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
109
147
|
setOpen(true);
|
|
110
148
|
return;
|
|
111
149
|
}
|
|
150
|
+
if (searchable && optionRefs.current[activeIndex] === document.activeElement) {
|
|
151
|
+
if (activeIndex === 0) {
|
|
152
|
+
(_c = searchInputRef.current) === null || _c === void 0 ? void 0 : _c.focus();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
112
156
|
setActiveIndex(i => Math.max(i - 1, 0));
|
|
113
157
|
break;
|
|
114
158
|
case 'Home':
|
|
@@ -121,7 +165,7 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
121
165
|
if (!open)
|
|
122
166
|
return;
|
|
123
167
|
e.preventDefault();
|
|
124
|
-
setActiveIndex(
|
|
168
|
+
setActiveIndex(filteredOptions.length - 1);
|
|
125
169
|
break;
|
|
126
170
|
case 'Enter':
|
|
127
171
|
case ' ':
|
|
@@ -131,14 +175,14 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
131
175
|
setOpen(true);
|
|
132
176
|
return;
|
|
133
177
|
}
|
|
134
|
-
if (
|
|
135
|
-
toggleValue(
|
|
178
|
+
if (filteredOptions[activeIndex])
|
|
179
|
+
toggleValue(filteredOptions[activeIndex].value);
|
|
136
180
|
break;
|
|
137
181
|
case 'Escape':
|
|
138
182
|
if (!open)
|
|
139
183
|
return;
|
|
140
184
|
e.preventDefault();
|
|
141
|
-
(
|
|
185
|
+
(_d = popoverRef.current) === null || _d === void 0 ? void 0 : _d.close();
|
|
142
186
|
break;
|
|
143
187
|
}
|
|
144
188
|
};
|
|
@@ -146,12 +190,14 @@ export function MultiSelect({ options, selectedValues = [], onChange, placeholde
|
|
|
146
190
|
setOpen(next);
|
|
147
191
|
if (next)
|
|
148
192
|
resetActiveIndex();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
193
|
+
else
|
|
194
|
+
setSearchQuery('');
|
|
195
|
+
}, dataCy: dataCy, fullWidth: fullWidth, autoFocusContent: false, returnFocus: true, trigger: (onClick, icon, isOpen) => (_jsx(Button, { variant: variant, onClick: onClick, onKeyDown: handleKeyDown, size: size, fullWidth: fullWidth, disabled: disabled, "aria-haspopup": "menu", "aria-expanded": !!isOpen, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx("span", { children: children !== null && children !== void 0 ? children : placeholder }), selectedValues.length > 0 ? (_jsx(Chip, { size: "sm", children: `${selectedValues.length}` })) : null] }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [onClear && selectedValues.length > 0 && _jsx(ClearButton, { onClick: onClear }), icon] })] }) })), children: _jsxs(Menu, { onKeyDown: handleKeyDown, children: [searchable ? (_jsx(Menu.Item, { children: _jsx(Input, { ref: searchInputRef, value: searchQuery, onChange: e => setSearchQuery(e.target.value), onKeyDown: handleKeyDown, placeholder: searchPlaceholder, icon: _jsx(Search, { size: 16 }), fullWidth: true }) })) : null, filteredOptions.map((option, index) => (_jsx(Menu.Item, { active: index === activeIndex, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", role: "menuitemcheckbox", "aria-checked": selectedSet.has(option.value), tabIndex: index === activeIndex ? 0 : -1, disabled: disabled, onFocus: () => setActiveIndex(index), onClick: () => {
|
|
196
|
+
toggleValue(option.value);
|
|
197
|
+
}, children: [_jsx("span", { style: {
|
|
198
|
+
pointerEvents: 'none',
|
|
199
|
+
display: 'flex',
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
color: !selectedSet.has(option.value) ? 'var(--color-border-strong)' : 'inherit',
|
|
202
|
+
}, children: selectedSet.has(option.value) ? _jsx(Check, {}) : _jsx(Square, {}) }), _jsx("span", { children: option.label })] }) }, option.value))), filteredOptions.length === 0 ? _jsx(Menu.Item, { disabled: true, children: emptyMessage }) : null] }) }));
|
|
157
203
|
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
/* Applied to actual interactive elements (button/a/custom that forwards className) */
|
|
22
22
|
.interactive {
|
|
23
23
|
display: flex;
|
|
24
|
-
align-items:
|
|
24
|
+
align-items: flex-start;
|
|
25
25
|
justify-content: flex-start;
|
|
26
26
|
gap: var(--spacing-xs);
|
|
27
27
|
inline-size: 100%;
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
font-family: var(--font-family);
|
|
39
39
|
font-size: var(--font-size-sm);
|
|
40
|
+
line-height: var(--line-height-normal);
|
|
40
41
|
color: var(--color-fg-default);
|
|
41
42
|
cursor: pointer;
|
|
42
43
|
|
|
@@ -47,6 +48,13 @@
|
|
|
47
48
|
color var(--transition-fast) var(--ease-standard);
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
.interactive > :last-child {
|
|
52
|
+
min-width: 0;
|
|
53
|
+
white-space: normal;
|
|
54
|
+
overflow-wrap: anywhere;
|
|
55
|
+
word-break: break-word;
|
|
56
|
+
}
|
|
57
|
+
|
|
50
58
|
/*
|
|
51
59
|
Applied to the immediate child of <li> even if it's NOT an interactive element (e.g. a <div>)
|
|
52
60
|
so that menu row styling still works for components that render a wrapper.
|
|
@@ -60,7 +68,7 @@
|
|
|
60
68
|
/* NEW: make wrapper-children (Checkbox/Radio) look/space like menu rows */
|
|
61
69
|
.row > .interactiveChild {
|
|
62
70
|
display: flex;
|
|
63
|
-
align-items:
|
|
71
|
+
align-items: flex-start;
|
|
64
72
|
inline-size: 100%;
|
|
65
73
|
padding-block: var(--spacing-xxs);
|
|
66
74
|
padding-inline: var(--spacing-md);
|
|
@@ -7,9 +7,11 @@ import { Button } from '../button/Button';
|
|
|
7
7
|
import { Menu } from '../menu/Menu';
|
|
8
8
|
import { Popover } from '../popover/Popover';
|
|
9
9
|
const DEFAULT_PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
|
|
10
|
+
const NUMBER_LOCALE = 'da-DK';
|
|
10
11
|
export function Pagination({ itemsCount = 0, skip = 0, take = DEFAULT_PAGE_SIZE_OPTIONS[1], onPageChange, pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, showFirstLast = true, showGoToPage = true, }) {
|
|
11
12
|
const popoverRef = useRef(null);
|
|
12
13
|
const pageSizeRef = useRef(null);
|
|
14
|
+
const formatNumber = useCallback((value) => value.toLocaleString(NUMBER_LOCALE), []);
|
|
13
15
|
const totalPages = useMemo(() => Math.max(1, Math.ceil(itemsCount / Math.max(1, take))), [itemsCount, take]);
|
|
14
16
|
const currentPage = useMemo(() => {
|
|
15
17
|
const p = Math.floor(skip / Math.max(1, take)) + 1;
|
|
@@ -38,17 +40,17 @@ export function Pagination({ itemsCount = 0, skip = 0, take = DEFAULT_PAGE_SIZE_
|
|
|
38
40
|
return '0 af 0';
|
|
39
41
|
const first = skip + 1;
|
|
40
42
|
const last = Math.min(skip + take, itemsCount);
|
|
41
|
-
return `${first
|
|
42
|
-
}, [itemsCount, skip, take]);
|
|
43
|
+
return `${formatNumber(first)}\u2013${formatNumber(last)} af ${formatNumber(itemsCount)}`;
|
|
44
|
+
}, [formatNumber, itemsCount, skip, take]);
|
|
43
45
|
const pages = useMemo(() => Array.from({ length: totalPages }, (_, i) => i + 1), [totalPages]);
|
|
44
46
|
const handlePageSizeChange = useCallback((size) => emit(1, size), [emit]);
|
|
45
|
-
return (_jsxs("div", { className: styles.container, children: [showFirstLast && (_jsx(Button, { size: "sm", icon: _jsx(ChevronsLeft, {}), disabled: !canPrev, onClick: firstPage, "aria-label": "First page" })), _jsx(Button, { size: "sm", icon: _jsx(ArrowLeft, {}), disabled: !canPrev, onClick: prevPage, "aria-label": "Previous page" }), _jsx("div", { className: styles.range, "aria-live": "polite", children: rangeLabel }), _jsx(Button, { size: "sm", icon: _jsx(ArrowRight, {}), disabled: !canNext, onClick: nextPage, "aria-label": "Next page" }), showFirstLast && (_jsx(Button, { size: "sm", icon: _jsx(ChevronsRight, {}), disabled: !canNext, onClick: lastPage, "aria-label": "Last page" })), showGoToPage && (_jsx(Popover, { ref: popoverRef, trigger: open => (_jsxs(Button, { size: "sm", variant: "outlined", onClick: open, children: ["Side ", currentPage, "/", totalPages, " ", _jsx(ChevronDown, { size: 16 })] })), children: _jsx(Menu, { children: pages === null || pages === void 0 ? void 0 : pages.map(page => (_jsx(Menu.Item, { active: page === currentPage, children: _jsx("button", { onClick: () => {
|
|
47
|
+
return (_jsxs("div", { className: styles.container, children: [showFirstLast && (_jsx(Button, { size: "sm", icon: _jsx(ChevronsLeft, {}), disabled: !canPrev, onClick: firstPage, "aria-label": "First page" })), _jsx(Button, { size: "sm", icon: _jsx(ArrowLeft, {}), disabled: !canPrev, onClick: prevPage, "aria-label": "Previous page" }), _jsx("div", { className: styles.range, "aria-live": "polite", children: rangeLabel }), _jsx(Button, { size: "sm", icon: _jsx(ArrowRight, {}), disabled: !canNext, onClick: nextPage, "aria-label": "Next page" }), showFirstLast && (_jsx(Button, { size: "sm", icon: _jsx(ChevronsRight, {}), disabled: !canNext, onClick: lastPage, "aria-label": "Last page" })), showGoToPage && (_jsx(Popover, { ref: popoverRef, trigger: open => (_jsxs(Button, { size: "sm", variant: "outlined", onClick: open, children: ["Side ", formatNumber(currentPage), "/", formatNumber(totalPages), " ", _jsx(ChevronDown, { size: 16 })] })), children: _jsx(Menu, { children: pages === null || pages === void 0 ? void 0 : pages.map(page => (_jsx(Menu.Item, { active: page === currentPage, children: _jsx("button", { onClick: () => {
|
|
46
48
|
var _a;
|
|
47
49
|
goToPage(page);
|
|
48
50
|
(_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
49
|
-
}, children: page }) }, page))) }) })), _jsx(Popover, { ref: pageSizeRef, trigger: open => (_jsxs(Button, { size: "sm", variant: "outlined", onClick: open, children: ["Vis ", take, " ", _jsx(ChevronDown, { size: 16 })] })), children: _jsx(Menu, { children: pageSizeOptions === null || pageSizeOptions === void 0 ? void 0 : pageSizeOptions.map(size => (_jsx(Menu.Item, { active: size === take, children: _jsx("button", { onClick: () => {
|
|
51
|
+
}, children: formatNumber(page) }) }, page))) }) })), _jsx(Popover, { ref: pageSizeRef, trigger: open => (_jsxs(Button, { size: "sm", variant: "outlined", onClick: open, children: ["Vis ", formatNumber(take), " ", _jsx(ChevronDown, { size: 16 })] })), children: _jsx(Menu, { children: pageSizeOptions === null || pageSizeOptions === void 0 ? void 0 : pageSizeOptions.map(size => (_jsx(Menu.Item, { active: size === take, children: _jsx("button", { onClick: () => {
|
|
50
52
|
var _a;
|
|
51
53
|
handlePageSizeChange(size);
|
|
52
54
|
(_a = pageSizeRef.current) === null || _a === void 0 ? void 0 : _a.close();
|
|
53
|
-
}, children: size }) }, size))) }) })] }));
|
|
55
|
+
}, children: formatNumber(size) }) }, size))) }) })] }));
|
|
54
56
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import type { TableProps } from './Table.types';
|
|
3
|
-
export declare function Table<T extends Record<string, any>>({ data, dataKey, columns, selectedRows, selectionMode, allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant, size, viewMode, striped, fillViewport,
|
|
3
|
+
export declare function Table<T extends Record<string, any>>({ data, dataKey, columns, selectedRows, selectionMode, allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant, size, viewMode, striped, fillViewport, tableWidth, tableRootRef, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement, showFirstLast, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }: TableProps<T>): JSX.Element;
|
|
4
|
+
export type { ColumnItem } from './Table.types';
|
|
@@ -8,25 +8,18 @@ import { TableHeader } from './components/TableHeader';
|
|
|
8
8
|
import { TableLoadingBody } from './components/TableLoadingBody';
|
|
9
9
|
import { cx } from './table.classes';
|
|
10
10
|
import styles from './Table.module.css';
|
|
11
|
-
import {
|
|
12
|
-
export function Table({ data, dataKey, columns, selectedRows, selectionMode = 'single', allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant = 'primary', size = 'md', viewMode, striped, fillViewport = false,
|
|
11
|
+
import { getVisibleColumns, SELECTION_COLUMN_PX } from './table.utils';
|
|
12
|
+
export function Table({ data, dataKey, columns, selectedRows, selectionMode = 'single', allRowsSelected, sortById, sortDirection, loading, emptyConfig, variant = 'primary', size = 'md', viewMode, striped, fillViewport = false, tableWidth, tableRootRef, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement = 'bottom', showFirstLast = false, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }) {
|
|
13
13
|
const visibleColumns = useMemo(() => getVisibleColumns(columns), [columns]);
|
|
14
14
|
const selectionInputName = useId();
|
|
15
15
|
const hasSelection = Boolean(selectedRows && onRowSelect);
|
|
16
|
-
const template = useMemo(() => {
|
|
17
|
-
return (gridTemplateColumns !== null && gridTemplateColumns !== void 0 ? gridTemplateColumns : buildDefaultGridTemplate({
|
|
18
|
-
hasSelection,
|
|
19
|
-
colCount: visibleColumns.length,
|
|
20
|
-
}));
|
|
21
|
-
}, [gridTemplateColumns, hasSelection, visibleColumns.length]);
|
|
22
|
-
const gridStyle = useMemo(() => ({ ['--grid-template']: template }), [template]);
|
|
23
16
|
const handlePageChange = useCallback((e) => {
|
|
24
17
|
onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e);
|
|
25
18
|
}, [onPageChange]);
|
|
26
|
-
const bodyContent = loading && !data.length ? (_jsx(TableLoadingBody, { rows: take !== null && take !== void 0 ? take : 5, columns: visibleColumns, hasSelection: hasSelection
|
|
19
|
+
const bodyContent = loading && !data.length ? (_jsx(TableLoadingBody, { rows: take !== null && take !== void 0 ? take : 5, columns: visibleColumns, hasSelection: hasSelection })) : (_jsx(TableBody, { data: data, dataKey: dataKey, columns: visibleColumns, striped: striped, selectedRows: selectedRows, hasSelection: hasSelection, selectionMode: selectionMode, selectionInputName: selectionInputName, viewMode: viewMode, getRowSeverity: getRowSeverity, onRowClick: onRowClick, onRowMouseEnter: onRowMouseEnter, onRowSelect: onRowSelect }));
|
|
27
20
|
const paginationEl = onPageChange && data.length > 0 ? (_jsx("div", { className: cx(styles.paginationSlot, paginationPlacement === 'top' && styles.paginationSlotTop), children: _jsx(Pagination, { itemsCount: totalItemsCount, take: take, skip: skip, onPageChange: handlePageChange, showFirstLast: showFirstLast, pageSizeOptions: pageSizeOptions }) })) : null;
|
|
28
21
|
const tableClassName = cx(styles.tableRoot, styles[variant], styles[size], getRowSeverity && styles.severityTable);
|
|
29
|
-
const tableShell = (_jsx("div", { ...rest, className: tableClassName,
|
|
22
|
+
const tableShell = (_jsx("div", { ...rest, className: tableClassName, children: _jsx("div", { ref: tableRootRef, className: styles.tableScroll, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (_jsxs("table", { className: styles.tableElement, "aria-rowcount": data.length, style: tableWidth != null ? { width: tableWidth } : undefined, children: [_jsxs("colgroup", { children: [hasSelection ? _jsx("col", { style: { width: SELECTION_COLUMN_PX } }) : null, visibleColumns.map(column => (_jsx("col", { style: column.width != null ? { width: column.width } : undefined }, column.id)))] }), _jsx(TableHeader, { columns: visibleColumns, hasSelection: hasSelection, selectionMode: selectionMode, allRowsSelected: allRowsSelected, onSelectAllRows: onSelectAllRows, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, headerExtras: headerExtras }), bodyContent] })) }) }));
|
|
30
23
|
if (fillViewport) {
|
|
31
24
|
return (_jsxs("div", { className: styles.fillViewportRoot, style: {
|
|
32
25
|
flexDirection: paginationPlacement === 'top' ? 'column-reverse' : 'column',
|