@dbcdk/react-components 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/components/accordion/Accordion.d.ts +2 -1
  2. package/dist/components/accordion/Accordion.js +14 -3
  3. package/dist/components/accordion/Accordion.module.css +1 -1
  4. package/dist/components/accordion/components/AccordionRow.d.ts +2 -1
  5. package/dist/components/accordion/components/AccordionRow.js +8 -6
  6. package/dist/components/accordion/components/AccordionRow.module.css +12 -8
  7. package/dist/components/button/Button.d.ts +1 -0
  8. package/dist/components/button/Button.js +3 -3
  9. package/dist/components/button/Button.module.css +12 -1
  10. package/dist/components/card/Card.module.css +1 -1
  11. package/dist/components/chip/Chip.js +11 -1
  12. package/dist/components/chip/Chip.module.css +92 -30
  13. package/dist/components/circle/Circle.d.ts +1 -1
  14. package/dist/components/circle/Circle.module.css +5 -1
  15. package/dist/components/clear-button/ClearButton.js +1 -1
  16. package/dist/components/clear-button/ClearButton.module.css +3 -0
  17. package/dist/components/code-block/CodeBlock.d.ts +7 -3
  18. package/dist/components/code-block/CodeBlock.js +35 -2
  19. package/dist/components/code-block/CodeBlock.module.css +49 -2
  20. package/dist/components/filter-field/FilterField.d.ts +2 -1
  21. package/dist/components/filter-field/FilterField.js +22 -7
  22. package/dist/components/filtering/chip-multi-toggle/ChipMultiToggle.d.ts +4 -3
  23. package/dist/components/filtering/chip-multi-toggle/ChipMultiToggle.js +3 -2
  24. package/dist/components/forms/checkbox/Checkbox.d.ts +1 -1
  25. package/dist/components/forms/checkbox/Checkbox.module.css +11 -0
  26. package/dist/components/hyperlink/Hyperlink.module.css +0 -1
  27. package/dist/components/overlay/modal/Modal.js +1 -1
  28. package/dist/components/overlay/side-panel/SidePanel.js +1 -1
  29. package/dist/components/page-layout/PageLayout.module.css +0 -2
  30. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +1 -1
  31. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +1 -1
  32. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +0 -4
  33. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +2 -1
  34. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +2 -2
  35. package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +9 -0
  36. package/dist/components/split-pane/SplitPane.js +1 -1
  37. package/dist/components/state-page/StatePage.module.css +1 -1
  38. package/dist/components/table/Table.js +5 -2
  39. package/dist/components/table/Table.module.css +19 -16
  40. package/dist/components/table/components/empty-state/EmptyState.d.ts +7 -22
  41. package/dist/components/table/components/empty-state/EmptyState.js +12 -8
  42. package/dist/components/table/components/empty-state/EmptyState.module.css +2 -14
  43. package/dist/components/tabs/Tabs.js +1 -1
  44. package/dist/components/tabs/Tabs.module.css +1 -1
  45. package/dist/components/toast/Toast.js +1 -1
  46. package/dist/hooks/useTableSelection.d.ts +1 -1
  47. package/dist/hooks/useTableSelection.js +97 -77
  48. package/dist/hooks/useViewportFill.js +12 -0
  49. package/dist/src/styles/styles.css +8 -0
  50. package/dist/styles/styles.css +8 -0
  51. package/dist/styles/themes/dbc/base.css +1 -0
  52. package/package.json +1 -1
@@ -68,24 +68,29 @@ function isFilterActive(value) {
68
68
  return value.trim().length > 0;
69
69
  return value != null;
70
70
  }
