@dbcdk/react-components 0.0.12 → 0.0.14

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 (76) hide show
  1. package/dist/components/accordion/Accordion.d.ts +2 -2
  2. package/dist/components/accordion/Accordion.js +34 -41
  3. package/dist/components/accordion/Accordion.module.css +13 -72
  4. package/dist/components/accordion/components/AccordionRow.d.ts +10 -0
  5. package/dist/components/accordion/components/AccordionRow.js +51 -0
  6. package/dist/components/accordion/components/AccordionRow.module.css +82 -0
  7. package/dist/components/breadcrumbs/Breadcrumbs.module.css +0 -1
  8. package/dist/components/button/Button.module.css +7 -7
  9. package/dist/components/card/Card.d.ts +9 -18
  10. package/dist/components/card/Card.js +34 -23
  11. package/dist/components/card/Card.module.css +22 -87
  12. package/dist/components/card/components/CardMeta.d.ts +15 -0
  13. package/dist/components/card/components/CardMeta.js +20 -0
  14. package/dist/components/card/components/CardMeta.module.css +51 -0
  15. package/dist/components/card-container/CardContainer.js +1 -1
  16. package/dist/components/card-container/CardContainer.module.css +3 -1
  17. package/dist/components/chip/Chip.module.css +7 -2
  18. package/dist/components/datetime-picker/DateTimePicker.d.ts +33 -8
  19. package/dist/components/datetime-picker/DateTimePicker.js +119 -78
  20. package/dist/components/datetime-picker/DateTimePicker.module.css +2 -0
  21. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +15 -3
  22. package/dist/components/datetime-picker/dateTimeHelpers.js +137 -23
  23. package/dist/components/filter-field/FilterField.js +21 -6
  24. package/dist/components/filter-field/FilterField.module.css +5 -5
  25. package/dist/components/forms/form-select/FormSelect.d.ts +35 -0
  26. package/dist/components/forms/form-select/FormSelect.js +86 -0
  27. package/dist/components/forms/form-select/FormSelect.module.css +236 -0
  28. package/dist/components/forms/input/Input.d.ts +0 -3
  29. package/dist/components/forms/input/Input.js +0 -3
  30. package/dist/components/forms/input/Input.module.css +7 -7
  31. package/dist/components/forms/radio-buttons/RadioButtons.module.css +1 -0
  32. package/dist/components/forms/select/Select.js +55 -16
  33. package/dist/components/interval-select/IntervalSelect.d.ts +9 -2
  34. package/dist/components/interval-select/IntervalSelect.js +21 -6
  35. package/dist/components/menu/Menu.d.ts +11 -14
  36. package/dist/components/menu/Menu.js +18 -33
  37. package/dist/components/menu/Menu.module.css +2 -2
  38. package/dist/components/overlay/modal/Modal.module.css +2 -1
  39. package/dist/components/overlay/modal/provider/ModalProvider.js +1 -3
  40. package/dist/components/overlay/side-panel/SidePanel.js +1 -1
  41. package/dist/components/overlay/side-panel/SidePanel.module.css +1 -1
  42. package/dist/components/page-layout/PageLayout.d.ts +16 -4
  43. package/dist/components/page-layout/PageLayout.js +57 -28
  44. package/dist/components/page-layout/PageLayout.module.css +153 -33
  45. package/dist/components/popover/Popover.d.ts +17 -4
  46. package/dist/components/popover/Popover.js +147 -65
  47. package/dist/components/popover/Popover.module.css +5 -0
  48. package/dist/components/split-pane/SplitPane.d.ts +10 -24
  49. package/dist/components/split-pane/SplitPane.js +83 -54
  50. package/dist/components/split-pane/SplitPane.module.css +11 -6
  51. package/dist/components/split-pane/provider/SplitPaneContext.js +5 -11
  52. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +3 -8
  53. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +57 -20
  54. package/dist/components/table/Table.d.ts +3 -8
  55. package/dist/components/table/Table.js +37 -76
  56. package/dist/components/table/Table.module.css +45 -42
  57. package/dist/components/table/{tanstack.d.ts → TanstackTable.d.ts} +5 -12
  58. package/dist/components/table/TanstackTable.js +84 -0
  59. package/dist/components/table/components/column-resizer/ColumnResizer.js +1 -1
  60. package/dist/components/table/components/column-resizer/ColumnResizer.module.css +17 -7
  61. package/dist/components/table/table.utils.d.ts +17 -0
  62. package/dist/components/table/table.utils.js +61 -0
  63. package/dist/components/table/tanstackTable.utils.d.ts +22 -0
  64. package/dist/components/table/tanstackTable.utils.js +104 -0
  65. package/dist/components/tabs/Tabs.d.ts +35 -12
  66. package/dist/components/tabs/Tabs.js +114 -26
  67. package/dist/components/tabs/Tabs.module.css +158 -71
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +1 -1
  70. package/dist/src/styles/styles.css +0 -1
  71. package/dist/styles/styles.css +0 -1
  72. package/dist/styles/themes/dbc/base.css +136 -0
  73. package/dist/styles/themes/dbc/dark.css +39 -202
  74. package/dist/styles/themes/dbc/light.css +17 -174
  75. package/package.json +4 -4
  76. package/dist/components/table/tanstack.js +0 -214
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ButtonVariant } from '../../components/button/Button';
3
3
  import { InputContainer } from '../../components/forms/input-container/InputContainer';
