@dbcdk/react-components 0.0.10 → 0.0.12

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 (52) hide show
  1. package/dist/components/card/Card.d.ts +21 -3
  2. package/dist/components/card/Card.js +17 -2
  3. package/dist/components/card/Card.module.css +59 -0
  4. package/dist/components/circle/Circle.d.ts +2 -1
  5. package/dist/components/circle/Circle.js +2 -2
  6. package/dist/components/circle/Circle.module.css +6 -2
  7. package/dist/components/code-block/CodeBlock.js +1 -1
  8. package/dist/components/code-block/CodeBlock.module.css +30 -17
  9. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  10. package/dist/components/copy-button/CopyButton.js +10 -2
  11. package/dist/components/filter-field/FilterField.js +16 -11
  12. package/dist/components/filter-field/FilterField.module.css +133 -12
  13. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
  14. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  15. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  16. package/dist/components/forms/input/Input.js +1 -1
  17. package/dist/components/forms/input/Input.module.css +1 -0
  18. package/dist/components/forms/input-container/InputContainer.module.css +1 -1
  19. package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
  20. package/dist/components/hyperlink/Hyperlink.js +35 -11
  21. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  22. package/dist/components/menu/Menu.d.ts +32 -0
  23. package/dist/components/menu/Menu.js +73 -13
  24. package/dist/components/menu/Menu.module.css +72 -4
  25. package/dist/components/overlay/modal/Modal.module.css +2 -2
  26. package/dist/components/overlay/side-panel/SidePanel.js +17 -0
  27. package/dist/components/overlay/side-panel/SidePanel.module.css +0 -2
  28. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  29. package/dist/components/popover/Popover.js +1 -1
  30. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
  31. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  32. package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
  33. package/dist/components/split-button/SplitButton.d.ts +1 -1
  34. package/dist/components/split-button/SplitButton.js +3 -1
  35. package/dist/components/split-button/SplitButton.module.css +4 -4
  36. package/dist/components/state-page/StatePage.module.css +1 -1
  37. package/dist/components/table/Table.d.ts +9 -4
  38. package/dist/components/table/Table.js +3 -6
  39. package/dist/components/table/Table.module.css +18 -5
  40. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  41. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  42. package/dist/components/table/tanstack.d.ts +12 -1
  43. package/dist/components/table/tanstack.js +75 -23
  44. package/dist/hooks/useTableSettings.d.ts +23 -4
  45. package/dist/hooks/useTableSettings.js +64 -17
  46. package/dist/src/styles/styles.css +38 -22
  47. package/dist/styles/animation.d.ts +5 -0
  48. package/dist/styles/animation.js +5 -0
  49. package/dist/styles/styles.css +38 -22
  50. package/dist/utils/localStorage.utils.d.ts +19 -0
  51. package/dist/utils/localStorage.utils.js +78 -0
  52. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
3
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import { nestedFiltering } from '../../../utils/arrays/nested-filtering';
5
5
  const hasChildren = (item) => Array.isArray(item.children) && item.children.length > 0;
6
6
  const hasHref = (item) => typeof item.href === 'string' && item.href.length > 0;
@@ -32,6 +32,9 @@ const SidebarContext = createContext({
32
32
  resetExpandAll: () => { },
33
33
  activeLink: '',
34
34
  setActiveLink: () => { },
35
+ expandItem: () => { },
36
+ collapseItem: () => { },
37
+ isExpanded: () => false,
35
38
  isSidebarCollapsed: false,
36
39
  handleSidebarCollapseChange: () => { },
37
40
  });
@@ -43,24 +46,60 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
43
46
  const [activeQuery, setActiveQuery] = useState('');
44
47
  const [areItemsCollapsed, setItemsCollapsed] = useState(initialCollapsed);
45
48
  const [activeHref, setActiveHref] = useState('');
49
+ // expandedItems is now the single source of truth for "open groups"
50
+ // (it includes both auto-expanded parents and user-expanded groups)
46
51
  const [expandedItems, setExpandedItems] = useState(new Set());
52
+ // Track items in a ref to avoid effect loops if parent recreates the items array every render
53
+ const itemsRef = useRef(items);
54
+ useEffect(() => {
55
+ itemsRef.current = items;
56
+ }, [items]);
47
57
  const [isSidebarCollapsed, setSidebarCollapsed] = useState(initialSidebarCollapsed !== null && initialSidebarCollapsed !== void 0 ? initialSidebarCollapsed : false);
48
58
  const hasExplicitInitialSidebarCollapsed = initialSidebarCollapsed !== undefined;
49
59
  const triggerExpandAll = useCallback(() => setDefaultExpanded(true), []);
50
60
  const resetExpandAll = useCallback(() => setDefaultExpanded(null), []);
51
61
  const setActiveLink = useCallback((href) => setActiveHref(href), []);