71
- export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, ...inputProps }) {
71
+ const VALUELESS_OPERATORS = ['isEmpty', 'isNotEmpty'];
72
+ export function FilterField({ field, control, operator, value, onChange, operators, options = [], single = true, size = 'md', label, placeholder = 'Type value…', disabled, 'data-cy': dataCy, width, ...inputProps }) {
72
73
  var _a, _b;
73
- const [selectedOperator, setSelectedOperator] = useState(operator);
74
74
  const ops = useMemo(() => operators !== null && operators !== void 0 ? operators : DEFAULT_TEXT_OPERATORS, [operators]);
75
+ // internal operator state (source of truth for UI)
76
+ const [selectedOperator, setSelectedOperator] = useState(operator);
77
+ // "active" based on value only (as requested)
75
78
  const active = isFilterActive(value);
76
- // Local state ONLY for input control (to avoid URL->props lag)
77
- const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
78
- const debounceRef = useRef(null);
79
- const isTypingRef = useRef(false);
79
+ // Overwrite internal operator if parent sends a new one
80
80
  useEffect(() => {
81
81
  if (ops.includes(operator)) {
82
82
  setSelectedOperator(operator);
83
83
  }
84
84
  }, [operator, ops]);
85
+ // Local state ONLY for input control (to avoid URL->props lag)
86
+ const [localValue, setLocalValue] = useState((_a = value) !== null && _a !== void 0 ? _a : '');
87
+ const debounceRef = useRef(null);
88
+ const isTypingRef = useRef(false);
85
89
  const emit = (next) => {
86
90
  var _a, _b;
87
91
  const nextOperator = (_a = next.operator) !== null && _a !== void 0 ? _a : selectedOperator;
88
92
  const nextValue = (_b = next.value) !== null && _b !== void 0 ? _b : value;
93
+ // Always keep internal operator in sync when user picks one
89
94
  if (next.operator)
90
95
  setSelectedOperator(nextOperator);
91
96
  onChange({
@@ -94,6 +99,16 @@ export function FilterField({ field, control, operator, value, onChange, operato
94
99
  value: nextValue,
95
100
  });
96
101
  };
102
+ const handleOperatorChange = (op) => {
103
+ setSelectedOperator(op);
104
+ if (!active && !VALUELESS_OPERATORS.includes(op))
105
+ return;
106
+ if (VALUELESS_OPERATORS.includes(op)) {
107
+ emit({ operator: op, value: null });
108
+ return;
109
+ }
110
+ emit({ operator: op });
111
+ };
97
112
  const scheduleEmitValue = (nextVal) => {
98
113
  if (debounceRef.current)
99
114
  clearTimeout(debounceRef.current);
@@ -122,7 +137,7 @@ export function FilterField({ field, control, operator, value, onChange, operato
122
137
  clearTimeout(debounceRef.current);
123
138
  };
124
139
  }, []);
125
- return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: `${styles.filterField} ${styles[size]} ${active ? styles.active : ''}`, children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: op => emit({ operator: op }), operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, children: control === 'input' ? (_jsx(Input, { ...inputProps, value: localValue, onChange: e => {
140
+ return (_jsxs("div", { ...(dataCy ? { 'data-cy': dataCy } : {}), className: `${styles.filterField} ${styles[size]} ${active ? styles.active : ''}`, children: [label ? _jsx("span", { className: `${styles.label} ${styles[size]}`, children: label }) : null, _jsx(OperatorDropdown, { value: selectedOperator, onChange: handleOperatorChange, operators: ops, size: size, disabled: disabled }), _jsx("div", { className: `${control === 'input' ? 'dbc-flex dbc-flex-grow' : styles.valueWrapper}`, style: { width }, children: control === 'input' ? (_jsx(Input, { ...inputProps, value: localValue, onChange: e => {
126
141
  const next = e.currentTarget.value;
127
142
  isTypingRef.current = true;
128
143
  setLocalValue(next);
@@ -13,15 +13,16 @@ interface ChipMultiToggleProps {
13
13
  onChange: (nextSelectedValues: string[]) => void;
14
14
  onToggle?: (toggledValue: string, isSelected: boolean) => void;
15
15
  size?: 'sm' | 'md' | 'lg';
16
- selectedSeverity: 'neutral' | 'info' | 'success' | 'warning' | 'danger';
16
+ selectedSeverity: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | 'brand';
17
17
  unselectedSeverity?: 'neutral' | 'info' | 'success' | 'warning' | 'danger' | null;
18
18
  fullWidth?: boolean;
19
19
  disabled?: boolean;
20
+ showSelectedIcon?: boolean;
20
21
  showAllOption?: boolean;
21
22
  allLabel?: string;
22
23
  allIcon?: React.ReactNode;
23
24
  /**
24
- * If true, clicking "All" toggles between:
25
+ * If true, clicking "All" toggles between:
25
26
  * - All (no filtering) => []
26
27
  * - None (match nothing) => [noneValue]
27
28
  */
@@ -32,5 +33,5 @@ interface ChipMultiToggleProps {
32
33
  */
33
34
  noneValue?: string;
34
35
  }
35
- export declare function ChipMultiToggle({ label, options, selectedValues, onChange, onToggle, size, selectedSeverity, unselectedSeverity, fullWidth, disabled, showAllOption, allLabel, allIcon, allTogglesNone, noneValue, }: ChipMultiToggleProps): JSX.Element;
36
+ export declare function ChipMultiToggle({ label, options, selectedValues, onChange, onToggle, size, selectedSeverity, unselectedSeverity, fullWidth, disabled, showAllOption, allLabel, allIcon, showSelectedIcon, allTogglesNone, noneValue, }: ChipMultiToggleProps): JSX.Element;
36
37
  export {};
@@ -1,9 +1,10 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Check } from 'lucide-react';
3
4
  import React from 'react';
4
5
  import { Chip } from '../../../components/chip/Chip';
5
6
  import styles from './ChipMultiToggle.module.css';
6
- export function ChipMultiToggle({ label, options, selectedValues = [], onChange, onToggle, size = 'sm', selectedSeverity = 'info', unselectedSeverity = null, fullWidth = false, disabled = false, showAllOption = false, allLabel = 'Alle', allIcon, allTogglesNone = false, noneValue = '__none__', }) {
7
+ export function ChipMultiToggle({ label, options, selectedValues = [], onChange, onToggle, size = 'sm', selectedSeverity = 'info', unselectedSeverity = null, fullWidth = false, disabled = false, showAllOption = false, allLabel = 'Alle', allIcon, showSelectedIcon = false, allTogglesNone = false, noneValue = '__none__', }) {
7
8
  const selectedSet = React.useMemo(() => new Set(selectedValues), [selectedValues]);
8
9
  const isNoneSelected = allTogglesNone && selectedSet.has(noneValue);
9
10
  const isAllSelected = showAllOption && !isNoneSelected && selectedSet.size === 0;
@@ -48,6 +49,6 @@ export function ChipMultiToggle({ label, options, selectedValues = [], onChange,
48
49
  return (_jsxs("div", { className: `${styles.wrapper} ${fullWidth ? styles.fullWidth : ''}`, children: [label && _jsx("span", { className: styles.label, children: label }), _jsxs("div", { className: styles.container, children: [showAllOption && (_jsx("button", { type: "button", className: styles.chipButton, onClick: toggleAll, "aria-pressed": isAllSelected || isNoneSelected, disabled: disabled, children: _jsx(Chip, { size: size, severity: (isAllSelected || isNoneSelected ? selectedSeverity : unselectedSeverity), customIcon: allIcon, type: unselectedSeverity === null ? 'outlined' : 'rounded', children: isNoneSelected ? `${allLabel} (ingen)` : allLabel }) }, "__all__")), options.map(opt => {
49
50
  // If none-state is active, everything else should look unselected
50
51
  const isSelected = !isNoneSelected && selectedSet.has(opt.value);
51
- return (_jsx("button", { type: "button", className: styles.chipButton, onClick: () => toggleValue(opt.value), "aria-pressed": isSelected, disabled: disabled, children: _jsx(Chip, { size: size, severity: (isSelected ? selectedSeverity : unselectedSeverity), customIcon: opt.icon, type: unselectedSeverity === null ? 'outlined' : 'rounded', children: opt.label }) }, opt.value));
52
+ return (_jsx("button", { type: "button", className: styles.chipButton, onClick: () => toggleValue(opt.value), "aria-pressed": isSelected, disabled: disabled, children: _jsx(Chip, { size: size, severity: (isSelected ? selectedSeverity : unselectedSeverity), customIcon: showSelectedIcon ? isSelected ? _jsx(Check, {}) : null : opt.icon, type: unselectedSeverity === null ? 'outlined' : 'rounded', children: opt.label }) }, opt.value));
52
53
  })] })] }));
53
54
  }
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { JSX, ReactNode } from 'react';
3
- type Variant = 'default' | 'primary' | 'outlined';
3
+ type Variant = 'default' | 'primary' | 'outlined' | 'success';
4
4
  type Size = 'sm' | 'md' | 'lg';
5
5
  interface CheckboxProps {
6
6
  checked?: boolean;
@@ -60,3 +60,14 @@
60
60
  color: var(--color-fg-default);
61
61
  }
62
62
  }
63
+
64
+ .success.checked {
65
+ background-color: var(--color-status-success);
66
+ &:not(:hover) {
67
+ border-color: transparent;
68
+ }
69
+
70
+ .icon {
71
+ color: var(--color-fg-on-brand);
72
+ }
73
+ }
@@ -2,7 +2,6 @@
2
2
  display: inline-flex;
3
3
  gap: var(--spacing-xs);
4
4
  position: relative;
5
- font-weight: normal;
6
5
  background: none;
7
6
  border: none;
8
7
  padding: 0;
@@ -88,5 +88,5 @@ export function Modal({ isOpen, onRequestClose, header, content, children, prima
88
88
  const resolvedSecondaryAction = secondaryAction !== null && secondaryAction !== void 0 ? secondaryAction : (primaryAction ? { label: 'Luk', onClick: onRequestCloseRef.current } : undefined);
89
89
  const shouldRenderFooter = Boolean(primaryAction || resolvedSecondaryAction);
90
90
  const body = children !== null && children !== void 0 ? children : content;
91
- return (_jsx("div", { className: styles.overlay, onClick: handleOverlayClick, children: _jsxs("div", { "data-cy": dataCy, ref: dialogRef, className: `${styles.modal} ${disableContentSpacing ? '' : styles.contentSpacing}`, onClick: stopPropagation, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, children: [_jsxs("div", { className: styles.header, children: [header && (_jsx(Headline, { severity: severity, size: 3, disableMargin: true, children: header })), _jsx(Button, { type: "button", variant: "inline", onClick: () => onRequestCloseRef.current(), "aria-label": "Luk", children: _jsx(X, {}) })] }), _jsx("div", { className: styles.body, children: body }), shouldRenderFooter && (_jsxs("div", { className: styles.footer, children: [resolvedSecondaryAction && (_jsxs(Button, { type: "button", variant: "outlined", onClick: resolvedSecondaryAction.onClick, disabled: isLoading, children: [resolvedSecondaryAction.icon && (_jsx("span", { className: styles.icon, children: resolvedSecondaryAction.icon })), _jsx("span", { children: resolvedSecondaryAction.label })] })), primaryAction && (_jsxs(Button, { type: "button", variant: "primary", onClick: primaryAction.onClick, disabled: primaryAction.disabled || isLoading, loading: isLoading, children: [primaryAction.icon && _jsx("span", { className: styles.icon, children: primaryAction.icon }), _jsx("span", { children: primaryAction.label })] }))] }))] }) }));
91
+ return (_jsx("div", { className: styles.overlay, onClick: handleOverlayClick, children: _jsxs("div", { "data-cy": dataCy, ref: dialogRef, className: `${styles.modal} ${disableContentSpacing ? '' : styles.contentSpacing}`, onClick: stopPropagation, role: "dialog", "aria-modal": "true", "aria-labelledby": header ? titleId : undefined, children: [_jsxs("div", { className: styles.header, children: [header && (_jsx(Headline, { severity: severity, size: 3, disableMargin: true, children: header })), _jsx(Button, { type: "button", variant: "inline", onClick: () => onRequestCloseRef.current(), "aria-label": "Luk", shape: "round", icon: _jsx(X, {}) })] }), _jsx("div", { className: styles.body, children: body }), shouldRenderFooter && (_jsxs("div", { className: styles.footer, children: [resolvedSecondaryAction && (_jsxs(Button, { type: "button", variant: "outlined", onClick: resolvedSecondaryAction.onClick, disabled: isLoading, children: [resolvedSecondaryAction.icon && (_jsx("span", { className: styles.icon, children: resolvedSecondaryAction.icon })), _jsx("span", { children: resolvedSecondaryAction.label })] })), primaryAction && (_jsxs(Button, { type: "button", variant: "primary", onClick: primaryAction.onClick, disabled: primaryAction.disabled || isLoading, loading: isLoading, children: [primaryAction.icon && _jsx("span", { className: styles.icon, children: primaryAction.icon }), _jsx("span", { children: primaryAction.label })] }))] }))] }) }));
92
92
  }
@@ -76,7 +76,7 @@ export function SidePanel({ isOpen, onClose, children, header, headerAddition, a
76
76
  }, "data-cy": "details-panel", role: "dialog", "aria-modal": "true", children: [hasDetails ? (_jsxs("aside", { className: styles.detailsCol, "data-cy": "details-panel-details", children: [_jsxs("div", { className: styles.detailsHeader, children: [_jsx("div", { className: styles.detailsTitle, children: detailsHeader }), _jsxs("div", { className: styles.detailsHeaderActions, children: [detailsHeaderAddition, onCloseDetails ? (_jsx(Button, { type: "button", size: "sm", variant: "outlined", onClick: e => {
77
77
  e.stopPropagation();
78
78
  onCloseDetails();
79
- }, children: "Luk" })) : null] })] }), _jsx("div", { className: styles.detailsContent, children: details })] })) : null, _jsxs("section", { className: styles.mainCol, "data-cy": "details-panel-main", children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: "dbc-flex dbc-justify-between", children: [_jsx(Headline, { size: 3, disableMargin: true, severity: severity, marker: showHeaderMarker, children: header }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xs", children: [headerAddition, _jsx(Button, { type: "button", size: "sm", variant: "inline", onClick: e => {
79
+ }, children: "Luk" })) : null] })] }), _jsx("div", { className: styles.detailsContent, children: details })] })) : null, _jsxs("section", { className: styles.mainCol, "data-cy": "details-panel-main", children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: "dbc-flex dbc-justify-between", children: [_jsx(Headline, { size: 3, disableMargin: true, severity: severity, marker: showHeaderMarker, children: header }), _jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xs", children: [headerAddition, _jsx(Button, { type: "button", size: "md", shape: "round", variant: "inline", onClick: e => {
80
80
  e.stopPropagation();
81
81
  onClose(e);
82
82
  }, "aria-label": "Luk panel", "data-cy": "side-panel-close-button", children: _jsx(X, {}) })] })] }) }), _jsx("div", { className: styles.content, "data-cy": "details-panel-content", children: children }), actions && _jsx("div", { className: styles.actions, children: actions })] })] })] }), document.body);