4
+ import { UtcIsoString } from '../datetime-picker/dateTimeHelpers';
4
5
  type InputContainerProps = React.ComponentProps<typeof InputContainer>;
5
6
  export type IntervalOption = {
6
7
  label: string;
@@ -12,9 +13,14 @@ export type IntervalSelectProps = Omit<InputContainerProps, 'children' | 'htmlFo
12
13
  id?: string;
13
14
  options: IntervalOption[];
14
15
  selectedValue: IntervalSelectValue;
15
- onChange: (date: Date, meta: {
16
+ /**
17
+ * Emits a UTC instant as ISO string (Z), consistent with DateTimePicker when enableTime=true.
18
+ * Also includes the computed local Date for convenience.
19
+ */
20
+ onChange: (isoUtc: UtcIsoString, meta: {
16
21
  minutesAgo: number;
17
22
  option: IntervalOption;
23
+ dateLocal: Date;
18
24
  }) => void;
19
25
  /** Base date for the calculation; defaults to "now". */
20
26
  baseDate?: Date;
@@ -23,8 +29,9 @@ export type IntervalSelectProps = Omit<InputContainerProps, 'children' | 'htmlFo
23
29
  variant?: ButtonVariant;
24
30
  onClear?: () => void;
25
31
  dataCy?: string;
32
+ disabled?: boolean;
26
33
  tooltip?: React.ReactNode;
27
34
  tooltipPlacement?: 'top' | 'right' | 'bottom' | 'left';
28
35
  };
29
- export declare function IntervalSelect({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, baseDate, placeholder, size, variant, onClear, dataCy, }: IntervalSelectProps): React.ReactNode;
36
+ export declare function IntervalSelect({ label, error, helpText, orientation, labelWidth, fullWidth, required, tooltip, tooltipPlacement, id, options, selectedValue, onChange, baseDate, placeholder, size, variant, onClear, dataCy, disabled, }: IntervalSelectProps): React.ReactNode;
30
37
  export {};
@@ -8,20 +8,23 @@ import { InputContainer } from '../../components/forms/input-container/InputCont
8
8
  import { Menu } from '../../components/menu/Menu';
9
9
  import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTrigger';
10
10
  import { Popover } from '../../components/popover/Popover';
11
+ import { isoFromLocalDate } from '../datetime-picker/dateTimeHelpers';
11
12
  export function IntervalSelect({
12
13
  // InputContainer props
13
14
  label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required,
14
15
  // tooltip
15
16
  tooltip, tooltipPlacement = 'right',
16
17
  // IntervalSelect props
17
- id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval', size, variant = 'outlined', onClear, dataCy, }) {
18
+ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval', size, variant = 'outlined', onClear, dataCy, disabled, }) {
18
19
  const generatedId = useId();
19
20
  const controlId = id !== null && id !== void 0 ? id : `interval-select-${generatedId}`;
20
21
  const describedById = `${controlId}-desc`;
22
+ const listboxId = `${controlId}-listbox`;
21
23
  const popoverRef = useRef(null);
22
24
  const optionRefs = useRef([]);
23
25
  const selectedIndex = useMemo(() => options.findIndex(o => o.minutesAgo === selectedValue), [options, selectedValue]);
24
26
  const [activeIndex, setActiveIndex] = useState(selectedIndex >= 0 ? selectedIndex : 0);
27
+ // Focus active option when opened (simple version: always focus when index changes)
25
28
  useEffect(() => {
26
29
  var _a;
27
30
  (_a = optionRefs.current[activeIndex]) === null || _a === void 0 ? void 0 : _a.focus();
@@ -44,8 +47,9 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
44
47
  const handleCommit = (opt) => {
45
48
  var _a;
46
49
  const base = baseDate !== null && baseDate !== void 0 ? baseDate : new Date();
47
- const dt = new Date(base.getTime() - opt.minutesAgo * 60000);
48
- onChange(dt, { minutesAgo: opt.minutesAgo, option: opt });
50
+ const dateLocal = new Date(base.getTime() - opt.minutesAgo * 60000);
51
+ const isoUtc = isoFromLocalDate(dateLocal);
52
+ onChange(isoUtc, { minutesAgo: opt.minutesAgo, option: opt, dateLocal });
49
53
  (_a = popoverRef.current) === null || _a === void 0 ? void 0 : _a.close();
50
54
  };
51
55
  const handleKeyDown = (e) => {
@@ -59,6 +63,14 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
59
63
  e.preventDefault();
60
64
  setActiveIndex(i => Math.max(i - 1, 0));
61
65
  break;
66
+ case 'Home':
67
+ e.preventDefault();
68
+ setActiveIndex(0);
69
+ break;
70
+ case 'End':
71
+ e.preventDefault();
72
+ setActiveIndex(options.length - 1);
73
+ break;
62
74
  case 'Enter':
63
75
  case ' ':
64
76
  e.preventDefault();
@@ -71,12 +83,15 @@ id, options, selectedValue, onChange, baseDate, placeholder = 'Vælg interval',
71
83
  break;
72
84
  }
73
85
  };
74
- return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, trigger: (onClick, icon) => (_jsx(Button, { ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'interval-select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
86
+ return (_jsxs(InputContainer, { label: label, htmlFor: controlId, fullWidth: fullWidth, error: error, helpText: helpText, orientation: orientation, labelWidth: labelWidth, required: required, children: [_jsx(Popover, { ref: popoverRef, contentId: listboxId, trigger: (onClick, icon, isOpen) => (_jsx(Button, { disabled: disabled, ...(tooltipEnabled ? triggerProps : {}), id: controlId, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'interval-select-button', onKeyDown: handleKeyDown, fullWidth: fullWidth, variant: variant, onClick: e => {
87
+ // Reset active to selected when opening
75
88
  setActiveIndex(selectedIndex >= 0 ? selectedIndex : 0);
76
89
  onClick(e);
77
- }, size: size, type: "button", "aria-haspopup": "listbox", "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx(Clock, { size: 14 }), selected ? selected.label : placeholder] }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
90
+ }, size: size, type: "button", "aria-haspopup": "listbox", "aria-expanded": !!isOpen, "aria-controls": listboxId, "aria-invalid": Boolean(error) || undefined, "aria-describedby": describedBy, children: _jsxs("span", { className: "dbc-flex dbc-justify-between dbc-items-center dbc-gap-xxs", style: { width: '100%' }, children: [_jsxs("span", { className: "dbc-flex dbc-items-center dbc-gap-xxs", children: [_jsx(Clock, { size: 14 }), selected ? selected.label : placeholder] }), onClear && selected && _jsx(ClearButton, { onClick: onClear }), icon] }) })), children: _jsx(Menu, { id: listboxId, onKeyDown: handleKeyDown, role: "listbox", children: options.map((opt, index) => {
78
91
  const isSelected = opt.minutesAgo === selectedValue;
79
92
  const isActive = index === activeIndex;
80
- return (_jsx(Menu.Item, { active: isActive, "aria-selected": isSelected, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", tabIndex: isActive ? 0 : -1, onClick: () => handleCommit(opt), onFocus: () => setActiveIndex(index), style: { display: 'flex', alignItems: 'center', width: '100%' }, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: isSelected ? _jsx(Check, {}) : null }), opt.label] }) }, opt.minutesAgo));
93
+ return (_jsx(Menu.Item, { active: isActive,
94
+ // IMPORTANT: listbox uses role="option"
95
+ itemRole: "option", "aria-selected": isSelected, children: _jsxs("button", { ref: el => (optionRefs.current[index] = el), type: "button", tabIndex: isActive ? 0 : -1, onClick: () => handleCommit(opt), onFocus: () => setActiveIndex(index), style: { display: 'flex', alignItems: 'center', width: '100%' }, children: [_jsx("span", { style: { width: 16, display: 'inline-flex', justifyContent: 'center' }, children: isSelected ? _jsx(Check, {}) : null }), opt.label] }) }, opt.minutesAgo));
81
96
  }) }) }), (error || helpText) && (_jsx("span", { id: describedById, style: { display: 'none' }, children: error !== null && error !== void 0 ? error : helpText }))] }));
82
97
  }
@@ -1,16 +1,23 @@
1
1
  import * as React from 'react';
2
2
  export interface MenuProps extends React.HTMLAttributes<HTMLUListElement> {
3
3
  children: React.ReactNode;
4
+ /**
5
+ * Default role for items rendered by Menu.Item when it clones/wraps content.
6
+ * - default: "menuitem" (for role="menu")
7
+ * - for Select/listbox usage, pass itemRole="option" and role="listbox" on Menu.
8
+ */
9
+ itemRole?: 'menuitem' | 'option';
4
10
  }
5
- /**
6
- * Use when you need a visual divider inside a Menu.
7
- * Renders a non-interactive <li role="separator" />
8
- */
9
11
  export type MenuSeparatorProps = React.LiHTMLAttributes<HTMLLIElement>;
10
12
  export interface MenuItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
11
13
  children: React.ReactNode;
12
14
  active?: boolean;
13
15
  disabled?: boolean;
16
+ /**
17
+ * Override the role applied to the interactive element for this item only.
18
+ * If not set, Menu's `itemRole` is used.
19
+ */
20
+ itemRole?: 'menuitem' | 'option';
14
21
  }
15
22
  export interface MenuCheckItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
16
23
  label: React.ReactNode;
@@ -19,18 +26,8 @@ export interface MenuCheckItemProps extends Omit<React.LiHTMLAttributes<HTMLLIEl
19
26
  onCheckedChange?: (checked: boolean) => void;
20
27
  }
21
28
  export interface MenuRadioItemProps extends Omit<React.LiHTMLAttributes<HTMLLIElement>, 'onChange'> {
22
- /**
23
- * Shared group name for the radio items in this menu section.
24
- * (Required by your RadioButton component)
25
- */
26
29
  name: string;
27
- /**
28
- * This option's value.
29
- */
30
30
  value: string;
31
- /**
32
- * Whether this radio option is selected.
33
- */
34
31
  checked: boolean;
35
32
  disabled?: boolean;
36
33
  label: string;
@@ -4,29 +4,24 @@ import * as React from 'react';
4
4
  import styles from './Menu.module.css';
5
5
  import { Checkbox } from '../forms/checkbox/Checkbox';
6
6
  import { RadioButton } from '../forms/radio-buttons/RadioButton';
7
- const MenuBase = React.forwardRef(({ children, className, ...props }, ref) => (_jsx("ul", { ref: ref, role: "menu", className: [styles.container, className].filter(Boolean).join(' '), ...props, children: children })));
7
+ const MenuBase = React.forwardRef(({ children, className, itemRole = 'menuitem', ...props }, ref) => (_jsx("ul", { ref: ref, role: "menu", "data-itemrole": itemRole, className: [styles.container, className].filter(Boolean).join(' '), ...props, children: children })));
8
8
  MenuBase.displayName = 'Menu';
9
9
  const isInteractiveEl = (el) => React.isValidElement(el) &&
10
10
  (typeof el.type === 'string' ? el.type === 'a' || el.type === 'button' : true);
11
- /**
12
- * Apply menu styles not only to the interactive element itself,
13
- * but also to immediate children (covers cases where the direct child is a <div>
14
- * wrapping a button-like thing, or components that render a wrapper).
15
- */
16
11
  function applyMenuItemPropsToElement(child, opts) {
17
- const { active, disabled, role = 'menuitem', tabIndex = -1, className } = opts;
12
+ var _a, _b, _c, _d;
13
+ const { active, disabled, role, tabIndex = -1, className } = opts;
18
14
  const childClass = [styles.item, active ? styles.active : ''].filter(Boolean).join(' ');
19
- // Always apply styling to the immediate child
20
15
  const nextImmediate = React.cloneElement(child, {
21
16
  className: [child.props.className, styles.interactiveChild, className]
22
17
  .filter(Boolean)
23
18
  .join(' '),
24
19
  });
25
- // If the immediate child is already interactive, we can apply full a11y+styles there
20
+ // If immediate child is interactive, apply a11y+styles there
26
21
  if (typeof child.type === 'string' && (child.type === 'a' || child.type === 'button')) {
27
22
  return React.cloneElement(child, {
28
- role,
29
- tabIndex,
23
+ role: (_a = child.props.role) !== null && _a !== void 0 ? _a : role,
24
+ tabIndex: (_b = child.props.tabIndex) !== null && _b !== void 0 ? _b : tabIndex,
30
25
  'aria-selected': active || undefined,
31
26
  'aria-disabled': disabled || undefined,
32
27
  className: [child.props.className, styles.interactive, childClass, className]
@@ -35,10 +30,10 @@ function applyMenuItemPropsToElement(child, opts) {
35
30
  ...(child.type === 'button' ? { disabled } : {}),
36
31
  });
37
32
  }
38
- // For custom components, we *assume* they forward props.
33
+ // For custom components, assume they forward props
39
34
  return React.cloneElement(nextImmediate, {
40
- role,
41
- tabIndex,
35
+ role: (_c = nextImmediate.props.role) !== null && _c !== void 0 ? _c : role,
36
+ tabIndex: (_d = nextImmediate.props.tabIndex) !== null && _d !== void 0 ? _d : tabIndex,
42
37
  'aria-selected': active || undefined,
43
38
  'aria-disabled': disabled || undefined,
44
39
  className: [nextImmediate.props.className, styles.interactive, childClass]
@@ -47,36 +42,26 @@ function applyMenuItemPropsToElement(child, opts) {
47
42
  disabled,
48
43
  });
49
44
  }
50
- const MenuItem = React.forwardRef(({ children, active, disabled, className, ...liProps }, ref) => {
45
+ const MenuItem = React.forwardRef(({ children, active, disabled, className, itemRole, ...liProps }, ref) => {
46
+ // If caller sets itemRole prop, use it; otherwise attempt to inherit from parent Menu via data attr.
47
+ // (We can’t reliably read parent props here without context; simplest is: caller passes itemRole on Menu.Item when needed.)
48
+ const resolvedRole = itemRole !== null && itemRole !== void 0 ? itemRole : 'menuitem';
51
49
  if (isInteractiveEl(children)) {
52
50
  const child = children;
53
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: applyMenuItemPropsToElement(child, { active, disabled }) }));
51
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: applyMenuItemPropsToElement(child, { active, disabled, role: resolvedRole }) }));
54
52
  }
55
- // Fallback: we wrap non-interactive children in a <button>
56
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("button", { role: "menuitem", tabIndex: -1, "aria-selected": active || undefined, "aria-disabled": disabled || undefined, className: [styles.interactive, styles.item, active ? styles.active : '']
53
+ // Fallback: wrap non-interactive children in a <button>
54
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("button", { role: resolvedRole, tabIndex: -1, "aria-selected": active || undefined, "aria-disabled": disabled || undefined, className: [styles.interactive, styles.item, active ? styles.active : '']
57
55
  .filter(Boolean)
58
56
  .join(' '), type: "button", disabled: disabled, children: children }) }));
59
57
  });
60
58
  MenuItem.displayName = 'Menu.Item';
61
- /**
62
- * Menu checkbox row that uses your Checkbox component.
63
- * Note: this is a *control inside a menu*, so we lean on your Checkbox a11y (`role="checkbox"`)
64
- * rather than forcing `menuitemcheckbox` roles.
65
- */
66
59
  const MenuCheckItem = React.forwardRef(({ label, checked, disabled, onCheckedChange, className, ...liProps }, ref) => {
67
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label,
68
- // Your Checkbox emits (checked, mouseEvent)
69
- onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
60
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(Checkbox, { noContainer: true, checked: checked, disabled: disabled, label: label, onChange: (next, _e) => onCheckedChange === null || onCheckedChange === void 0 ? void 0 : onCheckedChange(next) }) }) }));
70
61
  });