62
+ const expandItem = useCallback((href) => {
63
+ setExpandedItems(prev => {
64
+ if (prev.has(href))
65
+ return prev;
66
+ const next = new Set(prev);
67
+ next.add(href);
68
+ return next;
69
+ });
70
+ }, []);
71
+ const collapseItem = useCallback((href) => {
72
+ setExpandedItems(prev => {
73
+ if (!prev.has(href))
74
+ return prev;
75
+ const next = new Set(prev);
76
+ next.delete(href);
77
+ return next;
78
+ });
79
+ }, []);
80
+ const isExpanded = useCallback((href) => expandedItems.has(href), [expandedItems]);
81
+ // Auto-expand: when active link changes, ensure its parent chain is expanded.
82
+ // IMPORTANT: guard so we only set state when we actually add something.
52
83
  useEffect(() => {
53
84
  if (!activeHref)
54
85
  return;
55
- const path = findParentItem(activeHref, items);
86
+ const currentItems = itemsRef.current;
87
+ const path = findParentItem(activeHref, currentItems);
56
88
  const parents = path.split('.').filter(Boolean);
89
+ if (parents.length === 0)
90
+ return;
57
91
  setExpandedItems(prev => {
92
+ let changed = false;
58
93
  const next = new Set(prev);
59
- for (const p of parents)
60
- next.add(p);
61
- return next;
94
+ for (const p of parents) {
95
+ if (!next.has(p)) {
96
+ next.add(p);
97
+ changed = true;
98
+ }
99
+ }
100
+ return changed ? next : prev;
62
101
  });
63
- }, [activeHref, items]);
102
+ }, [activeHref]);
64
103
  const filteredItems = useMemo(() => {
65
104
  return activeQuery
66
105
  ? nestedFiltering(items, {
@@ -71,11 +110,13 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
71
110
  })
72
111
  : items;
73
112
  }, [items, activeQuery]);
113
+ // Searching should expand all, but do not fight the user forever.
114
+ // We just set defaultExpanded=true, and individual components can honor it.
74
115
  useEffect(() => {
75
- if (activeQuery) {
116
+ if (activeQuery)
76
117
  triggerExpandAll();
77
- }
78
118
  }, [activeQuery, triggerExpandAll]);
119
+ // Initial collapsed state: explicit prop > localStorage > responsive default.
79
120
  useEffect(() => {
80
121
  if (typeof window === 'undefined')
81
122
  return;
@@ -102,9 +143,8 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
102
143
  catch {
103
144
  console.error('Failed to parse sidebar collapsed state from storage');
104
145
  }
105
- // No explicit prop and nothing stored use responsive default
106
- const defaultCollapsed = currentBreakpoint === 'small';
107
- setSidebarCollapsed(defaultCollapsed);
146
+ // Nothing stored responsive default (but we do NOT persist this automatic choice)
147
+ setSidebarCollapsed(currentBreakpoint === 'small');
108
148
  }, [hasExplicitInitialSidebarCollapsed, initialSidebarCollapsed]);
109
149
  const persistCollapsed = useCallback((collapsed) => {
110
150
  if (typeof window === 'undefined')
@@ -116,26 +156,28 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
116
156
  console.error('Failed to persist sidebar collapsed state');
117
157
  }
118
158
  }, []);
159
+ // Only persist user-triggered changes
119
160
  const handleSidebarCollapseChange = useCallback((collapsed) => {
120
161
  setSidebarCollapsed(collapsed);
121
162
  persistCollapsed(collapsed);
122
163
  }, [persistCollapsed]);
123
164
  // Resize behavior:
124
- // - Track current breakpoint.
125
- // - Only when the breakpoint actually changes (small <-> large)
126
- // do we auto-apply the "responsive default" for that breakpoint.
165
+ // - only apply auto-collapse when breakpoint changes
166
+ // - do NOT persist the automatic change (only user actions persist)
127
167
  useEffect(() => {
128
168
  if (typeof window === 'undefined')
129
169
  return;
170
+ let lastBreakpoint = getBreakpoint(window.innerWidth);
130
171
  const onResize = () => {
131
172
  const nextBreakpoint = getBreakpoint(window.innerWidth);
132
- const autoCollapsed = nextBreakpoint === 'small';
133
- setSidebarCollapsed(autoCollapsed);
134
- persistCollapsed(autoCollapsed);
173
+ if (nextBreakpoint === lastBreakpoint)
174
+ return;
175
+ lastBreakpoint = nextBreakpoint;
176
+ setSidebarCollapsed(nextBreakpoint === 'small');
135
177
  };
136
178
  window.addEventListener('resize', onResize);
137
179
  return () => window.removeEventListener('resize', onResize);
138
- }, [persistCollapsed]);
180
+ }, []);
139
181
  const value = useMemo(() => ({
140
182
  defaultExpanded,
141
183
  expandedItems,
@@ -148,6 +190,9 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
148
190
  setItemsCollapsed,
149
191
  activeLink: activeHref,
150
192
  setActiveLink,
193
+ expandItem,
194
+ collapseItem,
195
+ isExpanded,
151
196
  isSidebarCollapsed,
152
197
  handleSidebarCollapseChange,
153
198
  }), [
@@ -162,6 +207,9 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
162
207
  setItemsCollapsed,
163
208
  activeHref,
164
209
  setActiveLink,
210
+ expandItem,
211
+ collapseItem,
212
+ isExpanded,
165
213
  isSidebarCollapsed,
166
214
  handleSidebarCollapseChange,
167
215
  ]);