@@ -34,7 +34,6 @@
34
34
  .sidebar {
35
35
  min-width: 0;
36
36
  overflow: hidden;
37
- border-right: var(--border-width-thin) solid var(--color-border-subtle);
38
37
  background: var(--color-bg-surface);
39
38
  }
40
39
 
@@ -72,7 +71,6 @@
72
71
  .header {
73
72
  min-width: 0;
74
73
  background: var(--color-bg-surface);
75
- border-bottom: var(--border-width-thin) solid var(--color-border-subtle);
76
74
  flex: 0 0 auto;
77
75
  }
78
76
 
@@ -62,5 +62,5 @@ export function ExpandableSidebarItem({ items, label, icon, component: Component
62
62
  }
63
63
  return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href }, key));
64
64
  };
65
- return (_jsxs("div", { className: `${styles.container} ${expanded ? styles.expanded : ''}`, children: [_jsx(Component, { onClick: () => toggleAccordion(undefined, true), children: _jsx(SidebarItemContent, { icon: icon, label: label, href: href, disableActiveStyles: expanded, 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}`)) }))] }));
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, 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
66
  }
@@ -10,5 +10,5 @@ export function SidebarContainer({ logo, // DBC Digital (company)
10
10
  productLogo, // DataIO (product)
11
11
  activeLink, }) {
12
12
  const { isSidebarCollapsed, handleSidebarCollapseChange } = useSidebar();
13
- return (_jsxs("div", { className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''}`, 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", "aria-label": "Collapse sidebar", icon: _jsx(ChevronLeft, { className: isSidebarCollapsed ? styles.collapsedIcon : '' }), onClick: () => handleSidebarCollapseChange(!isSidebarCollapsed) })] }) }), _jsxs("div", { className: styles.content, children: [_jsx("div", { className: styles.filter, children: _jsx(SidenavFiltering, {}) }), _jsx("div", { className: `${styles.links} hideScrollBar`, children: _jsx(SidebarItems, { activeLink: activeLink }) })] }), _jsx("div", { className: styles.footer, children: _jsx("div", { className: styles.companyLogo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) }) })] }));
13
+ return (_jsxs("div", { className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''}`, 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: [_jsx("div", { className: styles.filter, children: _jsx(SidenavFiltering, {}) }), _jsx("div", { className: `${styles.links} hideScrollBar`, children: _jsx(SidebarItems, { activeLink: activeLink }) })] }), _jsx("div", { className: styles.footer, children: _jsx("div", { className: styles.companyLogo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) }) })] }));
14
14
  }
@@ -71,10 +71,6 @@
71
71
 
72
72
  /* Collapse button */
73
73
  .productHeader button {
74
- border-radius: 0;
75
- min-height: var(--component-size-md);
76
- padding: var(--spacing-xs);
77
- color: var(--color-fg-muted);
78
74
  flex: 0 0 auto;
79
75
  }
80
76
 
@@ -6,5 +6,6 @@ export interface SidebarItemContentProps {
6
6
  suffixIcon?: React.ReactNode;
7
7
  href?: string;
8
8
  disableActiveStyles?: boolean;
9
+ headerStyle?: boolean;
9
10
  }
10
- export declare function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles, }: SidebarItemContentProps): JSX.Element;
11
+ export declare function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles, headerStyle, }: SidebarItemContentProps): JSX.Element;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styles from './SidebarItemContent.module.css';
3
3
  import { useSidebar } from '../../providers/SidebarProvider';
4
- export function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles = false, }) {
4
+ export function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles = false, headerStyle, }) {
5
5
  const { activeLink, isSidebarCollapsed } = useSidebar();
6
- return (_jsxs("span", { className: `${styles.container} ${!disableActiveStyles && activeLink === href ? styles.active : ''} ${isSidebarCollapsed ? styles.collapsed : ''}`, children: [_jsxs("span", { children: [_jsx("span", { className: styles.icon, children: icon }), !isSidebarCollapsed && _jsx("span", { className: styles.label, children: label })] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
6
+ 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, children: label })] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
7
7
  }
@@ -73,3 +73,12 @@
73
73
  outline: none;
74
74
  box-shadow: var(--focus-ring);
75
75
  }
76
+
77
+ .headerStyle .label {
78
+ font-weight: 500;
79
+ color: var(--color-fg-subtle);
80
+ font-size: var(--font-size-xs);
81
+ text-transform: uppercase;
82
+ letter-spacing: 0.04em;
83
+ transition: 0.15s ease-in-out;
84
+ }
@@ -6,7 +6,7 @@ import styles from './SplitPane.module.css';
6
6
  function clamp(n, min, max) {
7
7
  return Math.max(min, Math.min(max, n));
8
8
  }
9
- export function SplitPane({ children, initialPrimarySize = 300, minPrimarySize = 160, minSecondarySize = 160, direction = 'horizontal', showDivider = 'hover', gutterSize = 8, storageKey, }) {
9
+ export function SplitPane({ children, initialPrimarySize = 300, minPrimarySize = 160, minSecondarySize = 160, direction = 'horizontal', showDivider = 'hover', gutterSize = 16, storageKey, }) {
10
10
  return (_jsx(SplitPaneProvider, { direction: direction, initialPrimarySize: initialPrimarySize, minPrimarySize: minPrimarySize, minSecondarySize: minSecondarySize, storageKey: storageKey, children: _jsx(SplitPaneContainer, { showDivider: showDivider, gutterSize: gutterSize, children: children }) }));
11
11
  }
12
12
  function SplitPaneContainer({ children, showDivider, gutterSize, }) {
@@ -4,6 +4,6 @@
4
4
  }
5
5
 
6
6
  .illustration svg {
7
- width: 300px;
7
+ width: 200px;
8
8
  height: auto;
9
9
  }
@@ -28,7 +28,7 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
28
28
  const nextDir = getNextSortDirection(column.sortable, active, sortDirection !== null && sortDirection !== void 0 ? sortDirection : null);
29
29
  onSortChange(column, nextDir);
30
30
  };
31
- return (_jsx("th", { style: getColumnStyle(column.id, columnStyles, column.align, 'middle'), "aria-sort": ariaSort, className: `${styles.th}`, children: _jsxs("div", { className: styles.thInner, children: [column.sortable ? (_jsxs("button", { type: "button", className: styles.thButton, onClick: toggleSort, onKeyDown: e => {
31
+ return (_jsx("th", { style: getColumnStyle(column.id, columnStyles, column.align, 'middle'), "aria-sort": ariaSort, className: `${styles.th}`, children: _jsxs("div", { className: styles.thInner, children: [column.sortable ? (_jsxs("button", { type: "button", className: `${styles.thButton} ${column.align ? styles[`thButton${column.align}`] : ''}`, onClick: toggleSort, onKeyDown: e => {
32
32
  if (shouldToggleOnKey(e.key)) {
33
33
  e.preventDefault();
34
34
  toggleSort();
@@ -58,7 +58,10 @@ export function Table({ data, columns, selectedRows, onRowSelect, selectionMode
58
58
  ['--row-severity-color']: rowSeverity
59
59
  ? SeverityBgColor[rowSeverity]
60
60
  : undefined,
61
- }, className: `${onRowClick ? styles.clickableRow : ''} ${isSelected ? styles.selectedRow : ''} ${rowSeverity ? styles.severity : ''}`, children: [selectedRows && onRowSelect && dataKey && (_jsx("td", { className: `${styles.selectionCell}`, onClick: e => e.stopPropagation(), children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: () => onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, !selectedRows.has(rowId)) }) })), filteredColumns.map(column => (_jsx("td", { style: getColumnStyle(column.id, columnStyles, column.align, column.verticalAlign), className: `${styles.tableCell} ${shouldAllowWrap(column.allowWrap, isSelected, viewMode)
61
+ }, className: `${onRowClick ? styles.clickableRow : ''} ${isSelected ? styles.selectedRow : ''} ${rowSeverity ? styles.severity : ''}`, children: [selectedRows && onRowSelect && dataKey && (_jsx("td", { className: `${styles.selectionCell}`, children: _jsx(Checkbox, { variant: "primary", checked: selectedRows.has(rowId), size: "sm", onChange: (checked, e) => {
62
+ e.stopPropagation();
63
+ onRowSelect === null || onRowSelect === void 0 ? void 0 : onRowSelect(rowId, checked);
64
+ } }) })), filteredColumns.map(column => (_jsx("td", { style: getColumnStyle(column.id, columnStyles, column.align, column.verticalAlign), className: `${styles.tableCell} ${shouldAllowWrap(column.allowWrap, isSelected, viewMode)
62
65
  ? styles.allowWrap
63
66
  : styles.nowrap}`, children: getCellDisplayValue(row, column) }, column.id)))] }, getRowKey(rowId)));
64
67
  }) }))] }), !data.length && !loading && _jsx(TableEmptyState, { config: emptyConfig })] }));
