@dbcdk/react-components 0.0.9 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/components/accordion/Accordion.d.ts +27 -0
  2. package/dist/components/accordion/Accordion.js +66 -0
  3. package/dist/components/accordion/Accordion.module.css +87 -0
  4. package/dist/components/button/Button.module.css +1 -0
  5. package/dist/components/card/Card.d.ts +21 -3
  6. package/dist/components/card/Card.js +17 -2
  7. package/dist/components/card/Card.module.css +59 -0
  8. package/dist/components/circle/Circle.d.ts +5 -1
  9. package/dist/components/circle/Circle.js +2 -2
  10. package/dist/components/circle/Circle.module.css +60 -4
  11. package/dist/components/code-block/CodeBlock.js +1 -1
  12. package/dist/components/code-block/CodeBlock.module.css +30 -17
  13. package/dist/components/copy-button/CopyButton.d.ts +1 -0
  14. package/dist/components/copy-button/CopyButton.js +10 -2
  15. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
  16. package/dist/components/datetime-picker/DateTimePicker.js +72 -92
  17. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
  18. package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
  19. package/dist/components/filter-field/FilterField.js +16 -11
  20. package/dist/components/filter-field/FilterField.module.css +133 -12
  21. package/dist/components/forms/checkbox/Checkbox.d.ts +4 -10
  22. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  23. package/dist/components/forms/checkbox-group/CheckboxGroup.js +1 -1
  24. package/dist/components/forms/checkbox-group/CheckboxGroup.module.css +1 -1
  25. package/dist/components/forms/input/Input.d.ts +1 -0
  26. package/dist/components/forms/input/Input.js +2 -4
  27. package/dist/components/forms/input/Input.module.css +10 -11
  28. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  29. package/dist/components/forms/input-container/InputContainer.js +3 -3
  30. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  31. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  32. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  33. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  34. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  35. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  36. package/dist/components/forms/select/Select.d.ts +1 -1
  37. package/dist/components/forms/select/Select.js +3 -3
  38. package/dist/components/forms/text-area/Textarea.js +3 -3
  39. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  40. package/dist/components/headline/Headline.d.ts +2 -7
  41. package/dist/components/headline/Headline.js +5 -2
  42. package/dist/components/headline/Headline.module.css +61 -2
  43. package/dist/components/hyperlink/Hyperlink.d.ts +19 -6
  44. package/dist/components/hyperlink/Hyperlink.js +35 -7
  45. package/dist/components/hyperlink/Hyperlink.module.css +50 -2
  46. package/dist/components/icon/Icon.module.css +1 -0
  47. package/dist/components/interval-select/IntervalSelect.js +1 -1
  48. package/dist/components/menu/Menu.d.ts +32 -0
  49. package/dist/components/menu/Menu.js +73 -13
  50. package/dist/components/menu/Menu.module.css +72 -4
  51. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  52. package/dist/components/overlay/modal/Modal.module.css +2 -2
  53. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  54. package/dist/components/overlay/side-panel/SidePanel.js +77 -4
  55. package/dist/components/overlay/side-panel/SidePanel.module.css +149 -28
  56. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  57. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  58. package/dist/components/overlay/tooltip/useTooltipTrigger.js +4 -2
  59. package/dist/components/page-layout/PageLayout.js +0 -2
  60. package/dist/components/popover/Popover.js +1 -1
  61. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  62. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +36 -24
  63. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  64. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  65. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  66. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  67. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  68. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  69. package/dist/components/sidebar/providers/SidebarProvider.d.ts +4 -1
  70. package/dist/components/sidebar/providers/SidebarProvider.js +85 -58
  71. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  72. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  73. package/dist/components/split-button/SplitButton.d.ts +1 -1
  74. package/dist/components/split-button/SplitButton.js +3 -1
  75. package/dist/components/split-button/SplitButton.module.css +4 -4
  76. package/dist/components/state-page/StatePage.d.ts +9 -0
  77. package/dist/components/state-page/StatePage.js +20 -0
  78. package/dist/components/state-page/StatePage.module.css +9 -0
  79. package/dist/components/state-page/empty.d.ts +2 -0
  80. package/dist/components/state-page/empty.js +2 -0
  81. package/dist/components/state-page/error.d.ts +2 -0
  82. package/dist/components/state-page/error.js +2 -0
  83. package/dist/components/state-page/notFound.d.ts +2 -0
  84. package/dist/components/state-page/notFound.js +2 -0
  85. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  86. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  87. package/dist/components/table/Table.d.ts +9 -4
  88. package/dist/components/table/Table.js +6 -9
  89. package/dist/components/table/Table.module.css +180 -59
  90. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  91. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  92. package/dist/components/table/components/table-settings/TableSettings.d.ts +13 -3
  93. package/dist/components/table/components/table-settings/TableSettings.js +55 -4
  94. package/dist/components/table/tanstack.d.ts +12 -1
  95. package/dist/components/table/tanstack.js +75 -23
  96. package/dist/components/toast/Toast.js +5 -1
  97. package/dist/components/toast/Toast.module.css +40 -15
  98. package/dist/components/toast/provider/ToastProvider.js +1 -0
  99. package/dist/hooks/useTableSettings.d.ts +23 -4
  100. package/dist/hooks/useTableSettings.js +64 -17
  101. package/dist/hooks/useTimeDuration.js +9 -3
  102. package/dist/hooks/useViewportFill.js +1 -0
  103. package/dist/index.d.ts +6 -0
  104. package/dist/index.js +6 -1
  105. package/dist/src/styles/styles.css +60 -25
  106. package/dist/styles/animation.d.ts +5 -0
  107. package/dist/styles/animation.js +5 -0
  108. package/dist/styles/styles.css +60 -25
  109. package/dist/styles/themes/dbc/dark.css +1 -1
  110. package/dist/styles/themes/dbc/light.css +2 -1
  111. package/dist/utils/localStorage.utils.d.ts +19 -0
  112. package/dist/utils/localStorage.utils.js +78 -0
  113. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ /* Menu.module.css */
