@dbcdk/react-components 0.0.69 → 0.0.70

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 (22) hide show
  1. package/dist/components/card/Card.d.ts +1 -0
  2. package/dist/components/card/Card.js +2 -2
  3. package/dist/components/forms/select/Select.js +1 -1
  4. package/dist/components/json-viewer/JsonViewer.d.ts +2 -1
  5. package/dist/components/json-viewer/JsonViewer.js +60 -14
  6. package/dist/components/json-viewer/JsonViewer.module.css +11 -23
  7. package/dist/components/nav-bar/NavBar.d.ts +1 -0
  8. package/dist/components/sidebar/Sidebar.d.ts +2 -1
  9. package/dist/components/sidebar/Sidebar.js +2 -2
  10. package/dist/components/sidebar/components/SidebarItem.d.ts +2 -1
  11. package/dist/components/sidebar/components/SidebarItem.js +2 -2
  12. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +2 -1
  13. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +5 -8
  14. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +2 -1
  15. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +2 -2
  16. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +2 -1
  17. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +5 -2
  18. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +11 -2
  19. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +4 -4
  20. package/dist/components/sidebar/components/sidenav-filteirng/SidenavFiltering.d.ts +4 -1
  21. package/dist/components/sidebar/components/sidenav-filteirng/SidenavFiltering.js +2 -2
  22. package/package.json +1 -1
@@ -27,6 +27,7 @@ export interface CardProps {
27
27
  */
28
28
  link?: ReactNode;
29
29
  width?: 25 | 33 | 50 | 66 | 75 | 100;
30
+ headlineSize?: 1 | 2 | 3 | 4 | 5 | 6;
30
31
  }
31
32
  type CardComponent = ((props: CardProps) => JSX.Element) & {
32
33
  Meta: typeof CardMeta;
@@ -24,7 +24,7 @@ function getVariantClass(variant, s) {
24
24
  return s.variantDefault;
25
25
  }
26
26
  }