@@ -13,7 +13,7 @@
13
13
  font-size: var(--font-size-sm);
14
14
  color: var(--color-fg-default);
15
15
  background: var(--color-bg-surface);
16
- table-layout: auto;
16
+ table-layout: fixed;
17
17
  }
18
18
 
19
19
  .tableScroll {
@@ -42,8 +42,7 @@
42
42
  position: relative;
43
43
 
44
44
  padding-block: var(--spacing-xs);
45
- padding-inline: var(--spacing-md);
46
- padding-right: var(--spacing-lg);
45
+ padding-inline: var(--spacing-sm);
47
46
 
48
47
  text-align: left;
49
48
  vertical-align: middle;
@@ -76,7 +75,6 @@
76
75
  .th > .thInner {
77
76
  display: flex;
78
77
  align-items: center;
79
- gap: var(--spacing-xxs);
80
78
  inline-size: 100%;
81
79
  }
82
80
 
@@ -87,18 +85,24 @@
87
85
  align-items: center;
88
86
  gap: var(--spacing-xxs);
89
87
  inline-size: 100%;
90
-
91
88
  cursor: pointer;
92
89
  user-select: none;
93
-
94
90
  /* make it feel like a button */
95
91
  border-radius: var(--border-radius-default);
96
92
  padding-block: 2px;
97
93
  padding-inline: 2px;
98
94
  }
