@dbcdk/react-components 0.0.40 → 0.0.42

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.
@@ -6,6 +6,7 @@
6
6
  white-space: nowrap;
7
7
  font-family: var(--font-family);
8
8
  color: var(--color-fg-default);
9
+ line-height: 1;
9
10
  }
10
11
 
11
12
  .circle {
@@ -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 = [10, 25, 50, 100];
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);
@@ -66,7 +66,7 @@
66
66
  }
67
67
 
68
68
  .progressSegment[data-severity='progress'] {
69
- background: color-mix(in srgb, var(--color-status-info) 20%, var(--color-bg-surface));
69
+ background: color-mix(in srgb, var(--color-fg-subtle) 5%, var(--color-bg-surface));
70
70
  }
71
71
 
72
72
  .progressSegment[data-severity='missing'] {
@@ -82,7 +82,7 @@
82
82
 
83
83
  .header {
84
84
  position: relative;
85
- z-index: 2;
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: block;
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;
@@ -18,6 +18,7 @@ export interface ColumnItem<T> {
18
18
  emptyPlaceholder?: ReactNode;
19
19
  canHide?: boolean;
20
20
  severity?: any;
21
+ allowOverflow?: boolean;
21
22
  }
22
23
  export type HeaderExtrasArgs<T> = {
23
24
  column: ColumnItem<T>;
@@ -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
- export function usePagination({ data = [], skip = 0, take = 10, state, onStateChange, resetOnDataChange = false, }) {
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 [uncontrolled, setUncontrolled] = useState(() => ({
9
- skip: Math.max(0, skip),
10
- take: Math.max(1, take),
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
- // (For controlled, parent owns state.)
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
- take: Math.max(1, next.take),
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
- else
40
- setUncontrolled(normalized);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.40",
3
+ "version": "0.0.42",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -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
- }