@dbcdk/react-components 0.0.17 → 0.0.19
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/forms/checkbox/Checkbox.d.ts +1 -1
- package/dist/components/forms/checkbox/Checkbox.module.css +7 -0
- package/dist/components/forms/input/Input.module.css +0 -5
- package/dist/components/sidebar/providers/SidebarProvider.d.ts +1 -1
- package/dist/components/sidebar/providers/SidebarProvider.js +64 -35
- package/dist/components/table/components/empty-state/EmptyState.d.ts +0 -5
- package/dist/components/table/components/empty-state/EmptyState.js +1 -8
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { JSX, ReactNode } from 'react';
|
|
3
|
-
type Variant = 'default' | 'primary' | 'outlined' | 'success';
|
|
3
|
+
type Variant = 'default' | 'primary' | 'outlined' | 'success' | 'info';
|
|
4
4
|
type Size = 'sm' | 'md' | 'lg';
|
|
5
5
|
interface CheckboxProps {
|
|
6
6
|
checked?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { NavBarItem } from '../../../components/nav-bar/NavBar';
|
|
3
|
+
import type { NavBarItem } from '../../../components/nav-bar/NavBar';
|
|
4
4
|
export type SidebarContextValue = {
|
|
5
5
|
defaultExpanded: boolean | null;
|
|
6
6
|
expandedItems: Set<string>;
|
|
@@ -2,24 +2,54 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import { nestedFiltering } from '../../../utils/arrays/nested-filtering';
|
|
5
|
+
/**
|
|
6
|
+
* Production notes:
|
|
7
|
+
* - No console logging.
|
|
8
|
+
* - Auto-expands the correct expandable chain for the active link (including when the active link
|
|
9
|
+
* points to the expandable parent itself).
|
|
10
|
+
* - Normalizes hrefs (trailing slashes) so comparisons are stable.
|
|
11
|
+
*/
|
|
5
12
|
const hasChildren = (item) => Array.isArray(item.children) && item.children.length > 0;
|
|
6
13
|
const hasHref = (item) => typeof item.href === 'string' && item.href.length > 0;
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
const normalizeHref = (href) => {
|
|
15
|
+
if (!href)
|
|
16
|
+
return href;
|
|
17
|
+
// strip trailing slashes except root
|
|
18
|
+
return href.length > 1 ? href.replace(/\/+$/, '') : href;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Returns the chain of "expandable parents" (hrefs) that contain `targetHref`.
|
|
22
|
+
*
|
|
23
|
+
* Behavior:
|
|
24
|
+
* - If targetHref matches an expandable parent item itself, the chain includes it.
|
|
25
|
+
* - If targetHref matches a leaf under expandables, the chain includes expandable ancestors.
|
|
26
|
+
*
|
|
27
|
+
* Expandable parent definition:
|
|
28
|
+
* - any node with children AND an href
|
|
29
|
+
* (If you want to restrict this to a specific type, update isExpandableParent.)
|
|
30
|
+
*/
|
|
31
|
+
const findExpandableParentChain = (targetHref, items) => {
|
|
32
|
+
var _a;
|
|
33
|
+
const target = normalizeHref(targetHref);
|
|
34
|
+
const dfs = (nodes, parentExpandables) => {
|
|
35
|
+
for (const node of nodes) {
|
|
36
|
+
const nodeHref = hasHref(node) ? normalizeHref(node.href) : null;
|
|
37
|
+
const isExpandableParent = hasChildren(node) && hasHref(node);
|
|
38
|
+
const nextParents = isExpandableParent && nodeHref ? [...parentExpandables, nodeHref] : parentExpandables;
|
|
39
|
+
// Match this node
|
|
40
|
+
if (nodeHref === target) {
|
|
41
|
+
return isExpandableParent ? nextParents : parentExpandables;
|
|
42
|
+
}
|
|
43
|
+
// Recurse into children
|
|
44
|
+
if (hasChildren(node)) {
|
|
45
|
+
const found = dfs(node.children, nextParents);
|
|
46
|
+
if (found)
|
|
47
|
+
return found;
|
|
15
48
|
}
|
|
16
|
-
// Recurse
|
|
17
|
-
const path = findParentItem(navItem, currentItem.children, nextPath);
|
|
18
|
-
if (path)
|
|
19
|
-
return path;
|
|
20
49
|
}
|
|
21
|
-
|
|
22
|
-
|
|
50
|
+
return null;
|
|
51
|
+
};
|
|
52
|
+
return (_a = dfs(items, [])) !== null && _a !== void 0 ? _a : [];
|
|
23
53
|
};
|
|
24
54
|
const SidebarContext = createContext({
|
|
25
55
|
defaultExpanded: null,
|
|
@@ -46,10 +76,9 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
46
76
|
const [activeQuery, setActiveQuery] = useState('');
|
|
47
77
|
const [areItemsCollapsed, setItemsCollapsed] = useState(initialCollapsed);
|
|
48
78
|
const [activeHref, setActiveHref] = useState('');
|
|
49
|
-
// expandedItems is
|
|
50
|
-
// (it includes both auto-expanded parents and user-expanded groups)
|
|
79
|
+
// expandedItems is the source of truth for "open groups"
|
|
51
80
|
const [expandedItems, setExpandedItems] = useState(new Set());
|
|
52
|
-
//
|
|
81
|
+
// Keep latest items without creating effect loops if parent recreates the array
|
|
53
82
|
const itemsRef = useRef(items);
|
|
54
83
|
useEffect(() => {
|
|
55
84
|
itemsRef.current = items;
|
|
@@ -60,40 +89,41 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
60
89
|
const resetExpandAll = useCallback(() => setDefaultExpanded(null), []);
|
|
61
90
|
const setActiveLink = useCallback((href) => setActiveHref(href), []);
|
|
62
91
|
const expandItem = useCallback((href) => {
|
|
92
|
+
const h = normalizeHref(href);
|
|
63
93
|
setExpandedItems(prev => {
|
|
64
|
-
if (prev.has(
|
|
94
|
+
if (prev.has(h))
|
|
65
95
|
return prev;
|
|
66
96
|
const next = new Set(prev);
|
|
67
|
-
next.add(
|
|
97
|
+
next.add(h);
|
|
68
98
|
return next;
|
|
69
99
|
});
|
|
70
100
|
}, []);
|
|
71
101
|
const collapseItem = useCallback((href) => {
|
|
102
|
+
const h = normalizeHref(href);
|
|
72
103
|
setExpandedItems(prev => {
|
|
73
|
-
if (!prev.has(
|
|
104
|
+
if (!prev.has(h))
|
|
74
105
|
return prev;
|
|
75
106
|
const next = new Set(prev);
|
|
76
|
-
next.delete(
|
|
107
|
+
next.delete(h);
|
|
77
108
|
return next;
|
|
78
109
|
});
|
|
79
110
|
}, []);
|
|
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.
|
|
111
|
+
const isExpanded = useCallback((href) => expandedItems.has(normalizeHref(href)), [expandedItems]);
|
|
112
|
+
// Auto-expand: when active link changes, ensure its expandable parent chain is expanded.
|
|
83
113
|
useEffect(() => {
|
|
84
114
|
if (!activeHref)
|
|
85
115
|
return;
|
|
86
116
|
const currentItems = itemsRef.current;
|
|
87
|
-
const
|
|
88
|
-
const parents = path.split('.').filter(Boolean);
|
|
117
|
+
const parents = findExpandableParentChain(activeHref, currentItems);
|
|
89
118
|
if (parents.length === 0)
|
|
90
119
|
return;
|
|
91
120
|
setExpandedItems(prev => {
|
|
92
121
|
let changed = false;
|
|
93
122
|
const next = new Set(prev);
|
|
94
123
|
for (const p of parents) {
|
|
95
|
-
|
|
96
|
-
|
|
124
|
+
const norm = normalizeHref(p);
|
|
125
|
+
if (!next.has(norm)) {
|
|
126
|
+
next.add(norm);
|
|
97
127
|
changed = true;
|
|
98
128
|
}
|
|
99
129
|
}
|
|
@@ -110,8 +140,7 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
110
140
|
})
|
|
111
141
|
: items;
|
|
112
142
|
}, [items, activeQuery]);
|
|
113
|
-
// Searching should expand all
|
|
114
|
-
// We just set defaultExpanded=true, and individual components can honor it.
|
|
143
|
+
// Searching should expand all.
|
|
115
144
|
useEffect(() => {
|
|
116
145
|
if (activeQuery)
|
|
117
146
|
triggerExpandAll();
|
|
@@ -128,7 +157,7 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
128
157
|
window.localStorage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, JSON.stringify(value));
|
|
129
158
|
}
|
|
130
159
|
catch {
|
|
131
|
-
|
|
160
|
+
// ignore persist failures
|
|
132
161
|
}
|
|
133
162
|
return;
|
|
134
163
|
}
|
|
@@ -141,9 +170,9 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
141
170
|
}
|
|
142
171
|
}
|
|
143
172
|
catch {
|
|
144
|
-
|
|
173
|
+
// ignore parse failures
|
|
145
174
|
}
|
|
146
|
-
// Nothing stored
|
|
175
|
+
// Nothing stored -> responsive default (do NOT persist automatic choice)
|
|
147
176
|
setSidebarCollapsed(currentBreakpoint === 'small');
|
|
148
177
|
}, [hasExplicitInitialSidebarCollapsed, initialSidebarCollapsed]);
|
|
149
178
|
const persistCollapsed = useCallback((collapsed) => {
|
|
@@ -153,7 +182,7 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
153
182
|
window.localStorage.setItem(SIDEBAR_COLLAPSED_STORAGE_KEY, JSON.stringify(collapsed));
|
|
154
183
|
}
|
|
155
184
|
catch {
|
|
156
|
-
|
|
185
|
+
// ignore persist failures
|
|
157
186
|
}
|
|
158
187
|
}, []);
|
|
159
188
|
// Only persist user-triggered changes
|
|
@@ -163,7 +192,7 @@ export function SidebarProvider({ children, items, initialCollapsed = false, ini
|
|
|
163
192
|
}, [persistCollapsed]);
|
|
164
193
|
// Resize behavior:
|
|
165
194
|
// - only apply auto-collapse when breakpoint changes
|
|
166
|
-
// - do NOT persist
|
|
195
|
+
// - do NOT persist automatic changes (only user actions persist)
|
|
167
196
|
useEffect(() => {
|
|
168
197
|
if (typeof window === 'undefined')
|
|
169
198
|
return;
|
|
@@ -4,14 +4,9 @@ export type TableEmptyConfig = {
|
|
|
4
4
|
/** Keep it short + system-like */
|
|
5
5
|
title?: string;
|
|
6
6
|
description?: ReactNode;
|
|
7
|
-
/**
|
|
8
|
-
* Optional custom actions (rendered after built-ins).
|
|
9
|
-
* Good for injecting extra links/buttons from the table page.
|
|
10
|
-
*/
|
|
11
7
|
actions?: ReactNode;
|
|
12
8
|
onBack?: () => void;
|
|
13
9
|
onRefresh?: () => void;
|
|
14
|
-
/** New: clear filters */
|
|
15
10
|
onClearFilters?: () => void;
|
|
16
11
|
showClearFilters?: boolean;
|
|
17
12
|
clearFiltersLabel?: ReactNode;
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowLeft } from 'lucide-react';
|
|
3
|
-
import { Button } from '../../../../components/button/Button';
|
|
4
3
|
const defaultEmptyConfig = {
|
|
5
4
|
enabled: true,
|
|
6
5
|
title: 'Ingen resultater',
|
|
7
6
|
description: _jsx("span", { children: "Ingen data at vise." }),
|
|
8
7
|
showBack: false,
|
|
9
8
|
showRefresh: false,
|
|
10
|
-
// default: show button if handler exists
|
|
11
9
|
showClearFilters: true,
|
|
12
10
|
backLabel: (_jsxs(_Fragment, { children: [_jsx(ArrowLeft, { size: 16 }), "Tilbage"] })),
|
|
13
11
|
refreshLabel: _jsx(_Fragment, { children: "Indl\u00E6s igen" }),
|
|
14
12
|
clearFiltersLabel: _jsx(_Fragment, { children: "Nulstil alle filtre" }),
|
|
15
|
-
// "system-like": less hero, less vertical drama
|
|
16
13
|
className: 'dbc-flex dbc-flex-column dbc-items-start dbc-justify-start dbc-text-left dbc-gap-sm dbc-py-lg',
|
|
17
14
|
};
|
|
18
15
|
export function TableEmptyState({ config }) {
|
|
@@ -22,9 +19,5 @@ export function TableEmptyState({ config }) {
|
|
|
22
19
|
};
|
|
23
20
|
if (!merged.enabled)
|
|
24
21
|
return null;
|
|
25
|
-
|
|
26
|
-
const showRefresh = merged.showRefresh && typeof merged.onRefresh === 'function';
|
|
27
|
-
const showClearFilters = merged.showClearFilters && typeof merged.onClearFilters === 'function';
|
|
28
|
-
const hasAnyActions = Boolean(merged.actions) || showBack || showRefresh || showClearFilters;
|
|
29
|
-
return (_jsxs("div", { className: merged.className, children: [_jsx("div", { className: "dbc-text-sm", children: merged.description }), hasAnyActions && (_jsxs("div", { className: "dbc-flex dbc-gap-sm dbc-mt-xs", children: [showClearFilters && (_jsx(Button, { type: "button", variant: "secondary", onClick: merged.onClearFilters, children: merged.clearFiltersLabel })), showRefresh && (_jsx(Button, { type: "button", variant: "secondary", onClick: merged.onRefresh, children: merged.refreshLabel })), showBack && (_jsx(Button, { type: "button", variant: "secondary", onClick: merged.onBack, children: merged.backLabel })), merged.actions] }))] }));
|
|
22
|
+
return (_jsxs("div", { className: "dbc-flex dbc-flex-column dbc-gap-md dbc-pt-sm dbc-pb-sm", children: [_jsx("span", { className: "dbc-text-sm dbc-muted-text", children: merged.description }), merged.actions ? _jsx("span", { className: "dbc-flex dbc-gap-sm", children: merged.actions }) : null] }));
|
|
30
23
|
}
|