@@ -2,7 +2,7 @@ import type { ComponentProps, ReactNode } from 'react';
2
2
  import { Button } from '../button/Button';
3
3
  interface SplitButtonItem {
4
4
  label?: string;
5
- onClick: () => void;
5
+ onClick: (close?: () => void) => void;
6
6
  icon?: ReactNode;
7
7
  active?: boolean;
8
8
  }
@@ -4,5 +4,7 @@ import { Button } from '../button/Button';
4
4
  import { Menu } from '../menu/Menu';
5
5
  import { Popover } from '../popover/Popover';
6
6
  export function SplitButton({ children, options, onClick, icon, ...rest }) {
7
- return (_jsxs("div", { className: styles.container, style: { display: 'inline-flex', alignItems: 'center' }, children: [_jsxs(Button, { ...rest, onClick: onClick, children: [icon, children] }), _jsx(Popover, { trigger: (handleClick, icon) => (_jsx("span", { className: styles.triggerContainer, children: _jsx(Button, { ...rest, onClick: handleClick, children: icon }) })), children: _jsx(Menu, { children: options.map(option => (_jsx(Menu.Item, { active: option.active, children: _jsxs("button", { onClick: option.onClick, children: [option.icon, option.label] }) }, option.label))) }) })] }));
7
+ return (_jsxs("div", { className: styles.container, style: { display: 'inline-flex', alignItems: 'center' }, children: [_jsxs(Button, { ...rest, onClick: onClick, children: [icon, children] }), _jsx(Popover, { trigger: (handleClick, icon) => (_jsx("span", { className: styles.triggerContainer, children: _jsx(Button, { ...rest, onClick: handleClick, children: icon }) })), children: close => (_jsx(Menu, { children: options.map(option => (_jsx(Menu.Item, { active: option.active, children: _jsxs("button", { onClick: e => {
8
+ option.onClick(close);
9
+ }, children: [option.icon, option.label] }) }, option.label))) })) })] }));
8
10
  }
@@ -3,15 +3,15 @@
3
3
  }
4
4
 
5
5
  .container > button:first-child {
6
- border-start-start-radius: var(--border-radius-md);
7
- border-end-start-radius: var(--border-radius-md);
6
+ border-start-start-radius: var(--border-radius-default);
7
+ border-end-start-radius: var(--border-radius-default);
8
8
  border-start-end-radius: 0;
9
9
  border-end-end-radius: 0;
10
10
  }
11
11
 
12
12
  .triggerContainer button {
13
- border-start-end-radius: var(--border-radius-md);
14
- border-end-end-radius: var(--border-radius-md);
13
+ border-start-end-radius: var(--border-radius-default);
14
+ border-end-end-radius: var(--border-radius-default);
15
15
  border-start-start-radius: 0;
16
16
  border-end-start-radius: 0;
17
17
  padding-block: 0;
@@ -4,6 +4,6 @@
4
4
  }
5
5
 
6
6
  .illustration svg {
7
- width: 500px;
7
+ width: 300px;
8
8
  height: auto;
9
9
  }
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { JSX, ReactNode } from 'react';
2
+ import type { HTMLAttributes, JSX, ReactNode } from 'react';
3
3
  import { Severity } from '../../constants/severity.types';
4
4
  import { PageChangeEvent } from '../../components/pagination/Pagination';
5
5
  import { ViewMode } from '../../hooks/useTableSettings';
@@ -19,13 +19,14 @@ export interface ColumnItem<T> {
19
19
  allowWrap?: boolean;
20
20
  emptyPlaceholder?: ReactNode;
21
21
  width?: number | string;
22
+ canHide?: boolean;
22
23
  }
23
24
  type HeaderExtrasArgs<T> = {
24
25
  column: ColumnItem<T>;
25
26
  index: number;
26
27
  };
27
28
  export type TableVariant = 'primary' | 'embedded';
28
- export interface TableProps<T extends Record<string, any>> {
29
+ export type TableProps<T extends Record<string, any>> = Omit<HTMLAttributes<HTMLTableElement>, 'onClick'> & {
29
30
  data: T[];
30
31
  dataKey: keyof T;
31
32
  columns: ColumnItem<T>[];
@@ -42,6 +43,10 @@ export interface TableProps<T extends Record<string, any>> {
42
43
  headerExtras?: (args: HeaderExtrasArgs<T>) => ReactNode;
43
44
  columnStyles?: Partial<Record<string, React.CSSProperties>>;
44
45
  headerBelowRow?: ReactNode;
46
+ /**
47
+ * NEW: optional toolbar area above the table (right now used for column selector)
48
+ */
49
+ toolbar?: ReactNode;
45
50
  striped?: boolean;
46
51
  fillViewport?: boolean;
47
52
  viewportBottomOffset?: number;
@@ -58,6 +63,6 @@ export interface TableProps<T extends Record<string, any>> {
58
63
  showFirstLast?: boolean;
59
64
  viewMode?: ViewMode;
60
65
  emptyConfig?: TableEmptyConfig;
61
- }
62
- export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, headerBelowRow, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, }: TableProps<T>): JSX.Element;
66
+ } & Omit<HTMLAttributes<HTMLTableElement>, 'onClick'>;
67
+ export declare function Table<T extends Record<string, any>>({ data, columns, selectedRows, onRowSelect, selectionMode, onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, headerBelowRow, toolbar, striped, fillViewport, viewportBottomOffset, viewportMin, viewportIncludeMarginTop, take, skip, paginationPlacement, totalItemsCount, onPageChange, loading, variant, size, getRowSeverity, showFirstLast, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }: TableProps<T>): JSX.Element;
63
68
  export {};