99
95
 
96
+ .thButtonright {
97
+ justify-content: flex-end;
98
+ }
99
+
100
+ .thButtoncenter {
101
+ justify-content: center;
102
+ }
103
+
100
104
  .thButton:hover {
101
- background-color: var(--color-bg-contextual);
105
+ color: var(--color-fg-default);
102
106
  }
103
107
 
104
108
  .thButton:focus-visible {
@@ -110,7 +114,6 @@
110
114
  overflow: hidden;
111
115
  text-overflow: ellipsis;
112
116
  white-space: nowrap;
113
- flex-grow: 1;
114
117
  }
115
118
 
116
119
  .thExtras {
@@ -187,12 +190,14 @@ th.selectionCell {
187
190
  padding-inline: var(--spacing-xxs);
188
191
  }
189
192
 
193
+ .table.severityTable .tBody tr td:first-child {
194
+ padding-inline-start: var(--spacing-md);
195
+ }
190
196
  .table.severityTable .tBody tr td.selectionCell,
191
197
  .table.severityTable td.selectionCell,
192
198
  .table.severityTable .th.selectionCell,
193
199
  .table.severityTable th.selectionCell {
194
200
  padding-inline: var(--spacing-xxs);
195
- padding-inline-start: 14px;
196
201
  }
197
202
 
198
203
  .table.sm.severityTable .tBody tr td.selectionCell,
@@ -280,14 +285,12 @@ th.selectionCell {
280
285
  .table.severityTable .tBody tr.severity td:first-child::before {
281
286
  content: '';
282
287
  position: absolute;
283
-
284
- left: 4px;
285
- top: 4px;
286
- bottom: 4px;
287
- width: 5px;
288
-
288
+ left: 2px;
289
+ top: 1px;
290
+ bottom: 1px;
291
+ width: 3px;
289
292
  background-color: var(--row-severity-color);
290
- border-radius: 3px;
293
+ border-radius: 1px;
291
294
 
292
295
  z-index: 0;
293
296
  }
@@ -1,39 +1,24 @@
1
1
  import type { ReactNode, JSX } from 'react';
2
2
  export type TableEmptyConfig = {
3
- /**
4
- * Whether to show the empty state at all.
5
- * Default: true
6
- */
7
3
  enabled?: boolean;
8
- /**
9
- * Title + description text (defaults are your current Danish copy)
10
- */
4
+ /** Keep it short + system-like */
11
5
  title?: string;
12
6
  description?: ReactNode;
13
7
  /**
14
- * Optional custom actions area. If provided, it will be rendered first.
15
- * Useful for passing your own buttons/links/etc.
8
+ * Optional custom actions (rendered after built-ins).
9
+ * Good for injecting extra links/buttons from the table page.
16
10
  */
17
11
  actions?: ReactNode;
18
- /**
19
- * Convenience callbacks for built-in default buttons.
20
- * If provided, the corresponding button is shown (unless hidden via showBack/showRefresh).
21
- */
22
12
  onBack?: () => void;
23
13
  onRefresh?: () => void;
24
- /**
25
- * Control default buttons
26
- */
14
+ /** New: clear filters */
15
+ onClearFilters?: () => void;
16
+ showClearFilters?: boolean;
17
+ clearFiltersLabel?: ReactNode;
27
18
  showBack?: boolean;
28
19
  showRefresh?: boolean;
29
- /**
30
- * Override default labels for default buttons
31
- */
32
20
  backLabel?: ReactNode;
33
21
  refreshLabel?: ReactNode;
34
- /**
35
- * Wrapper className for layout styling
36
- */
37
22
  className?: string;
38
23
  };
39
24
  export declare function TableEmptyState({ config }: {
@@ -1,16 +1,19 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ArrowLeft } from 'lucide-react';
3
3
  import { Button } from '../../../../components/button/Button';
4
- import { StatePage } from '../../../../components/state-page/StatePage';
5
4
  const defaultEmptyConfig = {
6
5
  enabled: true,
7
6
  title: 'Ingen resultater',
8
- description: _jsx("span", { children: "Pr\u00F8v at \u00E6ndre dine filtre eller tilf\u00F8je data." }),
9
- showBack: true,
10
- showRefresh: true,
11
- backLabel: (_jsxs(_Fragment, { children: [_jsx(ArrowLeft, {}), "Tilbage"] })),
7
+ description: _jsx("span", { children: "Ingen data at vise." }),
8
+ showBack: false,
9
+ showRefresh: false,
10
+ // default: show button if handler exists
11
+ showClearFilters: true,
12
+ backLabel: (_jsxs(_Fragment, { children: [_jsx(ArrowLeft, { size: 16 }), "Tilbage"] })),
12
13
  refreshLabel: _jsx(_Fragment, { children: "Indl\u00E6s igen" }),
13
- className: 'dbc-flex dbc-flex-column dbc-justify-center dbc-items-center dbc-flex-grow ',
14
+ clearFiltersLabel: _jsx(_Fragment, { children: "Nulstil alle filtre" }),
15
+ // "system-like": less hero, less vertical drama
16
+ className: 'dbc-flex dbc-flex-column dbc-items-start dbc-justify-start dbc-text-left dbc-gap-sm dbc-py-lg',
14
17
  };
15
18
  export function TableEmptyState({ config }) {
16
19
  const merged = {
@@ -21,6 +24,7 @@ export function TableEmptyState({ config }) {
21
24
  return null;
22
25
  const showBack = merged.showBack && typeof merged.onBack === 'function';
23
26
  const showRefresh = merged.showRefresh && typeof merged.onRefresh === 'function';
24
- const hasAnyActions = Boolean(merged.actions) || showBack || showRefresh;
25
- return (_jsx(StatePage, { type: "empty", header: merged.title, actions: _jsxs("div", { children: [hasAnyActions && (_jsxs("div", { className: "dbc-flex dbc-gap-sm", children: [showBack && (_jsx(Button, { type: "button", onClick: merged.onBack, children: merged.backLabel })), showRefresh && (_jsx(Button, { type: "button", onClick: merged.onRefresh, children: merged.refreshLabel }))] })), merged.actions] }), children: merged.description }));
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] }))] }));
26
30
  }
