@dbcdk/react-components 0.0.34 → 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.
Files changed (33) hide show
  1. package/dist/components/button/Button.module.css +1 -0
  2. package/dist/components/card/components/CardMeta.js +2 -2
  3. package/dist/components/card/components/CardMeta.module.css +3 -3
  4. package/dist/components/datetime-picker/DateTimePicker.d.ts +1 -0
  5. package/dist/components/datetime-picker/DateTimePicker.js +2 -2
  6. package/dist/components/forms/checkbox/Checkbox.module.css +8 -2
  7. package/dist/components/forms/multi-select/MultiSelect.d.ts +4 -1
  8. package/dist/components/forms/multi-select/MultiSelect.js +69 -23
  9. package/dist/components/menu/Menu.module.css +10 -2
  10. package/dist/components/pagination/Pagination.js +7 -5
  11. package/dist/components/table/Table.d.ts +2 -1
  12. package/dist/components/table/Table.js +4 -11
  13. package/dist/components/table/Table.module.css +107 -182
  14. package/dist/components/table/Table.types.d.ts +2 -1
  15. package/dist/components/table/TanstackTable.js +40 -32
  16. package/dist/components/table/components/TableBody.d.ts +2 -3
  17. package/dist/components/table/components/TableBody.js +3 -3
  18. package/dist/components/table/components/TableHeader.d.ts +2 -3
  19. package/dist/components/table/components/TableHeader.js +2 -2
  20. package/dist/components/table/components/TableHeaderCell.js +2 -2
  21. package/dist/components/table/components/TableLoadingBody.d.ts +2 -3
  22. package/dist/components/table/components/TableLoadingBody.js +3 -3
  23. package/dist/components/table/components/TableRow.d.ts +1 -3
  24. package/dist/components/table/components/TableRow.js +5 -6
  25. package/dist/components/table/components/TableSelectionCell.js +3 -2
  26. package/dist/components/table/components/column-resizer/ColumnResizer.module.css +4 -5
  27. package/dist/components/table/table.utils.d.ts +0 -4
  28. package/dist/components/table/table.utils.js +0 -8
  29. package/dist/components/table/tanstackTable.utils.d.ts +10 -6
  30. package/dist/components/table/tanstackTable.utils.js +79 -83
  31. package/dist/src/styles/styles.css +1 -5
  32. package/dist/styles/styles.css +1 -5
  33. package/package.json +2 -1
@@ -27,6 +27,7 @@
27
27
  .buttonLink {
28
28
  color: inherit;
29
29
  text-decoration: none;
30
+ user-select: default;
30
31
  }
31
32
 
32
33
  .button:hover {
@@ -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 = true, }) {
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-medium);
44
+ font-weight: var(--font-weight-default);
45
45
  min-inline-size: 0;
46
46
  word-break: break-word;
47
47
  }
48
48
 
49
- .valueRegular {
50
- font-weight: var(--font-weight-default);
49
+ .valueBold {
50
+ font-weight: var(--font-weight-medium);
51
51
  }
52
52
 
53
53
  .nowrap {
@@ -22,6 +22,7 @@ type InputProps = React.ComponentProps<typeof Input>;
22
22
  type BaseProps = {
23
23
  mode?: Mode;
24
24
  enableTime?: boolean;
25
+ showCalendarIcon?: boolean;
25
26
  timeStep?: number;
26
27
  min?: Date;
27
28
  max?: Date;
@@ -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: center;
3
+ align-items: flex-start;
4
4
  gap: var(--spacing-sm);
5
5
  vertical-align: middle;
6
- line-height: 0;
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 = options.findIndex(option => selectedSet.has(option.value));
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 = options.findIndex(option => selectedSet.has(option.value));
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 || options.length === 0)
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 <= options.length; step += 1) {
61
- const index = (startIndex + step + options.length) % options.length;
62
- const label = (_b = (_a = options[index]) === null || _a === void 0 ? void 0 : _a.label) === null || _b === void 0 ? void 0 : _b.trim().toLocaleLowerCase();
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, options.length - 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(options.length - 1);
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 (options[activeIndex])
135
- toggleValue(options[activeIndex].value);
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
- (_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
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
- }, 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: _jsx(Menu, { onKeyDown: handleKeyDown, children: options.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: () => {
150
- toggleValue(option.value);
151
- }, children: [_jsx("span", { style: {
152
- pointerEvents: 'none',
153
- display: 'flex',
154
- alignItems: 'center',
155
- color: !selectedSet.has(option.value) ? 'var(--color-border-strong)' : 'inherit',
156
- }, children: selectedSet.has(option.value) ? _jsx(Check, {}) : _jsx(Square, {}) }), option.label] }) }, option.value))) }) }));
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: center;
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: center;
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.toLocaleString('da-DK')}\u2013${last.toLocaleString('da-DK')} af ${itemsCount.toLocaleString('da-DK')}`;
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, gridTemplateColumns, tableRootRef, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement, showFirstLast, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }: TableProps<T>): JSX.Element;
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 { buildDefaultGridTemplate, getVisibleColumns } 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, gridTemplateColumns, tableRootRef, toolbar, headerExtras, take, skip, totalItemsCount, paginationPlacement = 'bottom', showFirstLast = false, pageSizeOptions, getRowSeverity, onRowClick, onRowMouseEnter, onRowSelect, onSelectAllRows, onSortChange, onPageChange, ...rest }) {
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, gridStyle: gridStyle })) : (_jsx(TableBody, { data: data, dataKey: dataKey, columns: visibleColumns, gridStyle: gridStyle, striped: striped, selectedRows: selectedRows, hasSelection: hasSelection, selectionMode: selectionMode, selectionInputName: selectionInputName, viewMode: viewMode, getRowSeverity: getRowSeverity, onRowClick: onRowClick, onRowMouseEnter: onRowMouseEnter, onRowSelect: onRowSelect }));
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, ref: tableRootRef, className: tableClassName, role: "table", "aria-rowcount": data.length, children: _jsxs("div", { className: styles.tableContent, children: [_jsx("div", { className: styles.header, role: "rowgroup", children: _jsx(TableHeader, { columns: visibleColumns, gridStyle: gridStyle, hasSelection: hasSelection, selectionMode: selectionMode, allRowsSelected: allRowsSelected, onSelectAllRows: onSelectAllRows, sortById: sortById, sortDirection: sortDirection, onSortChange: onSortChange, headerExtras: headerExtras }) }), _jsx("div", { className: styles.bodyScroll, children: !data.length && !loading ? (_jsx("div", { className: styles.emptyStateSlot, children: _jsx(TableEmptyState, { config: emptyConfig }) })) : (bodyContent) })] }) }));
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',