27
- function CardImpl({ title, loading = false, variant = 'default', size = 'md', headerMarker = true, headerIcon, severity, image, imgPlacement = 'left', mediaWidth, actions, headerMeta, sectionTitle, showSectionDivider = false, children, link, width, }) {
27
+ function CardImpl({ title, loading = false, variant = 'default', size = 'md', headerMarker = true, headerIcon, severity, image, imgPlacement = 'left', mediaWidth, actions, headerMeta, sectionTitle, showSectionDivider = false, children, link, width, headlineSize = 4, }) {
28
28
  const outerStyle = width ? { ['--width']: `${width}%` } : undefined;
29
29
  const mediaStyle = mediaWidth
30
30
  ? { ['--card-media-width']: `${mediaWidth}px` }
@@ -35,7 +35,7 @@ function CardImpl({ title, loading = false, variant = 'default', size = 'md', he
35
35
  const showSection = !loading && (showSectionDivider || !!sectionTitle);
36
36
  const showBody = !loading && !!children;
37
37
  const showActions = !loading && !!actions;
38
- const inner = (_jsxs("div", { className: `${styles.inner} ${innerPlacementClass}`, children: [image ? (_jsx("div", { className: styles.media, style: mediaStyle, children: image })) : null, _jsxs("div", { className: styles.content, children: [hasHeader ? (_jsxs("header", { className: styles.header, children: [title ? (_jsx(Headline, { severity: severity, marker: headerMarker, icon: headerIcon, size: 4, weight: 500, disableMargin: true, children: title })) : null, headerMeta ? _jsx("div", { className: styles.headerMeta, children: headerMeta }) : null] })) : null, loading ? (_jsx("div", { className: styles.loadingList, "aria-busy": "true", "aria-live": "polite", children: Array.from({ length: 4 }, (_, index) => (_jsxs("div", { className: styles.loadingRow, children: [_jsx(SkeletonLoaderItem, {}), _jsx(SkeletonLoaderItem, { width: "100%" })] }, index))) })) : null, showSection ? (_jsxs("div", { className: styles.section, children: [showSectionDivider ? _jsx("div", { className: styles.sectionDivider }) : null, sectionTitle ? _jsx("div", { className: styles.sectionTitle, children: sectionTitle }) : null] })) : null, showBody ? _jsx("div", { className: styles.body, children: children }) : null, showActions ? _jsx("div", { className: styles.actions, children: actions }) : null] })] }));
38
+ const inner = (_jsxs("div", { className: `${styles.inner} ${innerPlacementClass}`, children: [image ? (_jsx("div", { className: styles.media, style: mediaStyle, children: image })) : null, _jsxs("div", { className: styles.content, children: [hasHeader ? (_jsxs("header", { className: styles.header, children: [title ? (_jsx(Headline, { severity: severity, marker: headerMarker, icon: headerIcon, size: headlineSize, weight: 500, disableMargin: true, children: title })) : null, headerMeta ? _jsx("div", { className: styles.headerMeta, children: headerMeta }) : null] })) : null, loading ? (_jsx("div", { className: styles.loadingList, "aria-busy": "true", "aria-live": "polite", children: Array.from({ length: 4 }, (_, index) => (_jsxs("div", { className: styles.loadingRow, children: [_jsx(SkeletonLoaderItem, {}), _jsx(SkeletonLoaderItem, { width: "100%" })] }, index))) })) : null, showSection ? (_jsxs("div", { className: styles.section, children: [showSectionDivider ? _jsx("div", { className: styles.sectionDivider }) : null, sectionTitle ? _jsx("div", { className: styles.sectionTitle, children: sectionTitle }) : null] })) : null, showBody ? _jsx("div", { className: styles.body, children: children }) : null, showActions ? _jsx("div", { className: styles.actions, children: actions }) : null] })] }));
39
39
  // keep existing behavior
40
40
  const cardContent = link ? _jsx(Hyperlink, { children: link }) : inner;
41
41
  return (_jsx("div", { className: `${styles.outerContainer} ${styles[size]}`, style: outerStyle, children: _jsx("div", { className: `${styles.container} ${variantClass}`, children: cardContent }) }));
@@ -157,7 +157,7 @@ export function Select({ label, error, helpText, orientation = 'vertical', label
157
157
  ids.push(tooltipId);
158
158
  return ids.length ? ids.join(' ') : undefined;
159
159
  })();
160
- return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef, open: open, onOpenChange: next => {
160
+ return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, modified: modified, children: [_jsx(Popover, { ref: popoverRef, open: open, fullWidth: fullWidth, onOpenChange: next => {
161
161
  setOpen(next);
162
162
  if (next)
163
163
  resetActiveToSelected();
@@ -3,9 +3,10 @@ export interface JsonViewerProps {
3
3
  value: unknown;
4
4
  defaultExpandedDepth?: number;
5
5
  expandAll?: boolean;
6
+ searchDebounceMs?: number;
6
7
  helperText?: string;
7
8
  searchPlaceholder?: string;
8
9
  emptySearchText?: string;
9
10
  className?: string;
10
11
  }
11
- export declare function JsonViewer({ value, defaultExpandedDepth, expandAll, helperText, searchPlaceholder, emptySearchText, className, }: JsonViewerProps): JSX.Element;
12
+ export declare function JsonViewer({ value, defaultExpandedDepth, expandAll, searchDebounceMs, helperText, searchPlaceholder, emptySearchText, className, }: JsonViewerProps): JSX.Element;
@@ -132,12 +132,44 @@ function isJsonValue(value) {
132
132
  }
133
133
  return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
134
134
  }
135
- export function JsonViewer({ value, defaultExpandedDepth = 2, expandAll = false, helperText, searchPlaceholder = 'Søg i nøgler, stier og værdier', emptySearchText = 'Ingen matchende noder fundet.', className, }) {
135
+ function collectExpandableNodeIds(value, path = []) {
136
+ if (!isJsonArray(value) && !isJsonObject(value))
137
+ return [];
138
+ const nodeId = getNodeId(path);
139
+ const childEntries = isJsonArray(value)
140
+ ? value.map((item, index) => [String(index), item])
141
+ : Object.entries(value);
142
+ return [
143
+ nodeId,
144
+ ...childEntries.flatMap(([childKey, childValue]) => collectExpandableNodeIds(childValue, [...path, childKey])),
145
+ ];
146
+ }
147
+ function collectInitiallyExpandedNodeIds(value, defaultExpandedDepth, expandAll, depth = 0, path = []) {
148
+ if (!isJsonArray(value) && !isJsonObject(value))
149
+ return [];
150
+ const nodeId = getNodeId(path);
151
+ const childEntries = isJsonArray(value)
152
+ ? value.map((item, index) => [String(index), item])
153
+ : Object.entries(value);
154
+ const includeNode = expandAll || depth < defaultExpandedDepth;
155
+ const nested = childEntries.flatMap(([childKey, childValue]) => collectInitiallyExpandedNodeIds(childValue, defaultExpandedDepth, expandAll, depth + 1, [
156
+ ...path,
157
+ childKey,
158
+ ]));
159
+ return includeNode ? [nodeId, ...nested] : nested;
160
+ }
161
+ export function JsonViewer({ value, defaultExpandedDepth = 2, expandAll = false, searchDebounceMs = 180, helperText, searchPlaceholder = 'Søg i nøgler, stier og værdier', emptySearchText = 'Ingen matchende noder fundet.', className, }) {
162
+ const [queryInput, setQueryInput] = useState('');
136
163
  const [query, setQuery] = useState('');
137
- const [globalExpandMode, setGlobalExpandMode] = useState(expandAll ? 'expanded' : 'default');
138
164
  const normalizedQuery = normalizeSearch(query);
139
165
  const inputId = useId();
140
166
  const { copiedId, setCopiedId } = useClipboardStatus();
167
+ useEffect(() => {
168
+ const timeoutId = window.setTimeout(() => {
169
+ setQuery(queryInput);
170
+ }, searchDebounceMs);
171
+ return () => window.clearTimeout(timeoutId);
172
+ }, [queryInput, searchDebounceMs]);
141
173
  const safeValue = useMemo(() => {
142
174
  if (isJsonValue(value))
143
175
  return value;
@@ -149,6 +181,8 @@ export function JsonViewer({ value, defaultExpandedDepth = 2, expandAll = false,
149
181
  };
150
182
  }, [value]);
151
183
  const matches = useMemo(() => (normalizedQuery ? collectSearchMatches(safeValue, normalizedQuery) : new Map()), [normalizedQuery, safeValue]);
184
+ const allExpandableNodeIds = useMemo(() => collectExpandableNodeIds(safeValue), [safeValue]);
185
+ const [expandedNodeIds, setExpandedNodeIds] = useState(() => new Set(collectInitiallyExpandedNodeIds(safeValue, defaultExpandedDepth, expandAll)));
152
186
  const hasResults = normalizedQuery ? matches.size > 0 : true;
153
187
  const handleCopy = async (nodeId, rawValue) => {
154
188
  try {
@@ -159,17 +193,35 @@ export function JsonViewer({ value, defaultExpandedDepth = 2, expandAll = false,
159
193
  setCopiedId(null);
160
194
  }
161
195
  };
162
- return (_jsxs("section", { className: [styles.viewer, className].filter(Boolean).join(' '), children: [_jsxs("div", { className: styles.toolbar, children: [_jsxs("div", { className: styles.toolbarTop, children: [_jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [_jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }), _jsx("input", { id: inputId, type: "search", value: query, onChange: event => setQuery(event.target.value), placeholder: searchPlaceholder, className: styles.searchInput }), query ? (_jsx("button", { type: "button", className: styles.clearButton, "aria-label": "Ryd s\u00F8gning", onClick: () => setQuery(''), children: _jsx(X, { "aria-hidden": "true" }) })) : null] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.toolbarButton, onClick: () => setGlobalExpandMode('expanded'), disabled: Boolean(normalizedQuery), children: "Fold alle ud" }), _jsx("button", { type: "button", className: styles.toolbarButton, onClick: () => setGlobalExpandMode('collapsed'), disabled: Boolean(normalizedQuery), children: "Fold alle sammen" })] })] }), _jsxs("div", { className: styles.metaRow, children: [helperText ? _jsx("span", { className: styles.helperText, children: helperText }) : _jsx("span", {}), _jsx("span", { className: styles.statusText, children: normalizedQuery
196
+ const handleExpandAll = () => {
197
+ setExpandedNodeIds(new Set(allExpandableNodeIds));
198
+ };
199
+ const handleCollapseAll = () => {
200
+ setExpandedNodeIds(new Set());
201
+ };
202
+ const handleToggleNode = (nodeId) => {
203
+ setExpandedNodeIds(current => {
204
+ const next = new Set(current);
205
+ if (next.has(nodeId))
206
+ next.delete(nodeId);
207
+ else
208
+ next.add(nodeId);
209
+ return next;
210
+ });
211
+ };
212
+ return (_jsxs("section", { className: [styles.viewer, className].filter(Boolean).join(' '), children: [_jsxs("div", { className: styles.toolbar, children: [_jsxs("div", { className: styles.toolbarTop, children: [_jsxs("label", { htmlFor: inputId, className: styles.searchField, children: [_jsx(Search, { className: styles.searchIcon, "aria-hidden": "true" }), _jsx("input", { id: inputId, type: "search", value: queryInput, onChange: event => setQueryInput(event.target.value), placeholder: searchPlaceholder, className: styles.searchInput }), queryInput ? (_jsx("button", { type: "button", className: styles.clearButton, "aria-label": "Ryd s\u00F8gning", onClick: () => {
213
+ setQueryInput('');
214
+ setQuery('');
215
+ }, children: _jsx(X, { "aria-hidden": "true" }) })) : null] }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.toolbarButton, onClick: handleExpandAll, disabled: Boolean(normalizedQuery), children: "Fold alle ud" }), _jsx("button", { type: "button", className: styles.toolbarButton, onClick: handleCollapseAll, disabled: Boolean(normalizedQuery), children: "Fold alle sammen" })] })] }), _jsxs("div", { className: styles.metaRow, children: [helperText ? _jsx("span", { className: styles.helperText, children: helperText }) : _jsx("span", {}), _jsx("span", { className: styles.statusText, children: normalizedQuery
163
216
  ? `${matches.size} matchende node${matches.size === 1 ? '' : 'r'}`
164
- : '' })] })] }), _jsx("div", { className: styles.treePane, role: "tree", "aria-label": "JSON-visning", children: hasResults ? (_jsx(JsonNode, { keyName: undefined, path: [], value: safeValue, depth: 0, defaultExpandedDepth: defaultExpandedDepth, query: normalizedQuery, matches: matches, globalExpandMode: globalExpandMode, copiedId: copiedId, onCopy: handleCopy })) : (_jsx("div", { className: styles.emptyState, children: emptySearchText })) })] }));
217
+ : '' })] })] }), _jsx("div", { className: styles.treePane, role: "tree", "aria-label": "JSON-visning", children: hasResults ? (_jsx(JsonNode, { keyName: undefined, path: [], value: safeValue, depth: 0, defaultExpandedDepth: defaultExpandedDepth, query: normalizedQuery, matches: matches, expandedNodeIds: expandedNodeIds, onToggleNode: handleToggleNode, copiedId: copiedId, onCopy: handleCopy })) : (_jsx("div", { className: styles.emptyState, children: emptySearchText })) })] }));
165
218
  }
166
- function JsonNode({ keyName, path, value, depth, defaultExpandedDepth, query, matches, globalExpandMode, copiedId, onCopy, }) {
219
+ function JsonNode({ keyName, path, value, depth, defaultExpandedDepth, query, matches, expandedNodeIds, onToggleNode, copiedId, onCopy, }) {
167
220
  const nodeId = getNodeId(path);
168
221
  const nodeType = getValueType(value);
169
222
  const match = matches.get(nodeId);
170
223
  const shouldRender = query ? Boolean(match) : true;
171
224
  const forceOpen = query ? Boolean(match === null || match === void 0 ? void 0 : match.descendant) : false;
172
- const [isExpanded, setIsExpanded] = useState(() => depth < defaultExpandedDepth);
173
225
  if (!shouldRender)
174
226
  return null;
175
227
  if (nodeType !== 'array' && nodeType !== 'object') {
@@ -185,14 +237,8 @@ function JsonNode({ keyName, path, value, depth, defaultExpandedDepth, query, ma
185
237
  ? Object.entries(value)
186
238
  : [];
187
239
  const summary = getSummary(value);
188
- const expanded = query || forceOpen
189
- ? true
190
- : globalExpandMode === 'expanded'
191
- ? true
192
- : globalExpandMode === 'collapsed'
193
- ? false
194
- : isExpanded;
195
- return (_jsxs("div", { className: styles.node, style: { ['--json-indent']: depth }, children: [_jsx("div", { className: styles.row, "data-match": (match === null || match === void 0 ? void 0 : match.self) || undefined, children: _jsxs("button", { type: "button", className: styles.toggle, onClick: () => setIsExpanded(current => !current), "aria-expanded": expanded, children: [expanded ? _jsx(ChevronDown, { "aria-hidden": "true" }) : _jsx(ChevronRight, { "aria-hidden": "true" }), _jsxs("span", { className: styles.rowLabel, children: [keyName !== undefined ? (_jsxs("span", { className: styles.keyChunk, children: [_jsx("span", { className: styles.key, children: _jsx(HighlightText, { text: keyName, query: query }) }), _jsx("span", { className: styles.punctuation, children: ":" })] })) : (_jsx("span", { className: styles.rootLabel, children: "$" })), _jsx("span", { className: styles.bracket, children: isJsonArray(value) ? '[' : '{' }), _jsx("span", { className: styles.summary, children: _jsx(HighlightText, { text: summary, query: query }) }), _jsx("span", { className: styles.bracket, children: isJsonArray(value) ? ']' : '}' })] })] }) }), query && !(match === null || match === void 0 ? void 0 : match.self) ? (_jsx("div", { className: styles.pathHint, children: _jsx(HighlightText, { text: getPathLabel(path), query: query }) })) : null, expanded ? (_jsx("div", { className: styles.children, role: depth === 0 ? 'group' : undefined, children: entries.map(([childKey, childValue]) => (_jsx(JsonNode, { keyName: isJsonArray(value) ? `[${childKey}]` : childKey, path: [...path, childKey], value: childValue, depth: depth + 1, defaultExpandedDepth: defaultExpandedDepth, query: query, matches: matches, globalExpandMode: globalExpandMode, copiedId: copiedId, onCopy: onCopy }, `${nodeId}-${childKey}`))) })) : null] }));
240
+ const expanded = query || forceOpen ? true : expandedNodeIds.has(nodeId);
241
+ return (_jsxs("div", { className: styles.node, style: { ['--json-indent']: depth }, children: [_jsx("div", { className: styles.row, "data-match": (match === null || match === void 0 ? void 0 : match.self) || undefined, children: _jsxs("button", { type: "button", className: styles.toggle, onClick: () => onToggleNode(nodeId), "aria-expanded": expanded, children: [expanded ? _jsx(ChevronDown, { "aria-hidden": "true" }) : _jsx(ChevronRight, { "aria-hidden": "true" }), _jsxs("span", { className: styles.rowLabel, children: [keyName !== undefined ? (_jsxs("span", { className: styles.keyChunk, children: [_jsx("span", { className: styles.key, children: _jsx(HighlightText, { text: keyName, query: query }) }), _jsx("span", { className: styles.punctuation, children: ":" })] })) : (_jsx("span", { className: styles.rootLabel, children: "$" })), _jsx("span", { className: styles.bracket, children: isJsonArray(value) ? '[' : '{' }), _jsx("span", { className: styles.summary, children: _jsx(HighlightText, { text: summary, query: query }) }), _jsx("span", { className: styles.bracket, children: isJsonArray(value) ? ']' : '}' })] })] }) }), query && !(match === null || match === void 0 ? void 0 : match.self) ? (_jsx("div", { className: styles.pathHint, children: _jsx(HighlightText, { text: getPathLabel(path), query: query }) })) : null, expanded ? (_jsx("div", { className: styles.children, role: depth === 0 ? 'group' : undefined, children: entries.map(([childKey, childValue]) => (_jsx(JsonNode, { keyName: isJsonArray(value) ? `[${childKey}]` : childKey, path: [...path, childKey], value: childValue, depth: depth + 1, defaultExpandedDepth: defaultExpandedDepth, query: query, matches: matches, expandedNodeIds: expandedNodeIds, onToggleNode: onToggleNode, copiedId: copiedId, onCopy: onCopy }, `${nodeId}-${childKey}`))) })) : null] }));
196
242
  }
197
243
  function capitalize(value) {
198
244
  return value.charAt(0).toUpperCase() + value.slice(1);
@@ -1,12 +1,7 @@
1
1
  .viewer {
2
- --json-pane-bg:
3
- linear-gradient(
4
- 180deg,
5
- color-mix(in oklab, var(--color-bg-inverse) 96%, white 4%),
6
- color-mix(in oklab, var(--color-bg-inverse) 92%, black 8%)
7
- );
8
- --json-pane-border: color-mix(in oklab, var(--color-fg-inverse) 14%, transparent);
9
- --json-pane-border-strong: color-mix(in oklab, var(--color-fg-inverse) 24%, transparent);
2
+ --json-pane-bg: var(--color-bg-inverse);
3
+ --json-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
4
+ --json-pane-border-strong: color-mix(in oklab, var(--color-fg-inverse) 18%, transparent);
10
5
  --json-pane-text: var(--color-fg-inverse);
11
6
  --json-pane-muted: color-mix(in oklab, var(--color-fg-inverse) 64%, transparent);
12
7
  --json-pane-subtle: color-mix(in oklab, var(--color-fg-inverse) 44%, transparent);
@@ -26,7 +21,7 @@
26
21
  color: var(--json-pane-text);
27
22
  border: var(--border-width-thin) solid var(--json-pane-border);
28
23
  border-radius: var(--border-radius-xl);
29
- box-shadow: var(--shadow-lg);
24
+ box-shadow: var(--shadow-md);
30
25
  padding: var(--spacing-sm);
31
26
  font-family: var(--font-family-mono);
32
27
  }
@@ -52,7 +47,7 @@
52
47
  padding-inline: var(--spacing-sm);
53
48
  border: var(--border-width-thin) solid var(--json-pane-border);
54
49
  border-radius: var(--border-radius-lg);
55
- background: color-mix(in oklab, var(--color-bg-inverse) 85%, var(--color-bg-surface) 15%);
50
+ background: transparent;
56
51
  }
57
52
 
58
53
  .searchField:focus-within {
@@ -118,7 +113,7 @@
118
113
  padding-inline: var(--spacing-sm);
119
114
  border: var(--border-width-thin) solid var(--json-pane-border);
120
115
  border-radius: var(--border-radius-md);
121
- background: color-mix(in oklab, var(--color-bg-inverse) 82%, var(--color-bg-surface) 18%);
116
+ background: transparent;
122
117
  color: var(--json-pane-text);
123
118
  font: inherit;
124
119
  cursor: pointer;
@@ -147,15 +142,8 @@
147
142
  display: flex;
148
143
  flex-direction: column;
149
144
  gap: var(--spacing-2xs);
150
- border: var(--border-width-thin) solid var(--json-pane-border);
151
- border-radius: var(--border-radius-lg);
152
- background:
153
- linear-gradient(
154
- 180deg,
155
- color-mix(in oklab, var(--color-bg-inverse) 89%, white 11%),
156
- color-mix(in oklab, var(--color-bg-inverse) 94%, black 6%)
157
- );
158
- padding: var(--spacing-sm);
145
+ background: transparent;
146
+ padding: 0;
159
147
  overflow: auto;
160
148
  }
161
149
 
@@ -178,7 +166,7 @@
178
166
  }
179
167
 
180
168
  .row[data-match='true'] {
181
- background: linear-gradient(90deg, var(--json-pane-selected), transparent 70%);
169
+ background: var(--json-pane-selected);
182
170
  }
183
171
 
184
172
  .toggle,
@@ -272,7 +260,7 @@
272
260
  top: 0;
273
261
  bottom: 0;
274
262
  width: 1px;
275
- background: color-mix(in oklab, var(--color-fg-inverse) 10%, transparent);
263
+ background: color-mix(in oklab, var(--color-fg-inverse) 8%, transparent);
276
264
  pointer-events: none;
277
265
  }
278
266
 
@@ -328,7 +316,7 @@
328
316
 
329
317
  .highlight {
330
318
  color: var(--color-fg-inverse);
331
- background: color-mix(in oklab, var(--json-pane-highlight) 78%, transparent);
319
+ background: color-mix(in oklab, var(--json-pane-highlight) 58%, transparent);
332
320
  border-radius: var(--border-radius-sm);
333
321
  padding-inline: 0.1em;
334
322
  }
@@ -4,6 +4,7 @@ export type NavBarItem = NavBarLinkItem | NavBarExpandableItem | NavBarGroupItem
4
4
  type NavBarBase = {
5
5
  label: string;
6
6
  icon?: ReactNode;
7
+ iconWidth?: string | number;
7
8
  enabled?: boolean;
8
9
  tags?: string[];
9
10
  truncateLabel?: boolean;
@@ -11,11 +11,12 @@ interface SidebarProps {
11
11
  activeLink?: string;
12
12
  version?: string | number;
13
13
  hideSearch?: boolean;
14
+ searchPlaceholder?: string;
14
15
  footer?: React.ReactNode;
15
16
  resizable?: boolean;
16
17
  defaultWidth?: number;
17
18
  minWidth?: number;
18
19
  storageKey?: string;
19
20
  }
20
- export declare function Sidebar({ items, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarProps): JSX.Element;
21
+ export declare function Sidebar({ items, productLogo, activeLink, version, hideSearch, searchPlaceholder, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarProps): JSX.Element;
21
22
  export {};
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { SidebarContainer } from './components/sidebar-container/SidebarContainer';
3
3
  import { SidebarProvider } from './providers/SidebarProvider';
4
- export function Sidebar({ items, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }) {
5
- return (_jsx(SidebarProvider, { items: items, children: _jsx(SidebarContainer, { productLogo: productLogo, activeLink: activeLink, version: version, hideSearch: hideSearch, footer: footer, resizable: resizable, defaultWidth: defaultWidth, minWidth: minWidth, storageKey: storageKey }) }));
4
+ export function Sidebar({ items, productLogo, activeLink, version, hideSearch, searchPlaceholder, footer, resizable, defaultWidth, minWidth, storageKey, }) {
5
+ return (_jsx(SidebarProvider, { items: items, children: _jsx(SidebarContainer, { productLogo: productLogo, activeLink: activeLink, version: version, hideSearch: hideSearch, searchPlaceholder: searchPlaceholder, footer: footer, resizable: resizable, defaultWidth: defaultWidth, minWidth: minWidth, storageKey: storageKey }) }));
6
6
  }
@@ -4,7 +4,8 @@ interface SidebarItemProps {
4
4
  label: string;
5
5
  icon: React.ReactNode;
6
6
  href?: string;
7
+ iconWidth?: string | number;
7
8
  truncateLabel?: boolean;
8
9
  }
9
- export declare function SidebarItem({ component: Component, label, icon, href, truncateLabel, }: SidebarItemProps): React.ReactNode;
10
+ export declare function SidebarItem({ component: Component, label, icon, href, iconWidth, truncateLabel, }: SidebarItemProps): React.ReactNode;
10
11
  export {};
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { SidebarItemContent } from './sidebar-item-content/SidebarItemContent';
3
- export function SidebarItem({ component: Component, label, icon, href, truncateLabel, }) {
3
+ export function SidebarItem({ component: Component, label, icon, href, iconWidth, truncateLabel, }) {
4
4
  if (!Component) {
5
5
  return null;
6
6
  }
7
- return (_jsx(Component, { children: _jsx(SidebarItemContent, { icon: icon, label: label, href: href, truncateLabel: truncateLabel }) }));
7
+ return (_jsx(Component, { children: _jsx(SidebarItemContent, { icon: icon, iconWidth: iconWidth, label: label, href: href, truncateLabel: truncateLabel }) }));
8
8
  }
@@ -6,7 +6,8 @@ type ExpandableSidebarItemProps = {
6
6
  component: React.ElementType;
7
7
  icon: React.ReactNode;
8
8
  href: string;
9
+ iconWidth?: string | number;
9
10
  truncateLabel?: boolean;
10
11
  };
11
- export declare function ExpandableSidebarItem({ items, label, icon, component: Component, href, truncateLabel, }: ExpandableSidebarItemProps): React.ReactNode;
12
+ export declare function ExpandableSidebarItem({ items, label, icon, component: Component, href, iconWidth, truncateLabel, }: ExpandableSidebarItemProps): React.ReactNode;
12
13
  export {};
@@ -10,14 +10,11 @@ import { SidebarItemContent } from '../sidebar-item-content/SidebarItemContent';
10
10
  import { SidebarItem } from '../SidebarItem';
11
11
  const isGroup = (item) => item.type === 'group';
12
12
  const isExpandable = (item) => item.type === 'expandable';
13
- export function ExpandableSidebarItem({ items, label, icon, component: Component, href, truncateLabel, }) {
13
+ export function ExpandableSidebarItem({ items, label, icon, component: Component, href, iconWidth, truncateLabel, }) {
14
14
  const { defaultExpanded, resetExpandAll, isSidebarCollapsed, handleSidebarCollapseChange, expandItem, collapseItem, isExpanded, } = useSidebar();
15
15
  // Local-only state for animation coordination
16
16
  const [closing, setClosing] = useState(false);
17
- const [ready, setReady] = useState(false);
18
- useEffect(() => {
19
- setReady(true);
20
- }, []);
17
+ const ready = true;
21
18
  // Single source of truth: expanded comes from provider state
22
19
  const expanded = useMemo(() => isExpanded(href), [href, isExpanded]);
23
20
  // Expand-all behavior (e.g. search)
@@ -58,9 +55,9 @@ export function ExpandableSidebarItem({ items, label, icon, component: Component
58
55
  return (_jsxs("div", { className: styles.group, children: [_jsx("div", { className: styles.groupLabel, children: item.label }), (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child, idx) => renderNavItem(child, `${key}-${idx}`))] }, key));
59
56
  }
60
57
  if (isExpandable(item)) {
61
- return (_jsx(ExpandableChild, { items: (_b = item.children) !== null && _b !== void 0 ? _b : [], label: item.label, icon: item.icon, href: item.href, component: item.component }, key));
58
+ return (_jsx(ExpandableChild, { items: (_b = item.children) !== null && _b !== void 0 ? _b : [], label: item.label, icon: item.icon, href: item.href, iconWidth: item.iconWidth, component: item.component }, key));
62
59
  }
63
- return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href, truncateLabel: item.truncateLabel }, key));
60
+ return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href, iconWidth: item.iconWidth, truncateLabel: item.truncateLabel }, key));
64
61
  };
65
- return (_jsxs("div", { className: `${styles.container}`, children: [_jsx(Component, { onClick: () => toggleAccordion(undefined, true), children: _jsx(SidebarItemContent, { headerStyle: expanded, icon: icon, label: label, href: href, disableActiveStyles: expanded, truncateLabel: truncateLabel, suffixIcon: isSidebarCollapsed ? null : (_jsx(Button, { variant: "outlined", onClick: toggleAccordion, children: _jsx(ChevronDown, { className: `${styles.chevron} ${expanded ? styles.chevronExpanded : ''}` }) })) }) }), expanded && !isSidebarCollapsed && (_jsx("div", { onAnimationEnd: handleAnimationEnd, className: `${styles.childrenContainer} ${closing ? 'animate--collapse' : ''} ${expanded ? 'animate--expand' : 'visually-hidden'}`, children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`)) }))] }));
62
+ return (_jsxs("div", { className: `${styles.container}`, children: [_jsx(Component, { onClick: () => toggleAccordion(undefined, true), children: _jsx(SidebarItemContent, { headerStyle: expanded, icon: icon, iconWidth: iconWidth, label: label, href: href, disableActiveStyles: expanded, truncateLabel: truncateLabel, suffixIcon: isSidebarCollapsed ? null : (_jsx(Button, { variant: "outlined", onClick: toggleAccordion, children: _jsx(ChevronDown, { className: `${styles.chevron} ${expanded ? styles.chevronExpanded : ''}` }) })) }) }), expanded && !isSidebarCollapsed && (_jsx("div", { onAnimationEnd: handleAnimationEnd, className: `${styles.childrenContainer} ${closing ? 'animate--collapse' : ''} ${expanded ? 'animate--expand' : 'visually-hidden'}`, children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`)) }))] }));
66
63
  }