@@ -9,7 +9,7 @@ import { Pagination } from '../../components/pagination/Pagination';
9
9
  import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
10
10
  import { TableEmptyState } from './components/empty-state/EmptyState';
11
11
  import styles from './Table.module.css';
12
- export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, headerBelowRow, striped, fillViewport = false, viewportBottomOffset = 0, viewportMin = 120, viewportIncludeMarginTop = false, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, }) {
12
+ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode = 'single', onSortChange, onRowClick, sortById, sortDirection, dataKey, headerExtras, columnStyles, headerBelowRow, toolbar, striped, fillViewport = false, viewportBottomOffset = 0, viewportMin = 120, viewportIncludeMarginTop = false, take, skip, paginationPlacement = 'bottom', totalItemsCount, onPageChange, loading, variant = 'primary', size = 'md', getRowSeverity, showFirstLast = false, allRowsSelected, onSelectAllRows, viewMode, emptyConfig, ...rest }) {
13
13
  const filteredColumns = useMemo(() => columns.filter(c => !c.hidden), [columns]);
14
14
  const handlePageChange = useCallback((e) => {
15
15
  onPageChange === null || onPageChange === void 0 ? void 0 : onPageChange(e);
@@ -31,7 +31,7 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
31
31
  min: viewportMin,
32
32
  includeMarginTop: viewportIncludeMarginTop,
33
33
  });
34
- const tableEl = (_jsxs(_Fragment, { children: [_jsxs("table", { className: `${styles.table} ${styles[variant]} ${styles[size]} ${getRowSeverity ? styles.severityTable : ''}`, children: [_jsxs("thead", { children: [_jsxs("tr", { children: [selectedRows && onRowSelect && dataKey && (_jsx("th", { className: `${styles.fitContent} ${styles.th} $`, children: selectionMode === 'multiple' ? (_jsx(Checkbox, { size: "sm", variant: "primary", checked: allRowsSelected, onChange: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null })), filteredColumns.map((column, index) => {
34
+ const tableEl = (_jsxs(_Fragment, { children: [toolbar ? _jsx("div", { style: { marginBottom: 12 }, children: toolbar }) : null, _jsxs("table", { ...rest, className: `${styles.table} ${styles[variant]} ${styles[size]} ${getRowSeverity ? styles.severityTable : ''}`, children: [_jsxs("thead", { children: [_jsxs("tr", { children: [selectedRows && onRowSelect && dataKey && (_jsx("th", { className: `${styles.fitContent} ${styles.th} $`, children: selectionMode === 'multiple' ? (_jsx(Checkbox, { size: "sm", variant: "primary", checked: allRowsSelected, onChange: checked => onSelectAllRows === null || onSelectAllRows === void 0 ? void 0 : onSelectAllRows(checked) })) : null })), filteredColumns.map((column, index) => {
35
35
  const isActiveSort = sortById === column.id;
36
36
  const ariaSort = column.sortable && isActiveSort
37
37
  ? sortDirection === 'asc'
@@ -73,12 +73,9 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
73
73
  e.stopPropagation();
74
74
  const isSelected = selectedRows.has(rowId);
75
75
  if (selectionMode === 'single') {
76
- // In single mode, treat modifier-click as "select this row"
77
- // (toggle if already selected)
78
76
  onRowSelect(rowId, !isSelected);
79
77
  }
80
78
  else {
81
- // multiple mode: toggle selection
82
79
  onRowSelect(rowId, !isSelected);
83
80
  }
84
81
  return;
@@ -92,7 +89,7 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
92
89
  var _a, _b;
93
90
  return (_jsx("td", { style: getColStyle(column.id, column.align, column.verticalAlign, column.width), className: `${styles.tableCell} ${column.fitContent ? 'fitContent' : ''} ${column.allowWrap ||
94
91
  (selectedRows === null || selectedRows === void 0 ? void 0 : selectedRows.has(row[dataKey])) ||
95
- viewMode === 'comfortable'
92
+ viewMode === 'wrapped'
96
93
  ? styles.allowWrap
97
94
  : styles.nowrap} `, children: column.render
98
95
  ? column.render(row) || ((_a = column.emptyPlaceholder) !== null && _a !== void 0 ? _a : '')
@@ -37,19 +37,32 @@
37
37
 
38
38
  .table .th {
39
39
  position: relative;
40
+
40
41
  padding-block: var(--spacing-xs);
41
42
  padding-inline: var(--spacing-md);
42
- text-align: start;
43
+ padding-right: var(--spacing-lg);
44
+
45
+ text-align: left;
43
46
  vertical-align: middle;
47
+
44
48
  background: inherit;
45
49
 
46
- color: var(--color-fg-muted);
50
+ /* Typography */
47
51
  font-size: var(--font-size-xs);
48
- font-weight: var(--font-weight-medium);
52
+ font-weight: var(--font-weight-normal);
53
+ letter-spacing: var(--letter-spacing-wide);
54
+ text-transform: uppercase;
49
55
 
56
+ color: var(--color-fg-subtle);
57
+
58
+ /* Truncation */
50
59
  white-space: nowrap;
51
- min-width: unset;
52
- max-width: unset;
60
+ overflow: hidden;
61
+ text-overflow: ellipsis;
62
+
63
+ /* Width control */
64
+ min-width: 0;
65
+ max-width: var(--card-label-width);
53
66
  }
54
67
 
55
68
  /* Small variant: header padding */
@@ -1,8 +1,18 @@
1
- import type { JSX } from 'react';
1
+ import { ColumnDef } from '@tanstack/react-table';
2
+ import type { JSX, ReactNode } from 'react';
2
3
  import { ViewMode } from '../../../../hooks/useTableSettings';
3
- interface TableSettingsProps {
4
+ import { ButtonSize } from '../../../button/Button';
5
+ interface TableSettingsProps<T extends Record<string, any>> {
4
6
  handleChangeViewMode: (mode: ViewMode) => void;
5
7
  viewMode: ViewMode;
8
+ columns?: ColumnDef<T>[];
9
+ visibleColumnIds?: string[];
10
+ onVisibleColumnIdsChange?: (nextVisibleIds: string[]) => void;
11
+ columnsLabel?: string;
12
+ allPresetLabel?: string;
13
+ standardPresetLabel?: string;
14
+ buttonSize?: ButtonSize;
15
+ additionalSettings?: (close?: () => void) => ReactNode;
6
16
  }
7
- export declare function TableSettings({ viewMode, handleChangeViewMode }: TableSettingsProps): JSX.Element;
17
+ export declare function TableSettings<T extends Record<string, any>>({ viewMode, handleChangeViewMode, columns, visibleColumnIds, onVisibleColumnIdsChange, columnsLabel, allPresetLabel, standardPresetLabel, buttonSize, additionalSettings, }: TableSettingsProps<T>): JSX.Element;
8
18
  export {};
@@ -1,12 +1,63 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
3
  import { ListChevronsDownUp, Settings } from 'lucide-react';
4
+ import { useMemo } from 'react';
3
5
  import { Button } from '../../../button/Button';
4
6
  import { Menu } from '../../../menu/Menu';
5
7
  import { Popover } from '../../../popover/Popover';
6
- export function TableSettings({ viewMode, handleChangeViewMode }) {
8
+ export function TableSettings({ viewMode, handleChangeViewMode, columns = [], visibleColumnIds = [], onVisibleColumnIdsChange, columnsLabel = 'Kolonner', allPresetLabel = 'Alle', standardPresetLabel = 'Standard', buttonSize = 'sm', additionalSettings, }) {
7
9
  const handleViewModeChange = (mode, close) => {
8
- handleChangeViewMode(mode === 'compact' ? 'compact' : 'comfortable');
10
+ handleChangeViewMode(mode === 'wrapped' ? 'compact' : 'wrapped');
9
11
  close === null || close === void 0 ? void 0 : close();
10
12
  };
11
- return (_jsx(Popover, { trigger: (onClick, icon) => (_jsxs(Button, { onClick: onClick, children: [_jsx(Settings, {}), icon] })), children: close => (_jsx(Menu, { children: _jsx(Menu.Item, { active: viewMode === 'compact', children: _jsxs("button", { type: "button", onClick: () => handleViewModeChange('compact', close), children: [_jsx(ListChevronsDownUp, {}), "Kompakt"] }) }) })) }));
13
+ const hideableColumns = useMemo(() => columns.filter(c => c.enableHiding !== false), [columns]);
14
+ const allPresetIds = useMemo(() => hideableColumns.map(c => c.id), [hideableColumns]);
15
+ const standardPresetIds = useMemo(() => hideableColumns.filter(c => { var _a; return ((_a = c.meta) === null || _a === void 0 ? void 0 : _a.hidden) !== true; }).map(c => c.id), [hideableColumns]);
16
+ const visibleSet = useMemo(() => new Set(visibleColumnIds), [visibleColumnIds]);
17
+ const visibleCount = useMemo(() => {
18
+ return hideableColumns.reduce((acc, c) => { var _a; return acc + (visibleSet.has((_a = c.id) !== null && _a !== void 0 ? _a : '') ? 1 : 0); }, 0);
19
+ }, [hideableColumns, visibleSet]);
20
+ const setVisibleIds = (nextIds) => {
21
+ if (!onVisibleColumnIdsChange)
22
+ return;
23
+ const safe = nextIds.length > 0 ? nextIds : standardPresetIds.length > 0 ? standardPresetIds : allPresetIds;
24
+ onVisibleColumnIdsChange(safe.filter((id) => typeof id === 'string' && Boolean(id)));
25
+ };
26
+ const toggleColumn = (id, nextVisible) => {
27
+ const next = new Set(visibleColumnIds);
28
+ if (nextVisible)
29
+ next.add(id);
30
+ else
31
+ next.delete(id);
32
+ setVisibleIds(Array.from(next));
33
+ };
34
+ const isAllActive = useMemo(() => {
35
+ if (!hideableColumns.length)
36
+ return false;
37
+ return hideableColumns.every(c => c.id && visibleSet.has(c.id));
38
+ }, [hideableColumns, visibleSet]);
39
+ const isStandardActive = useMemo(() => {
40
+ if (!hideableColumns.length)
41
+ return false;
42
+ const std = new Set(standardPresetIds);
43
+ return hideableColumns.every(c => c.id && visibleSet.has(c.id) === std.has(c.id));
44
+ }, [hideableColumns, visibleSet, standardPresetIds]);
45
+ // Required by your RadioButton component
46
+ const presetRadioName = 'table-columns-preset';
47
+ return (_jsx(Popover, { trigger: (onClick, icon) => (_jsxs(Button, { size: buttonSize, onClick: onClick, type: "button", children: [_jsx(Settings, {}), icon] })), children: close => (_jsxs(Menu, { children: [additionalSettings === null || additionalSettings === void 0 ? void 0 : additionalSettings(close), _jsx(Menu.Item, { active: viewMode === 'wrapped', children: _jsxs("button", { type: "button", onClick: () => handleViewModeChange(viewMode, close), children: [_jsx(ListChevronsDownUp, {}), "Ombryd tekst"] }) }), hideableColumns.length > 0 && onVisibleColumnIdsChange ? (_jsxs(_Fragment, { children: [_jsx(Menu.Separator, {}), _jsx("div", { style: { padding: '6px 10px', fontSize: 12, opacity: 0.7 }, children: columnsLabel }), _jsx(Menu.RadioItem, { name: presetRadioName, value: "all", checked: isAllActive, label: allPresetLabel, onValueChange: () => {
48
+ setVisibleIds(allPresetIds.filter((id) => typeof id === 'string' && Boolean(id)));
49
+ close === null || close === void 0 ? void 0 : close();
50
+ } }), _jsx(Menu.RadioItem, { name: presetRadioName, value: "standard", checked: isStandardActive, label: standardPresetLabel, onValueChange: () => {
51
+ setVisibleIds(standardPresetIds.filter((id) => typeof id === 'string' && Boolean(id)));
52
+ close === null || close === void 0 ? void 0 : close();
53
+ } }), _jsx(Menu.Separator, {}), hideableColumns.map(col => {
54
+ const isVisible = col.id ? visibleSet.has(col.id) : false;
55
+ const disableUncheckingLast = isVisible && visibleCount <= 1;
56
+ const label = col.header;
57
+ return (_jsx(Menu.CheckItem, { checked: isVisible, disabled: disableUncheckingLast, label: label, onCheckedChange: nextChecked => {
58
+ if (disableUncheckingLast)
59
+ return;
60
+ toggleColumn(col.id, nextChecked);
61
+ } }, col.id));
62
+ })] })) : null] })) }));
12
63
  }
@@ -3,12 +3,23 @@ import * as React from 'react';
3
3
  import { type TableProps, type TableVariant } from './Table';
4
4
  import { ViewMode } from '../../hooks/useTableSettings';
5
5
  type Filterable<T> = Array<keyof T>;
6
- export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles'> & {
6
+ export type TanstackTableProps<T extends Record<string, any>> = Omit<TableProps<T>, 'columns' | 'onSortChange' | 'sortById' | 'sortDirection' | 'headerBelowRow' | 'headerExtras' | 'columnStyles' | 'toolbar'> & {
7
7
  columns: ReadonlyArray<ColumnDef<T, any>>;
8
8
  filterable?: Filterable<T>;
9
9
  onSortingChange?: (sortBy: string | number | symbol | null, direction: 'asc' | 'desc' | null) => void;
10
+ initialSortBy?: string;
11
+ initialSortDirection?: 'asc' | 'desc';
10
12
  variant?: TableVariant;
11
13
  viewMode?: ViewMode;
14
+ /**
15
+ * TanStack-agnostic column visibility input.
16
+ *
17
+ * If provided, this list is the single source of truth for which columns are visible.
18
+ * If not provided (or empty), defaults are derived from ColumnDef meta.hidden.
19
+ *
20
+ * NOTE: Passing [] is treated as "unset" and will fall back to defaults.
21
+ */
22
+ visibleColumnIds?: string[];
12
23
  };
13
24
  export declare function TanstackTable<T extends Record<string, any>>(props: TanstackTableProps<T>): React.ReactNode;
14
25
  export {};
@@ -2,8 +2,8 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, } from '@tanstack/react-table';
4
4
  import * as React from 'react';
5
- import ColumnResizer from './components/column-resizer/ColumnResizer';
6
5
  import { Table } from './Table';
6
+ import ColumnResizer from './components/column-resizer/ColumnResizer';
7
7
  function getColumnId(def, index) {
8
8
  const d = def;
9
9
  if (d.id != null && String(d.id).length)
@@ -12,9 +12,20 @@ function getColumnId(def, index) {
12
12
  return String(d.accessorKey);
13
13
  return `col_${index}`;
14
14
  }
15
- function mapDefsToColumnItems(defs) {
15
+ function buildDefaultVisibleIdsFromDefs(defs) {
16
+ const ids = [];
17
+ defs.forEach((def, idx) => {
18
+ var _a;
19
+ const id = getColumnId(def, idx);
20
+ const hiddenByMeta = Boolean((_a = def.meta) === null || _a === void 0 ? void 0 : _a.hidden);
21
+ if (!hiddenByMeta)
22
+ ids.push(id);
23
+ });
24
+ return ids;
25
+ }
26
+ function mapDefsToColumnItems(defs, columnVisibility) {
16
27
  return defs.map((def, index) => {
17
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
28
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
18
29
  const id = getColumnId(def, index);
19
30
  const accessorKey = def.accessorKey;
20
31
  const accessorFn = def.accessorFn;
@@ -39,41 +50,62 @@ function mapDefsToColumnItems(defs) {
39
50
  else {
40
51
  render = () => null;
41
52
  }
53
+ const isVisible = (_a = columnVisibility[id]) !== null && _a !== void 0 ? _a : true;
42
54
  return {
43
55
  id,
44
56
  header: def.header,
45
57
  accessor: accessorKey,
46
- sortable: (_a = def.enableSorting) !== null && _a !== void 0 ? _a : !!accessorKey,
58
+ sortable: (_b = def.enableSorting) !== null && _b !== void 0 ? _b : !!accessorKey,
47
59
  render,
48
- hidden: (_c = (_b = def.meta) === null || _b === void 0 ? void 0 : _b.hidden) !== null && _c !== void 0 ? _c : false,
49
- align: (_e = (_d = def.meta) === null || _d === void 0 ? void 0 : _d.align) !== null && _e !== void 0 ? _e : undefined,
50
- verticalAlign: (_g = (_f = def.meta) === null || _f === void 0 ? void 0 : _f.verticalAlign) !== null && _g !== void 0 ? _g : undefined,
51
- fitContent: (_j = (_h = def.meta) === null || _h === void 0 ? void 0 : _h.fitContent) !== null && _j !== void 0 ? _j : false,
52
- emptyPlaceholder: (_l = (_k = def.meta) === null || _k === void 0 ? void 0 : _k.emptyPlaceholder) !== null && _l !== void 0 ? _l : '-',
53
- allowWrap: (_o = (_m = def.meta) === null || _m === void 0 ? void 0 : _m.allowWrap) !== null && _o !== void 0 ? _o : false,
54
- fillWidth: (_q = (_p = def.meta) === null || _p === void 0 ? void 0 : _p.fillWidth) !== null && _q !== void 0 ? _q : false,
55
- severity: (_s = (_r = def.meta) === null || _r === void 0 ? void 0 : _r.severity) !== null && _s !== void 0 ? _s : undefined,
60
+ hidden: !isVisible,
61
+ align: (_d = (_c = def.meta) === null || _c === void 0 ? void 0 : _c.align) !== null && _d !== void 0 ? _d : undefined,
62
+ verticalAlign: (_f = (_e = def.meta) === null || _e === void 0 ? void 0 : _e.verticalAlign) !== null && _f !== void 0 ? _f : undefined,
63
+ fitContent: (_h = (_g = def.meta) === null || _g === void 0 ? void 0 : _g.fitContent) !== null && _h !== void 0 ? _h : false,
64
+ emptyPlaceholder: (_k = (_j = def.meta) === null || _j === void 0 ? void 0 : _j.emptyPlaceholder) !== null && _k !== void 0 ? _k : '-',
65
+ allowWrap: (_m = (_l = def.meta) === null || _l === void 0 ? void 0 : _l.allowWrap) !== null && _m !== void 0 ? _m : false,
66
+ fillWidth: (_p = (_o = def.meta) === null || _o === void 0 ? void 0 : _o.fillWidth) !== null && _p !== void 0 ? _p : false,
67
+ severity: (_r = (_q = def.meta) === null || _q === void 0 ? void 0 : _q.severity) !== null && _r !== void 0 ? _r : undefined,
56
68
  };
57
69
  });
58
70
  }
59
71
  export function TanstackTable(props) {
60
72
  var _a, _b;
61
- const { data, dataKey, columns, filterable = [], onSortingChange, ...tableProps } = props;
73
+ const { data, dataKey, columns, filterable = [], onSortingChange, initialSortBy, initialSortDirection, visibleColumnIds, ...tableProps } = props;
62
74
  const [sorting, setSorting] = React.useState([]);
63
75
  const [columnFilters, setColumnFilters] = React.useState([]);
64
76
  const [columnSizing, setColumnSizing] = React.useState({});
77
+ // IDs in the same order as defs
78
+ const allColumnIds = React.useMemo(() => columns.map((def, i) => getColumnId(def, i)), [columns]);
79
+ // Defaults derived from ColumnDef meta.hidden
80
+ const defaultVisibleIds = React.useMemo(() => buildDefaultVisibleIdsFromDefs(columns), [columns]);
81
+ // Treat [] as "unset" and fall back to defaults
82
+ const effectiveVisibleIds = React.useMemo(() => {
83
+ if (visibleColumnIds && visibleColumnIds.length > 0)
84
+ return visibleColumnIds;
85
+ return defaultVisibleIds;
86
+ }, [visibleColumnIds, defaultVisibleIds]);
87
+ // TanStack visibility state
88
+ const [columnVisibility, setColumnVisibility] = React.useState(() => {
89
+ const visible = new Set(effectiveVisibleIds);
90
+ const next = {};
91
+ for (const id of allColumnIds)
92
+ next[id] = visible.has(id);
93
+ return next;
94
+ });
65
95
  const table = useReactTable({
66
96
  data,
67
97
  columns: columns,
68
- state: { sorting, columnFilters, columnSizing },
98
+ state: { sorting, columnFilters, columnSizing, columnVisibility },
69
99
  onSortingChange: setSorting,
70
100
  onColumnFiltersChange: setColumnFilters,
71
101
  onColumnSizingChange: setColumnSizing,
102
+ onColumnVisibilityChange: setColumnVisibility,
72
103
  getCoreRowModel: getCoreRowModel(),
73
104
  getSortedRowModel: getSortedRowModel(),
74
105
  getFilteredRowModel: getFilteredRowModel(),
75
106
  enableColumnResizing: true,
76
107
  columnResizeMode: 'onChange',
108
+ initialState: {},
77
109
  defaultColumn: {
78
110
  enableResizing: true,
79
111
  minSize: 80,
@@ -81,7 +113,28 @@ export function TanstackTable(props) {
81
113
  maxSize: 800,
82
114
  },
83
115
  });
84
- const columnItems = React.useMemo(() => mapDefsToColumnItems(columns), [columns]);
116
+ // Apply external "visibleColumnIds" -> TanStack column visibility using toggleVisibility,
117
+ // as requested (no selector UI here).
118
+ React.useEffect(() => {
119
+ const desired = new Set(effectiveVisibleIds);
120
+ for (const id of allColumnIds) {
121
+ const col = table.getColumn(id);
122
+ if (!col)
123
+ continue;
124
+ const shouldBeVisible = desired.has(id);
125
+ if (col.getIsVisible() !== shouldBeVisible) {
126
+ col.toggleVisibility(shouldBeVisible);
127
+ }
128
+ }
129
+ }, [table, allColumnIds, effectiveVisibleIds]);
130
+ React.useEffect(() => {
131
+ if (initialSortDirection !== 'asc' && initialSortDirection !== 'desc')
132
+ return table.setSorting([]);
133
+ table.setSorting(() => initialSortBy && initialSortDirection
134
+ ? [{ id: initialSortBy, desc: initialSortDirection === 'desc' }]
135
+ : []);
136
+ }, [initialSortBy, initialSortDirection, table]);
137
+ const columnItems = React.useMemo(() => mapDefsToColumnItems(columns, columnVisibility), [columns, columnVisibility]);
85
138
  const visibleData = table.getRowModel().rows.map(r => r.original);
86
139
  const s = (_a = table.getState().sorting) === null || _a === void 0 ? void 0 : _a[0];
87
140
  const sortById = (_b = s === null || s === void 0 ? void 0 : s.id) !== null && _b !== void 0 ? _b : undefined;
@@ -149,14 +202,13 @@ export function TanstackTable(props) {
149
202
  }) }));
150
203
  }, [columnItems, filterable, table, gridTemplateColumns]);
151
204
  return (_jsx(Table, { ...tableProps, dataKey: dataKey, data: visibleData, columns: columnItems, sortById: sortById, sortDirection: sortDirection, onSortChange: (col, dir) => {
152
- var _a, _b, _c;
153
- const id = String(col.id);
154
- if (!id)
155
- return;
156
- if (!dir)
205
+ var _a;
206
+ if (dir == null) {
157
207
  table.setSorting([]);
158
- else
159
- table.setSorting([{ id, desc: dir === 'desc' }]);
160
- onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange((_c = (_b = (_a = table.getState().sorting) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null, dir);
208
+ onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(null, null);
209
+ return;
210
+ }
211
+ table.setSorting([{ id: col.id, desc: dir === 'desc' }]);
212
+ onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange((_a = col.id) !== null && _a !== void 0 ? _a : null, dir);
161
213
  }, headerExtras: headerExtras, columnStyles: columnStyles, headerBelowRow: headerBelowRow }));
162
214
  }