71
62
  MenuCheckItem.displayName = 'Menu.CheckItem';
72
- /**
73
- * Menu radio row that uses your RadioButton component.
74
- * Same note as above: we keep your native radio semantics.
75
- */
76
63
  const MenuRadioItem = React.forwardRef(({ name, value, checked, disabled, label, onValueChange, className, ...liProps }, ref) => {
77
- return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(RadioButton, { noContainer: true, name: name, value: value, checked: checked, disabled: disabled, label: label,
78
- // Your RadioButton emits (value, changeEvent)
79
- onChange: (v, _e) => onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(v) }) }) }));
64
+ return (_jsx("li", { ref: ref, role: "none", className: [styles.row, className].filter(Boolean).join(' '), ...liProps, children: _jsx("div", { className: styles.interactiveChild, children: _jsx(RadioButton, { noContainer: true, name: name, value: value, checked: checked, disabled: disabled, label: label, onChange: (v, _e) => onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(v) }) }) }));
80
65
  });
81
66
  MenuRadioItem.displayName = 'Menu.RadioItem';
82
67
  const MenuSeparator = React.forwardRef(({ className, ...props }, ref) => (_jsx("li", { ref: ref, role: "separator", className: [styles.separator, className].filter(Boolean).join(' '), ...props })));
@@ -29,7 +29,7 @@
29
29
  text-decoration: none;
