@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.
- package/dist/components/card/Card.d.ts +21 -3
- package/dist/components/card/Card.js +17 -2
- package/dist/components/card/Card.module.css +59 -0
- package/dist/components/circle/Circle.d.ts +2 -1
- package/dist/components/circle/Circle.js +2 -2
- package/dist/components/circle/Circle.module.css +6 -2
- package/dist/components/code-block/CodeBlock.js +1 -1
- package/dist/components/code-block/CodeBlock.module.css +30 -17
- package/dist/components/copy-button/CopyButton.d.ts +1 -0
- package/dist/components/copy-button/CopyButton.js +10 -2
- package/dist/components/filter-field/FilterField.js +16 -11
- package/dist/components/filter-field/FilterField.module.css +133 -12
- package/dist/components/forms/checkbox/Checkbox.d.ts +2 -2
- package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
- package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
- package/dist/components/forms/input/Input.js +1 -1
- package/dist/components/forms/input/Input.module.css +1 -0
- package/dist/components/forms/input-container/InputContainer.module.css +1 -1
- package/dist/components/hyperlink/Hyperlink.d.ts +19 -7
- package/dist/components/hyperlink/Hyperlink.js +35 -11
- package/dist/components/hyperlink/Hyperlink.module.css +50 -2
- package/dist/components/menu/Menu.d.ts +32 -0
- package/dist/components/menu/Menu.js +73 -13
- package/dist/components/menu/Menu.module.css +72 -4
- package/dist/components/overlay/modal/Modal.module.css +2 -2
- package/dist/components/overlay/side-panel/SidePanel.js +17 -0
- package/dist/components/overlay/side-panel/SidePanel.module.css +0 -2
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
- package/dist/components/popover/Popover.js +1 -1
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +22 -18
- package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
- package/dist/components/sidebar/providers/SidebarProvider.js +66 -18
- package/dist/components/split-button/SplitButton.d.ts +1 -1
- package/dist/components/split-button/SplitButton.js +3 -1
- package/dist/components/split-button/SplitButton.module.css +4 -4
- package/dist/components/state-page/StatePage.module.css +1 -1
- package/dist/components/table/Table.d.ts +9 -4
- package/dist/components/table/Table.js +3 -6
- package/dist/components/table/Table.module.css +18 -5
- package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
- package/dist/components/table/components/table-settings/TableSettings.js +55 -4
- package/dist/components/table/tanstack.d.ts +12 -1
- package/dist/components/table/tanstack.js +75 -23
- package/dist/hooks/useTableSettings.d.ts +23 -4
- package/dist/hooks/useTableSettings.js +64 -17
- package/dist/src/styles/styles.css +38 -22
- package/dist/styles/animation.d.ts +5 -0
- package/dist/styles/animation.js +5 -0
- package/dist/styles/styles.css +38 -22
- package/dist/utils/localStorage.utils.d.ts +19 -0
- package/dist/utils/localStorage.utils.js +78 -0
- 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
|
|
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.
|
|
61
|
-
|
|
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
|
|
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
|
-
//
|
|
106
|
-
|
|
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
|
-
// -
|
|
125
|
-
// -
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
}, [
|
|
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
|
]);
|
|
@@ -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:
|
|
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-
|
|
7
|
-
border-end-start-radius: var(--border-radius-
|
|
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-
|
|
14
|
-
border-end-end-radius: var(--border-radius-
|
|
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;
|
|
@@ -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
|
|
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 === '
|
|
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
|
-
|
|
43
|
+
padding-right: var(--spacing-lg);
|
|
44
|
+
|
|
45
|
+
text-align: left;
|
|
43
46
|
vertical-align: middle;
|
|
47
|
+
|
|
44
48
|
background: inherit;
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
/* Typography */
|
|
47
51
|
font-size: var(--font-size-xs);
|
|
48
|
-
font-weight: var(--font-weight-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
|
1
|
+
import { ColumnDef } from '@tanstack/react-table';
|
|
2
|
+
import type { JSX, ReactNode } from 'react';
|
|
2
3
|
import { ViewMode } from '../../../../hooks/useTableSettings';
|
|
3
|
-
|
|
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
|
-
|
|
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 === '
|
|
10
|
+
handleChangeViewMode(mode === 'wrapped' ? 'compact' : 'wrapped');
|
|
9
11
|
close === null || close === void 0 ? void 0 : close();
|
|
10
12
|
};
|
|
11
|
-
|
|
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
|
|
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
|
|
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: (
|
|
58
|
+
sortable: (_b = def.enableSorting) !== null && _b !== void 0 ? _b : !!accessorKey,
|
|
47
59
|
render,
|
|
48
|
-
hidden:
|
|
49
|
-
align: (
|
|
50
|
-
verticalAlign: (
|
|
51
|
-
fitContent: (
|
|
52
|
-
emptyPlaceholder: (
|
|
53
|
-
allowWrap: (
|
|
54
|
-
fillWidth: (
|
|
55
|
-
severity: (
|
|
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
|
-
|
|
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
|
|
153
|
-
|
|
154
|
-
if (!id)
|
|
155
|
-
return;
|
|
156
|
-
if (!dir)
|
|
205
|
+
var _a;
|
|
206
|
+
if (dir == null) {
|
|
157
207
|
table.setSorting([]);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
}
|