@@ -1,16 +1,4 @@
1
- .container {
2
- position: absolute;
3
- inset: 0;
4
- display: flex;
5
- align-items: center;
6
- justify-content: center;
7
- transform: translateY(clamp(-32px, -6vh, -12px));
8
- padding-top: 56px;
9
- box-sizing: border-box;
10
- }
11
-
1
+ .container,
12
2
  .icon {
13
- inline-size: var(--component-size-lg);
14
- block-size: var(--component-size-lg);
15
- color: var(--color-fg-muted);
3
+ display: none;
16
4
  }
@@ -117,7 +117,7 @@ export function Tabs({ header, variant, panelStyle = false, tabs, value, default
117
117
  const selected = index === activeIndex;
118
118
  const tabDomId = `${uid}-tab-${String(tab.id)}`;
119
119
  const panelDomId = `${uid}-panel-${String(tab.id)}`;
120
- return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { severity: "info", size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
120
+ return (_jsx("div", { className: `${styles.tab} ${selected ? styles.active : ''}`, children: _jsxs("button", { id: tabDomId, type: "button", className: styles.tabButton, role: "tab", "aria-selected": selected, "aria-controls": panelDomId, tabIndex: selected ? 0 : -1, disabled: tab.disabled, onClick: () => setValue(tab.id), onKeyDown: e => onKeyDownTab(e, index), children: [tab.headerIcon ? _jsx("span", { className: styles.icon, children: tab.headerIcon }) : null, _jsx("span", { className: styles.label, children: tab.header }), tab.badge !== undefined && tab.badge > 0 ? (_jsx("span", { className: styles.badge, children: _jsx(Chip, { severity: "neutral", size: "sm", children: tab.badge.toLocaleString('da-DK') }) })) : null] }) }, tab.id));
121
121
  }) }), _jsx("div", { id: activeTab ? `${uid}-panel-${String(activeTab.id)}` : undefined, role: "tabpanel", "aria-labelledby": activeTab ? `${uid}-tab-${String(activeTab.id)}` : undefined, className: styles.tabContent, children: activeTab === null || activeTab === void 0 ? void 0 : activeTab.content })] })] }));