30
30
 
31
31
  /* choose your density */
32
- padding-block: calc(var(--spacing-xs) + var(--density-comfortable));
32
+ padding-block: var(--spacing-xs);
33
33
  padding-inline: var(--spacing-md);
34
34
 
35
35
  background: transparent;
@@ -62,7 +62,7 @@
62
62
  display: flex;
63
63
  align-items: center;
64
64
  inline-size: 100%;
65
- padding-block: calc(var(--spacing-xxs) + var(--density-comfortable));
65
+ padding-block: var(--spacing-xxs);
66
66
  padding-inline: var(--spacing-md);
67
67
  border-radius: var(--border-radius-sm);
68
68
  }
@@ -7,7 +7,7 @@
7
7
  justify-content: center;
8
8
  padding-top: clamp(var(--spacing-md), 12vh, 24vh);
9
9
  padding-bottom: var(--spacing-md);
10
- z-index: var(--z-modal);
10
+ z-index: var(--z-backdrop-modal);
11
11
  overflow-y: auto;
12
12
  }
13
13
 
@@ -22,6 +22,7 @@
22
22
  box-shadow: var(--shadow-lg);
23
23
  font-family: var(--font-family);
24
24
  min-width: 500px;
25
+ z-index: var(--z-modal);
25
26
  color: var(--color-fg-default);