@@ -6,11 +6,12 @@ interface SidebarContainerProps {
6
6
  activeLink?: string;
7
7
  version?: string | number;
8
8
  hideSearch?: boolean;
9
+ searchPlaceholder?: string;
9
10
  footer?: ReactNode;
10
11
  resizable?: boolean;
11
12
  defaultWidth?: number;
12
13
  minWidth?: number;
13
14
  storageKey?: string;
14
15
  }
15
- export declare function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarContainerProps): JSX.Element;
16
+ export declare function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, searchPlaceholder, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarContainerProps): JSX.Element;
16
17
  export {};
@@ -39,7 +39,7 @@ function removeStoredWidth(key) {
39
39
  // ignore
40
40
  }
41
41
  }
42
- export function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth = 240, minWidth = 160, storageKey, }) {
42
+ export function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, searchPlaceholder = 'Filtrer menu', footer, resizable, defaultWidth = 240, minWidth = 160, storageKey, }) {
43
43
  const { isSidebarCollapsed, handleSidebarCollapseChange } = useSidebar();
44
44
  // Always start from defaultWidth so server and client render identical HTML.
45
45
  // localStorage is read in a useEffect after hydration to avoid SSR mismatch.
@@ -149,5 +149,5 @@ export function SidebarContainer({ logo, productLogo, activeLink, version, hideS
149
149
  const containerStyle = useMemo(() => ({
150
150
  '--sidebar-width': `${sidebarWidth}px`,
151
151
  }), [sidebarWidth]);
152
- return (_jsxs("div", { ref: containerRef, role: "complementary", className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''} ${isResizing ? styles.resizing : ''}`, style: containerStyle, children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: styles.productHeader, children: [_jsx("div", { className: styles.productLogo, children: productLogo }), _jsx(Button, { size: "md", variant: "inline", shape: "round", "aria-label": "Collapse sidebar", icon: _jsx(ChevronLeft, { className: isSidebarCollapsed ? styles.collapsedIcon : '' }), onClick: () => handleSidebarCollapseChange(!isSidebarCollapsed) })] }) }), _jsxs("div", { className: styles.content, children: [!hideSearch && (_jsx("div", { className: styles.filter, children: _jsx(SidenavFiltering, {}) })), _jsx("div", { className: `${styles.links} hideScrollBar`, children: _jsx(SidebarItems, { activeLink: activeLink }) })] }), footer && _jsx("div", { className: styles.footerSlot, children: footer }), _jsxs("div", { className: styles.footer, children: [_jsx("div", { className: styles.companyLogo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) }), version && _jsx("div", { className: `${styles.version} dbc-muted-text dbc-sm-text`, children: version })] }), resizable && (_jsx("div", { className: styles.resizeHandle, role: "separator", "aria-label": "Resize sidebar", "aria-orientation": "vertical", "aria-valuemin": Math.round(minWidth), "aria-valuemax": ariaMaxWidth !== undefined ? Math.round(ariaMaxWidth) : undefined, "aria-valuenow": Math.round(sidebarWidth), tabIndex: isSidebarCollapsed ? -1 : 0, onPointerDown: onResizePointerDown, onPointerMove: onResizePointerMove, onPointerUp: endResizeDrag, onPointerCancel: endResizeDrag, onDoubleClick: resetWidth, onKeyDown: onKeyDown }))] }));
152
+ return (_jsxs("div", { ref: containerRef, role: "complementary", className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''} ${isResizing ? styles.resizing : ''}`, style: containerStyle, children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: styles.productHeader, children: [_jsx("div", { className: styles.productLogo, children: productLogo }), _jsx(Button, { size: "md", variant: "inline", shape: "round", "aria-label": "Collapse sidebar", icon: _jsx(ChevronLeft, { className: isSidebarCollapsed ? styles.collapsedIcon : '' }), onClick: () => handleSidebarCollapseChange(!isSidebarCollapsed) })] }) }), _jsxs("div", { className: styles.content, children: [!hideSearch && (_jsx("div", { className: styles.filter, children: _jsx(SidenavFiltering, { placeholder: searchPlaceholder }) })), _jsx("div", { className: `${styles.links} hideScrollBar`, children: _jsx(SidebarItems, { activeLink: activeLink }) })] }), footer && _jsx("div", { className: styles.footerSlot, children: footer }), _jsxs("div", { className: styles.footer, children: [_jsx("div", { className: styles.companyLogo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) }), version && _jsx("div", { className: `${styles.version} dbc-muted-text dbc-sm-text`, children: version })] }), resizable && (_jsx("div", { className: styles.resizeHandle, role: "separator", "aria-label": "Resize sidebar", "aria-orientation": "vertical", "aria-valuemin": Math.round(minWidth), "aria-valuemax": ariaMaxWidth !== undefined ? Math.round(ariaMaxWidth) : undefined, "aria-valuenow": Math.round(sidebarWidth), tabIndex: isSidebarCollapsed ? -1 : 0, onPointerDown: onResizePointerDown, onPointerMove: onResizePointerMove, onPointerUp: endResizeDrag, onPointerCancel: endResizeDrag, onDoubleClick: resetWidth, onKeyDown: onKeyDown }))] }));
153
153
  }