122
122
  }
123
123
  Tabs.Item = TabsItem;
@@ -2,6 +2,7 @@
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
  min-width: 0;
5
+ overflow: auto;
5
6
  }
6
7
 
7
8
  .headerContainer {
@@ -172,7 +173,6 @@
172
173
  }
173
174
 
174
175
  .outlined .tabContent {
175
- /* plain panel by default */
176
176
  background: transparent;
177
177
  padding: 0;
178
178
  }
@@ -7,6 +7,6 @@ import { Headline } from '../headline/Headline';
7
7
  export function Toast({ title, message, severity = 'info', action, onClose, }) {
8
8
  const showHeader = Boolean(title);
9
9
  const showMessage = Boolean(message);
10
- const CloseButton = onClose ? (_jsx(Button, { type: "button", variant: "inline", className: styles.closeButton, "aria-label": "Dismiss notification", onClick: onClose, children: _jsx(X, { className: styles.closeIcon, "aria-hidden": "true" }) })) : null;
10
+ const CloseButton = onClose ? (_jsx(Button, { type: "button", variant: "inline", shape: "round", className: styles.closeButton, "aria-label": "Dismiss notification", onClick: onClose, children: _jsx(X, { className: styles.closeIcon, "aria-hidden": "true" }) })) : null;
11
11
  return (_jsxs("div", { className: `${styles.toast} ${styles[severity]}`, role: "status", children: [_jsxs("div", { className: styles.content, children: [showHeader && (_jsxs("div", { className: styles.row, children: [_jsx(Headline, { size: 4, severity: severity, disableMargin: true, children: title }), CloseButton] })), showMessage && (_jsxs("div", { className: styles.row, children: [_jsx("div", { className: styles.message, children: message }), !showHeader && CloseButton] }))] }), action && (_jsx("div", { className: styles.actions, children: _jsx(Button, { type: "button", variant: "primary", onClick: action.onClick, children: action.label }) }))] }));
12
12
  }
@@ -16,7 +16,7 @@ export declare function useTableSelection<T>({ storageKey, items, getId, initial
16
16
  selectedItems: T[];
17
17
  selectedItemMap: Map<Id, T>;
18
18
  toggleItem: (item: T) => void;
19
- toggleId: (id: Id) => void;
19
+ toggleId: (id: Id, selected?: boolean) => void;
20
20
  clearSelection: () => void;
21
21
  allSelected: boolean;
22
22
  anySelected: boolean;