26
27
  }
27
28
 
@@ -43,20 +43,18 @@ export function ModalProvider({ children }) {
43
43
  label: confirmLabel,
44
44
  onClick: () => {
45
45
  resolvePending(true);
46
- closeModal();
47
46
  },
48
47
  },
49
48
  secondaryAction: {
50
49
  label: cancelLabel,
51
50
  onClick: () => {
52
51
  resolvePending(false);
53
- closeModal();
54
52
  },
55
53
  },
56
54
  });
57
55
  setIsOpen(true);
58
56
  });
59
- }, [closeModal, resolvePending]);
57
+ }, [resolvePending]);
60
58
  const modalNode = (_jsxs(ModalContext.Provider, { value: { openModal, closeModal, confirm }, children: [children, mounted &&
61
59
  createPortal(_jsx(Modal, { ...(config !== null && config !== void 0 ? config : {}), isOpen: isOpen, onRequestClose: handleRequestClose, children: config === null || config === void 0 ? void 0 : config.children }), document.body)] }));
62
60
  return modalNode;
@@ -79,5 +79,5 @@ export function SidePanel({ isOpen, onClose, children, header, headerAddition, a
79
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 => {
80
80
  e.stopPropagation();
81
81
  onClose(e);
82
- }, "aria-label": "Close panel", 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);
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);
83
83
  }