@@ -5,8 +5,9 @@ export interface SidebarItemContentProps {
5
5
  label: React.ReactNode;
6
6
  suffixIcon?: React.ReactNode;
7
7
  href?: string;
8
+ iconWidth?: string | number;
8
9
  disableActiveStyles?: boolean;
9
10
  headerStyle?: boolean;
10
11
  truncateLabel?: boolean;
11
12
  }
12
- export declare function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles, headerStyle, truncateLabel, }: SidebarItemContentProps): JSX.Element;
13
+ export declare function SidebarItemContent({ icon, label, suffixIcon, href, iconWidth, disableActiveStyles, headerStyle, truncateLabel, }: SidebarItemContentProps): JSX.Element;
@@ -2,7 +2,10 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import styles from './SidebarItemContent.module.css';
4
4
  import { useSidebar } from '../../providers/SidebarProvider';
5
- export function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles = false, headerStyle, truncateLabel = false, }) {
5
+ export function SidebarItemContent({ icon, label, suffixIcon, href, iconWidth, disableActiveStyles = false, headerStyle, truncateLabel = false, }) {
6
6
  const { activeLink, isSidebarCollapsed } = useSidebar();
7
- return (_jsxs("span", { className: `${styles.container} ${!disableActiveStyles && activeLink === href ? styles.active : ''} ${isSidebarCollapsed ? styles.collapsed : ''} ${headerStyle ? styles.headerStyle : ''}`, children: [_jsxs("span", { children: [_jsx("span", { className: styles.icon, children: icon }), !isSidebarCollapsed && (_jsx("span", { className: `${styles.label} ${truncateLabel ? styles.truncate : ''}`, title: truncateLabel && typeof label === 'string' ? label : undefined, children: label }))] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
7
+ const iconStyle = iconWidth !== undefined
8
+ ? { ['--sidebar-item-icon-width']: iconWidth }
9
+ : undefined;
10
+ return (_jsxs("span", { className: `${styles.container} ${!disableActiveStyles && activeLink === href ? styles.active : ''} ${isSidebarCollapsed ? styles.collapsed : ''} ${headerStyle ? styles.headerStyle : ''}`, children: [_jsxs("span", { className: styles.iconLabel, children: [_jsx("span", { className: styles.icon, style: iconStyle, children: icon }), !isSidebarCollapsed && (_jsx("span", { className: `${styles.label} ${truncateLabel ? styles.truncate : ''}`, title: truncateLabel && typeof label === 'string' ? label : undefined, children: label }))] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
8
11
  }
@@ -37,11 +37,13 @@
37
37
  border-radius: 0;
38
38
  }
39
39
 
40
- .container > span {
40
+ .iconLabel {
41
41
  display: flex;
42
42
  align-items: center;
43
43
  gap: var(--spacing-sm);
44
44
  min-inline-size: 0;
45
+ flex: 1 1 0;
46
+ overflow: hidden;
45
47
  }
46
48
 
47
49
  .container:not(.active):hover {
@@ -52,13 +54,19 @@
52
54
 
53
55
  .icon,
54
56
  .suffixIcon {
57
+ inline-size: var(--sidebar-item-icon-width, auto);
55
58
  min-inline-size: var(--icon-size-sm);
59
+ flex: 0 0 var(--sidebar-item-icon-width, auto);
56
60
  display: flex;
57
61
  align-items: center;
58
- justify-content: center;
62
+ justify-content: flex-start;
59
63
  color: var(--color-fg-subtle);
60
64
  }
61
65
 
66
+ .icon {
67
+ min-inline-size: var(--sidebar-item-icon-width, var(--icon-size-sm));
68
+ }
69
+
62
70
  .icon svg,
63
71
  .suffixIcon svg {
64
72
  inline-size: var(--icon-size-sm);
@@ -87,6 +95,7 @@
87
95
 
88
96
  .label {
89
97
  min-inline-size: 0;
98
+ flex: 1 1 auto;
90
99
  }
91
100
 
92
101
  .truncate {
@@ -16,11 +16,11 @@ export function SidebarItems({ activeLink }) {
16
16
  return isSidebarCollapsed ? ((_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child, idx) => renderItem(child, `${key}-c${idx}`))) : (_jsxs("div", { className: styles.group, children: [_jsx("div", { className: styles.groupLabel, children: item.label }), (_b = item.children) === null || _b === void 0 ? void 0 : _b.map((child, idx) => renderItem(child, `${key}-c${idx}`))] }, key));
17
17
  }
18
18
  if (item.type === 'expandable') {
19
- const { component: Component, label, icon, children, href, truncateLabel } = item;
20
- return (_jsx(ExpandableSidebarItem, { items: children, label: label, icon: icon, href: href, truncateLabel: truncateLabel, component: Component }, key));
19
+ const { component: Component, label, icon, children, href, iconWidth, truncateLabel } = item;
20
+ return (_jsx(ExpandableSidebarItem, { items: children, label: label, icon: icon, href: href, iconWidth: iconWidth, truncateLabel: truncateLabel, component: Component }, key));
21
21
  }
22
- const { component: Component, label, icon, href, truncateLabel } = item;
23
- return (_jsx(SidebarItem, { component: Component, label: label, icon: icon, href: href, truncateLabel: truncateLabel }, key));
22
+ const { component: Component, label, icon, href, iconWidth, truncateLabel } = item;
23
+ return (_jsx(SidebarItem, { component: Component, label: label, icon: icon, href: href, iconWidth: iconWidth, truncateLabel: truncateLabel }, key));
24
24
  };
25
25
  return filteredItems === null || filteredItems === void 0 ? void 0 : filteredItems.map((item, idx) => renderItem(item, `nav-${idx}-${item.label}`));
26
26
  }
@@ -1,3 +1,6 @@
1
1
  import React from 'react';
2
- declare const SidenavFiltering: React.FC;
2
+ interface SidenavFilteringProps {
3
+ placeholder?: string;
4
+ }
5
+ declare const SidenavFiltering: React.FC<SidenavFilteringProps>;
3
6
  export default SidenavFiltering;
@@ -5,7 +5,7 @@ import React from 'react';
5
5
  import { Button } from '../../../button/Button';
6
6
  import { SearchBox } from '../../../search-box/SearchBox';
7
7
  import { useSidebar } from '../../providers/SidebarProvider';
8
- const SidenavFiltering = () => {
8
+ const SidenavFiltering = ({ placeholder = 'Filtrer menu' }) => {
9
9
  const { activeQuery, setActiveQuery, isSidebarCollapsed, handleSidebarCollapseChange } = useSidebar();
10
10
  const searchBoxRef = React.useRef(null);
11
11
  const handleSearch = (value) => {
@@ -20,6 +20,6 @@ const SidenavFiltering = () => {
20
20
  }, 50);
21
21
  } }));
22
22
  }
23
- return (_jsx(SearchBox, { ref: searchBoxRef, inputWidth: "100%", inputSize: "sm", value: activeQuery !== null && activeQuery !== void 0 ? activeQuery : '', onSearch: handleSearch, placeholder: "Filtrer menu" }));
23
+ return (_jsx(SearchBox, { ref: searchBoxRef, inputWidth: "100%", inputSize: "sm", value: activeQuery !== null && activeQuery !== void 0 ? activeQuery : '', onSearch: handleSearch, placeholder: placeholder }));
24
24
  };
25
25
  export default SidenavFiltering;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbcdk/react-components",
3
- "version": "0.0.69",
3
+ "version": "0.0.70",
4
4
  "description": "Reusable React components for DBC projects",
5
5
  "license": "ISC",
6
6
  "author": "",