1
2
  .container {
2
3
  list-style: none;
3
4
  margin: 0;
@@ -17,6 +18,7 @@
17
18
  display: contents;
18
19
  }
19
20
 
21
+ /* Applied to actual interactive elements (button/a/custom that forwards className) */
20
22
  .interactive {
21
23
  display: flex;
22
24
  align-items: center;
@@ -45,29 +47,95 @@
45
47
  color var(--transition-fast) var(--ease-standard);
46
48
  }
47
49
 
48
- .interactive:hover {
50
+ /*
51
+ Applied to the immediate child of <li> even if it's NOT an interactive element (e.g. a <div>)
52
+ so that menu row styling still works for components that render a wrapper.
53
+ */
54
+ .interactiveChild {
55
+ display: block;
56
+ inline-size: 100%;
57
+ border-radius: var(--border-radius-sm);
58
+ }
59
+
60
+ /* NEW: make wrapper-children (Checkbox/Radio) look/space like menu rows */
61
+ .row > .interactiveChild {
62
+ display: flex;
63
+ align-items: center;
64
+ inline-size: 100%;
65
+ padding-block: calc(var(--spacing-xxs) + var(--density-comfortable));
66
+ padding-inline: var(--spacing-md);
67
+ border-radius: var(--border-radius-sm);
68
+ }
69
+
70
+ /* NEW: let Checkbox/Radio consume full width so the hover area feels right */
71
+ .row > .interactiveChild > * {
72
+ inline-size: 100%;
73
+ }
74
+
75
+ /* NEW: add consistent gap between control and label inside Checkbox/Radio
76
+ Both components use a root element with className={styles.container}.
77
+ Because they're CSS modules, we must target it with :global(.container). */
78
+ .row :global(.container) {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: var(--spacing-sm);
82
+ }
83
+
84
+ /* Hover: support both cases (interactive element, or wrapper child) */
85
+ .interactive:hover,
86
+ .row:hover > .interactiveChild {
49
87
  background-color: var(--color-bg-hover-subtle);
50
88
  }
51
89
 
90
+ /* Focus ring: support both cases */
52
91
  .interactive:focus-visible {
53
92
  outline: none;
54
93
  box-shadow: var(--focus-ring);
55
94
  }
56
95
 
96
+ /* If wrapper contains a focusable element, show ring when any child is focused */
97
+ .row:focus-within > .interactiveChild {
98
+ outline: none;
99
+ box-shadow: var(--focus-ring);
100
+ }
101
+
102
+ /* Selected/active (legacy + item variant) */
57
103
  .active,
58
- .interactive[aria-selected='true'] {
104
+ .interactive[aria-selected='true'],
105
+ .row > .interactiveChild.active,
106
+ .row > .interactiveChild[aria-selected='true'] {
59
107
  background-color: var(--color-bg-selected);
60
108
  color: var(--color-fg-default);
61
109
  }
62
110
 
111
+ /* Checked (legacy support; kept in case any interactive element still uses aria-checked) */
112
+ .interactive[aria-checked='true'],
113
+ .row > .interactiveChild[aria-checked='true'] {
114
+ background-color: var(--color-bg-selected);
115
+ color: var(--color-fg-default);
116
+ }
117
+
118
+ /* Disabled: support both cases */
63
119
  .interactive[aria-disabled='true'],
64
- .interactive:disabled {
120
+ .interactive:disabled,
121
+ .row > .interactiveChild[aria-disabled='true'] {
65
122
  color: var(--color-disabled-fg);
66
123
  cursor: not-allowed;
67
124
  pointer-events: none;
68
125
  }
69
126
 
70
- .interactive svg {
127
+ /* Icons inside either interactive element or wrapper */
128
+ .interactive svg,
129
+ .interactiveChild svg {
71
130
  inline-size: var(--icon-size-md);
72
131
  block-size: var(--icon-size-md);
73
132
  }
133
+
134
+ /* Visual separator row (used by <Menu.Separator />) */
135
+ .separator {
136
+ block-size: 1px;
137
+ margin-block: var(--spacing-2xs);
138
+ background: var(--color-border-subtle);
139
+ opacity: 0.8;
140
+ border-radius: 999px;
141
+ }
@@ -1,18 +1,36 @@
1
1
  import type { ElementType, ReactNode, JSX } from 'react';
2
- export type NavBarItem = {
3
- component?: ElementType<any>;
2
+ export type NavBarItem = NavBarLinkItem | NavBarExpandableItem | NavBarGroupItem;
3
+ type NavBarBase = {
4
4
  label: string;
5
5
  icon?: ReactNode;
6
+ enabled?: boolean;
7
+ tags?: string[];
8
+ };
9
+ /** Simple clickable item */
10
+ export type NavBarLinkItem = NavBarBase & {
11
+ type?: 'item';
12
+ href: string;
13
+ component?: ElementType<any>;
6
14
  active?: boolean;
7
15
  external?: boolean;
8
- enabled?: boolean;
16
+ };
17
+ /** Clickable + expandable item (has href AND children) */
18
+ export type NavBarExpandableItem = NavBarBase & {
19
+ type: 'expandable';
9
20
  href: string;
10
- tags?: string[];
11
- children?: NavBarItem[];
21
+ component?: ElementType<any>;
22
+ active?: boolean;
23
+ external?: boolean;
24
+ children: NavBarItem[];
25
+ };
26
+ /** Non-clickable group header */
27
+ export type NavBarGroupItem = NavBarBase & {
28
+ type: 'group';
29
+ children: NavBarItem[];
12
30
  };
13
31
  interface NavBarProps {
14
32
  logo?: ReactNode;
15
- items: NavBarItem[];
33
+ items: NavBarLinkItem[];
16
34
  productName?: string;
17
35
  addition?: ReactNode;
18
36
  }
@@ -15,13 +15,13 @@
15
15
  background: var(--color-bg-surface);
16
16
  border-radius: var(--border-radius-lg);
17
17
  min-width: 320px;
18
- max-width: 540px;
18
+ max-width: 700px;
19
19
  max-height: calc(100vh - (2 * var(--spacing-md)));
20
20
  display: flex;
21
21
  flex-direction: column;
22
22
  box-shadow: var(--shadow-lg);
23
23
  font-family: var(--font-family);
24
- min-width: 400px;
24
+ min-width: 500px;
25
25
  color: var(--color-fg-default);
26
26
  }
27
27
 
@@ -1,16 +1,24 @@
1
- import type { HTMLAttributes, ReactNode } from 'react';
1
+ import React, { type HTMLAttributes, type ReactNode } from 'react';
2
2
  import { Severity } from '../../../constants/severity.types';
3
3
  interface SidePanelProps {
4
4
  children?: ReactNode;
5
5
  header: ReactNode;
6
6
  headerAddition?: ReactNode;
7
- actions: ReactNode;
8
- onClose: () => void;
7
+ actions?: ReactNode;
8
+ onClose: (event?: React.MouseEvent<HTMLButtonElement> | React.MouseEvent<HTMLDivElement>) => void;
9
9
  isOpen: boolean;
10
10
  showBackdrop?: boolean;
11
11
  severity?: Severity;
12
12
  showHeaderMarker?: boolean;
13
13
  width?: number | string;
14
+ /**
15
+ * Optional details pane (separate column).
16
+ */
17
+ details?: ReactNode;
18
+ detailsHeader?: ReactNode;
19
+ detailsWidth?: number | string;
20
+ onCloseDetails?: () => void;
21
+ detailsHeaderAddition?: ReactNode;
14
22
  }
15
- export declare function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
23
+ export declare function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop, severity, showHeaderMarker, width, details, detailsHeader, detailsWidth, onCloseDetails, detailsHeaderAddition, ...props }: SidePanelProps & HTMLAttributes<HTMLElement>): ReactNode;
16
24
  export {};
@@ -1,10 +1,83 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { X } from 'lucide-react';
3
+ import { useEffect, useRef, useState, } from 'react';
4
+ import { createPortal } from 'react-dom';
3
5
  import { Button } from '../../../components/button/Button';
4
6
  import { Headline } from '../../../components/headline/Headline';
7
+ import { MOTION_MS } from '../../../styles/animation';
5
8
  import styles from './SidePanel.module.css';
6
- export function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop = true, severity, showHeaderMarker = true, width = '300px', ...props }) {
7
- return (_jsxs(_Fragment, { children: [isOpen && showBackdrop && (_jsx("div", { className: styles.backdrop, onClick: () => {
8
- onClose();
9
- } })), _jsxs("div", { ...props, className: `${styles.sidePanel} ${isOpen ? styles.open : ''}`, style: { '--side-panel-width': width }, 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, addition: headerAddition, children: header }), _jsx(Button, { type: "button", size: "sm", variant: "inline", onClick: onClose, children: _jsx(X, {}) })] }) }), _jsx("div", { className: styles.content, children: children }), _jsx("div", { className: styles.actions, children: actions })] })] }));
9
+ export function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop = true, severity, showHeaderMarker = true, width = '400px', details, detailsHeader = 'Output', detailsWidth = '420px', onCloseDetails, detailsHeaderAddition, ...props }) {
10
+ const [mounted, setMounted] = useState(false);
11
+ const [shouldRender, setShouldRender] = useState(isOpen);
12
+ const [isActive, setIsActive] = useState(false);
13
+ const panelRef = useRef(null);
14
+ useEffect(() => setMounted(true), []);
15
+ // OPEN: ensure rendered so animation can play
16
+ useEffect(() => {
17
+ if (isOpen)
18
+ setShouldRender(true);
19
+ }, [isOpen]);
20
+ // Close on ESC key
21
+ useEffect(() => {
22
+ if (!isOpen)
23
+ return;
24
+ const handleKeyDown = (e) => {
25
+ if (e.key === 'Escape') {
26
+ e.stopPropagation();
27
+ onClose();
28
+ }
29
+ };
30
+ document.addEventListener('keydown', handleKeyDown);
31
+ return () => {
32
+ document.removeEventListener('keydown', handleKeyDown);
33
+ };
34
+ }, [isOpen, onClose]);
35
+ // Two-phase OPEN/CLOSE class toggle (lets CSS transitions kick in reliably)
36
+ useEffect(() => {
37
+ if (!shouldRender)
38
+ return;
39
+ if (!isOpen) {
40
+ setIsActive(false);
41
+ return;
42
+ }
43
+ const raf = requestAnimationFrame(() => setIsActive(true));
44
+ return () => cancelAnimationFrame(raf);
45
+ }, [isOpen, shouldRender]);
46
+ // When closing: wait for transform transition end to unmount.
47
+ useEffect(() => {
48
+ if (!shouldRender)
49
+ return;
50
+ const el = panelRef.current;
51
+ if (!el)
52
+ return;
53
+ const onTransitionEnd = (e) => {
54
+ if (e.target !== el)
55
+ return;
56
+ if (e.propertyName !== 'transform')
57
+ return;
58
+ if (!isOpen)
59
+ setShouldRender(false);
60
+ };
61
+ el.addEventListener('transitionend', onTransitionEnd);
62
+ return () => el.removeEventListener('transitionend', onTransitionEnd);
63
+ }, [isOpen, shouldRender]);
64
+ if (!mounted)
65
+ return null;
66
+ if (!shouldRender)
67
+ return null;
68
+ const hasDetails = Boolean(details);
69
+ return createPortal(_jsxs(_Fragment, { children: [showBackdrop && (_jsx("div", { className: `${styles.backdrop} ${isActive ? styles.backdropOpen : ''}`, onClick: e => {
70
+ e.stopPropagation();
71
+ onClose(e);
72
+ } })), _jsxs("div", { ref: panelRef, ...props, className: `${styles.sidePanel} ${isActive ? styles.open : ''} ${hasDetails ? styles.withDetails : styles.noDetails}`, style: {
73
+ '--side-panel-width': width,
74
+ '--details-width': detailsWidth,
75
+ '--panel-dur': MOTION_MS.panelSlide + 'ms',
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
+ e.stopPropagation();
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 => {
80
+ e.stopPropagation();
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);
10
83
  }
@@ -1,56 +1,177 @@
1
1
  .sidePanel {
2
- padding: 0 var(--spacing-md);
2
+ --col-pad: var(--spacing-md);
3
+
4
+ --panel-ease: cubic-bezier(0.22, 1, 0.36, 1); /* smooth spring-ish without overshoot */
5
+
6
+ /* Shadow + edge */
7
+ --shadow-opacity: 0.22;
8
+ --edge-opacity: 0.12;
9
+
10
+ box-sizing: border-box;
11
+ padding: 0;
3
12
  height: 100vh;
4
- width: var(--side-panel-width, 400px);
13
+
5
14
  position: fixed;
6
15
  right: 0;
7
16
  top: 0;
8
- display: flex;
9
- flex-direction: column;
10
- overflow: auto;
17
+
18
+ background-color: var(--color-bg-surface);
19
+ z-index: var(--z-drawer);
20
+
21
+ transform: translate3d(100%, 0, 0);
22
+ transition: transform var(--panel-dur) var(--panel-ease);
23
+ will-change: transform;
24
+
25
+ display: grid;
26
+ gap: var(--spacing-md);
27
+ align-items: stretch;
28
+
29
+ overflow: hidden;
30
+ pointer-events: auto;
31
+
32
+ /* Make pseudo-elements layer properly */
33
+ isolation: isolate;
34
+ }
35
+
36
+ /* “Lift” layer: drop-shadow + edge highlight (both fade in) */
37
+ .sidePanel::before {
38
+ content: '';
39
+ position: absolute;
40
+ inset: 0;
41
+ pointer-events: none;
42
+ z-index: -1; /* behind content but within isolated stacking context */
43
+
44
+ /* 1) drop-shadow = smoother than animating box-shadow
45
+ 2) edge gradient = premium depth cue */
46
+ filter: drop-shadow(0 16px 32px rgba(0, 0, 0, var(--shadow-opacity)));
47
+ opacity: 0;
48
+
49
+ /* Edge highlight from the left edge (panel’s leading edge) */
50
+ background: linear-gradient(
51
+ 90deg,
52
+ rgba(255, 255, 255, var(--edge-opacity)),
53
+ rgba(255, 255, 255, 0) 36%
54
+ );
55
+
56
+ transition: opacity var(--panel-dur) var(--panel-ease);
57
+
58
+ will-change: opacity;
59
+ }
60
+
61
+ /* Optional: super subtle “sheen” right as it settles (feels snappy) */
62
+ .sidePanel.open::before {
63
+ opacity: 1;
64
+ }
65
+
66
+ .sidePanel.open {
67
+ transform: translate3d(0, 0, 0);
68
+ }
69
+
70
+ /* MAIN TRACK WIDTH = content + padding*2 */
71
+ .sidePanel.noDetails {
72
+ grid-template-columns: calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2));
73
+ width: calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2));
74
+ }
75
+
76
+ /* when details show, main track stays the same; panel grows */
77
+ .sidePanel.withDetails {
78
+ grid-template-columns:
79
+ calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2))
80
+ calc(var(--details-width, 420px) + (var(--col-pad) * 2));
81
+
82
+ width: calc(
83
+ (var(--side-panel-width, 400px) + (var(--col-pad) * 2)) +
84
+ (var(--details-width, 420px) + (var(--col-pad) * 2)) + var(--spacing-md)
85
+ );
86
+ }
87
+
88
+ /* columns get the padding */
89
+ .mainCol,
90
+ .detailsCol {
91
+ box-sizing: border-box;
92
+ padding: 0 var(--col-pad);
93
+
94
+ min-width: 0;
95
+ min-height: 0;
96
+
11
97
  display: flex;
12
98
  flex-direction: column;
13
99
  gap: var(--spacing-md);
14
- transform: translateX(100%);
15
- transition: transform 0.2s ease-in-out;
16
- box-shadow: var(--shadow-lg);
17
- z-index: var(--z-drawer);
18
- background-color: var(--color-bg-surface);
19
- max-height: 100vh;
100
+ }
101
+
102
+ .header {
103
+ padding: var(--spacing-md) 0;
20
104
  }
21
105
 
22
106
  .content {
23
107
  flex: 1;
108
+ min-height: 0;
109
+ overflow: auto;
24
110
  font-size: var(--font-size-sm);
25
111
  }
26
112
 
27
- .header {
28
- position: sticky;
29
- top: 0;
30
- padding: var(--spacing-md) 0;
31
- background: inherit;
32
- }
33
-
34
113
  .actions {
35
- position: sticky;
36
114
  padding: var(--spacing-md) 0;
37
- background: inherit;
38
- bottom: 0;
39
115
  display: flex;
40
116
  justify-content: flex-end;
41
117
  gap: var(--spacing-sm);
118
+ min-width: 0;
42
119
  }
43
120
 
44
- .sidePanel.open {
45
- transform: translateX(0);
121
+ /* details styling */
122
+ .detailsCol {
123
+ border: 1px solid var(--color-border-subtle);
124
+ border-radius: var(--radius-md);
125
+ overflow: hidden;
126
+ background: var(--color-bg-surface);
127
+ }
128
+
129
+ .detailsHeader {
130
+ padding: var(--spacing-sm) 0;
131
+ border-bottom: 1px solid var(--color-border-subtle);
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: space-between;
135
+ gap: var(--spacing-sm);
136
+ }
137
+
138
+ .detailsTitle {
139
+ font-weight: 600;
46
140
  }
47
141
 
142
+ .detailsHeaderActions {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: var(--spacing-sm);
146
+ }
147
+
148
+ .detailsContent {
149
+ padding: var(--spacing-md) 0;
150
+ overflow: auto;
151
+ min-height: 0;
152
+ flex: 1 1 auto;
153
+ }
154
+
155
+ /* Backdrop with a nice fade */
48
156
  .backdrop {
49
157
  position: fixed;
50
- top: 0;
51
- left: 0;
52
- width: 100%;
53
- height: 100%;
54
- background: var(--overlay-scrim);
158
+ inset: 0;
159
+ background-color: rgba(0, 0, 0, 0.45);
55
160
  z-index: var(--z-backdrop);
161
+
162
+ opacity: 0;
163
+ transition: opacity var(--panel-dur) var(--panel-ease);
164
+ will-change: opacity;
165
+ }
166
+
167
+ .backdropOpen {
168
+ opacity: 1;
169
+ }
170
+
171
+ @media (prefers-reduced-motion: reduce) {
172
+ .sidePanel,
173
+ .sidePanel::before,
174
+ .backdrop {
175
+ transition: none !important;
176
+ }
56
177
  }
@@ -1,4 +1,4 @@
1
- export declare function useSidePanel(): {
1
+ export declare function useSidePanel(initialOpen?: boolean): {
2
2
  isOpen: boolean;
3
3
  openSidePanel: () => void;
4
4
  closeSidePanel: () => void;
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react';
2
- export function useSidePanel() {
3
- const [isOpen, setIsOpen] = useState(false);
2
+ export function useSidePanel(initialOpen = false) {
3
+ const [isOpen, setIsOpen] = useState(initialOpen);
4
4
  const openSidePanel = () => setIsOpen(true);
5
5
  const closeSidePanel = () => setIsOpen(false);
6
6
  return {
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useContext, useEffect, useId, useRef, useState } from 'react';
2
+ import { MOTION_MS } from '../../../styles/animation';
2
3
  import { TooltipContext } from './TooltipProvider';
3
4
  export function useTooltipTrigger(options) {
4
5
  const ctx = useContext(TooltipContext);
@@ -45,7 +46,6 @@ export function useTooltipTrigger(options) {
45
46
  }
46
47
  show();
47
48
  }, [isOpen, show, hide]);
48
- // ✅ Only call update if THIS tooltip is the active one AND something changed
49
49
  useEffect(() => {
50
50
  var _a;
51
51
  if (!isOpen)
@@ -84,7 +84,9 @@ export function useTooltipTrigger(options) {
84
84
  const onFocus = () => {
85
85
  clearTimers();
86
86
  if (!isControlled)
87
- setOpen(true);
87
+ setTimeout(() => {
88
+ setOpen(true);
89
+ }, MOTION_MS.tooltipOpen);
88
90
  };
89
91
  const onBlur = () => {
90
92
  clearTimers();
@@ -2,7 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { PageLayoutHero } from './components/page-layout-hero/PageLayoutHero';
4
4
  import styles from './PageLayout.module.css';
5
- /** Base component */
6
5
  const PageLayoutBase = ({ children, sidebar, header, containScrolling = false, orientation = 'vertical', }) => {
7
6
  if (orientation === 'vertical') {
8
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: {
@@ -17,7 +16,6 @@ const PageLayoutBase = ({ children, sidebar, header, containScrolling = false, o
17
16
  display: 'flex',
18
17
  flexDirection: 'column',
19
18
  padding: 'var(--spacing-md)',
20
- gap: 'var(--spacing-md)',
21
19
  backgroundColor: 'var(--color-bg-surface)',
22
20
  overflow: 'auto',
23
21
  }, children: children })] })] }) }));
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { ChevronDown, ChevronUp } from 'lucide-react';
4
- import { createPortal } from 'react-dom';
5
4
  import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
5
+ import { createPortal } from 'react-dom';
6
6
  import styles from './Popover.module.css';
7
7
  export const Popover = forwardRef(function Popover({ trigger: Trigger, children, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, }, ref) {
8
8
  const [pos, setPos] = useState({ top: 0, left: 0, width: 0, visible: false });
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { NavBarItem } from '../../../../components/nav-bar/NavBar';
3
- interface ExpandableSidebarItemProps {
2
+ import type { NavBarItem } from '../../../../components/nav-bar/NavBar';
3
+ type ExpandableSidebarItemProps = {
4
4
  items: NavBarItem[];
5
5
  label: string;
6
- component?: React.ElementType;
6
+ component: React.ElementType;
7
7
  icon: React.ReactNode;
8
- href?: string;
9
- }
8
+ href: string;
9
+ };
10
10
  export declare function ExpandableSidebarItem({ items, label, icon, component: Component, href, }: ExpandableSidebarItemProps): React.ReactNode;
11
11
  export {};