@@ -157,7 +157,7 @@
157
157
  position: fixed;
158
158
  inset: 0;
159
159
  background-color: rgba(0, 0, 0, 0.45);
160
- z-index: var(--z-backdrop);
160
+ z-index: var(--z-backdrop-drawer);
161
161
 
162
162
  opacity: 0;
163
163
  transition: opacity var(--panel-dur) var(--panel-ease);
@@ -1,11 +1,14 @@
1
- import type { PropsWithChildren, ReactNode, FC } from 'react';
2
- import { PageLayoutHeroProps } from './components/page-layout-hero/PageLayoutHero';
1
+ import type { FC, PropsWithChildren, ReactNode } from 'react';
2
+ import { type PageLayoutHeroProps } from './components/page-layout-hero/PageLayoutHero';
3
3
  type Orientation = 'vertical' | 'horizontal';
4
4
  export interface PageLayoutProps extends PropsWithChildren {
5
+ /**
6
+ * If true, PageLayout becomes a self-contained app shell (100vh) and
7
+ * scroll is contained inside the intended regions (sidebar/content).
8
+ * If false, the document scrolls normally (body scroll).
9
+ */
5
10
  containScrolling?: boolean;
6
11
  orientation?: Orientation;
7
- sidebar?: ReactNode;
8
- header?: ReactNode;
9
12
  }
10
13
  export interface PageLayoutHeaderProps {
11
14
  maxWidth?: boolean;
@@ -18,7 +21,16 @@ export interface PageLayoutContentProps {
18
21
  export interface PageLayoutFooterProps {
19
22
  children: ReactNode;
20
23
  }
24
+ export interface PageLayoutSidebarProps {
25
+ children: ReactNode;
26
+ /**
27
+ * Optional: if your sidebar contains navigation, you might wrap your nav in <nav>.
28
+ * This component will render an <aside>.
29
+ */
30
+ ariaLabel?: string;
31
+ }
21
32
  export declare const PageLayout: FC<PageLayoutProps> & {
33
+ Sidebar: FC<PageLayoutSidebarProps>;
22
34
  Header: FC<PageLayoutHeaderProps>;
23
35
  Hero: FC<PageLayoutHeroProps>;
24
36
  Content: FC<PageLayoutContentProps>;
@@ -1,38 +1,67 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { PageLayoutHero } from './components/page-layout-hero/PageLayoutHero';
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Children, isValidElement } from 'react';
3
+ import { PageLayoutHero, } from './components/page-layout-hero/PageLayoutHero';
4
4
  import styles from './PageLayout.module.css';
5
- const PageLayoutBase = ({ children, sidebar, header, containScrolling = false, orientation = 'vertical', }) => {
6
- if (orientation === 'vertical') {
7
- return (_jsx("div", { className: `${styles.container} ${styles.vertical} ${containScrolling ? styles.containScrolling : ''}`, children: _jsxs("div", { style: { flex: 1, display: 'flex', height: '100%', maxWidth: '100%' }, children: [sidebar, _jsxs("div", { style: {
8
- flex: 1,
9
- display: 'flex',
10
- flexDirection: 'column',
11
- height: '100%',
12
- maxHeight: '100vh',
13
- overflow: 'auto',
14
- }, children: [header && _jsx("div", { className: styles.header, children: header }), _jsx("div", { style: {
15
- flex: 1,
16
- display: 'flex',
17
- flexDirection: 'column',
18
- padding: 'var(--spacing-md)',
19
- backgroundColor: 'var(--color-bg-surface)',
20
- overflow: 'auto',
21
- }, children: children })] })] }) }));
22
- }
23
- return (_jsx("div", { className: `${styles.container} ${containScrolling ? styles.containScrolling : ''}`, children: children }));
5
+ function getSlotName(el) {
6
+ var _a;
7
+ const t = el.type;
8
+ return (_a = t === null || t === void 0 ? void 0 : t.__PAGE_LAYOUT_SLOT__) !== null && _a !== void 0 ? _a : null;
9
+ }
10
+ function splitSlots(children) {
11
+ const slots = {};
12
+ const rest = [];
13
+ Children.forEach(children, child => {
14
+ if (!isValidElement(child)) {
15
+ if (child != null)
16
+ rest.push(child);
17
+ return;
18
+ }
19
+ const slot = getSlotName(child);
20
+ if (!slot) {
21
+ rest.push(child);
22
+ return;
23
+ }
24
+ // If multiple of same slot are provided, last wins (simple + predictable).
25
+ slots[slot] = child;
26
+ });
27
+ return { slots, rest };
28
+ }
29
+ /** Slot components */
30
+ const PageLayoutSidebar = ({ children, }) => {
31
+ // This returns children; layout wrapper decides how to render.
32
+ // We keep this as a slot marker component.
33
+ return _jsx(_Fragment, { children: children });
24
34
  };
25
- /** Subcomponents */
26
- const PageLayoutHeader = ({ maxWidth = true, children, }) => {
35
+ PageLayoutSidebar.__PAGE_LAYOUT_SLOT__ = 'Sidebar';
36
+ const PageLayoutHeader = ({ maxWidth = false, children, }) => {
27
37
  return (_jsx("div", { className: styles.headerContainer, children: _jsx("div", { className: `${styles.headerContent} ${maxWidth ? styles.maxWidth : ''}`, children: children }) }));
28
38
  };
29
- const PageLayoutContent = ({ maxWidth = true, children, }) => {
30
- return _jsx("main", { className: `${styles.content} ${maxWidth ? styles.maxWidth : ''}`, children: children });
39
+ PageLayoutHeader.__PAGE_LAYOUT_SLOT__ = 'Header';
40
+ const PageLayoutContent = ({ maxWidth = false, children, }) => {
41
+ return (_jsx("div", { className: `${styles.contentInner} ${maxWidth ? styles.maxWidth : ''}`, children: children }));
31
42
  };
32
- const PageLayoutFooter = ({ children }) => {
33
- return _jsx("footer", { className: styles.footer, children: children });
43
+ PageLayoutContent.__PAGE_LAYOUT_SLOT__ = 'Content';
44
+ const PageLayoutFooter = ({ children, }) => {
45
+ return _jsx(_Fragment, { children: children });
46
+ };
47
+ PageLayoutFooter.__PAGE_LAYOUT_SLOT__ = 'Footer';
48
+ PageLayoutHero.__PAGE_LAYOUT_SLOT__ = 'Hero';
49
+ const PageLayoutBase = ({ children, containScrolling = false, orientation = 'vertical', }) => {
50
+ var _a, _b;
51
+ const { slots, rest } = splitSlots(children);
52
+ // If no explicit <PageLayout.Content>, we’ll treat remaining children as content.
53
+ const content = (_a = slots.Content) !== null && _a !== void 0 ? _a : (rest.length ? _jsx(PageLayoutContent, { maxWidth: true, children: rest }) : undefined);
54
+ const rootClass = [
55
+ styles.root,
56
+ orientation === 'vertical' ? styles.vertical : styles.horizontal,
57
+ containScrolling ? styles.containScrolling : styles.documentScrolling,
58
+ ]
59
+ .filter(Boolean)
60
+ .join(' ');
61
+ return (_jsxs("div", { className: rootClass, children: [slots.Sidebar ? (_jsx("aside", { className: styles.sidebar, "aria-label": (_b = slots.Sidebar.props) === null || _b === void 0 ? void 0 : _b.ariaLabel, children: slots.Sidebar })) : null, _jsxs("div", { className: styles.mainColumn, children: [slots.Header ? _jsx("header", { className: styles.header, children: slots.Header }) : null, slots.Hero ? _jsx("div", { className: styles.hero, children: slots.Hero }) : null, content || slots.Footer ? (_jsxs("div", { className: styles.mainScroll, children: [content ? _jsx("main", { className: styles.content, children: content }) : null, slots.Footer ? _jsx("footer", { className: styles.footer, children: slots.Footer }) : null] })) : null] })] }));
34
62
  };
35
63
  export const PageLayout = Object.assign(PageLayoutBase, {
64
+ Sidebar: PageLayoutSidebar,
36
65
  Header: PageLayoutHeader,
37
66
  Hero: PageLayoutHero,
38
67
  Content: PageLayoutContent,