@dbcdk/react-components 0.0.8 → 0.0.10

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 (88) hide show
  1. package/README.md +167 -0
  2. package/dist/components/__stories__/_data/tabs.d.ts +9 -0
  3. package/dist/components/__stories__/_data/tabs.js +31 -0
  4. package/dist/components/accordion/Accordion.d.ts +27 -0
  5. package/dist/components/accordion/Accordion.js +66 -0
  6. package/dist/components/accordion/Accordion.module.css +87 -0
  7. package/dist/components/button/Button.module.css +1 -0
  8. package/dist/components/circle/Circle.d.ts +4 -1
  9. package/dist/components/circle/Circle.js +2 -2
  10. package/dist/components/circle/Circle.module.css +54 -2
  11. package/dist/components/code-block/CodeBlock.module.css +1 -1
  12. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -7
  13. package/dist/components/datetime-picker/DateTimePicker.js +117 -64
  14. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -2
  15. package/dist/components/datetime-picker/dateTimeHelpers.js +32 -17
  16. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -8
  17. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  18. package/dist/components/forms/input/Input.d.ts +1 -0
  19. package/dist/components/forms/input/Input.js +2 -4
  20. package/dist/components/forms/input/Input.module.css +9 -11
  21. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  22. package/dist/components/forms/input-container/InputContainer.js +3 -3
  23. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  24. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  25. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  26. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  27. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  28. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  29. package/dist/components/forms/select/Select.d.ts +1 -1
  30. package/dist/components/forms/select/Select.js +3 -3
  31. package/dist/components/forms/text-area/Textarea.js +3 -3
  32. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  33. package/dist/components/headline/Headline.d.ts +2 -7
  34. package/dist/components/headline/Headline.js +5 -2
  35. package/dist/components/headline/Headline.module.css +61 -2
  36. package/dist/components/hyperlink/Hyperlink.d.ts +1 -0
  37. package/dist/components/hyperlink/Hyperlink.js +5 -1
  38. package/dist/components/icon/Icon.module.css +1 -0
  39. package/dist/components/interval-select/IntervalSelect.js +1 -1
  40. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  41. package/dist/components/overlay/modal/provider/ModalProvider.d.ts +2 -2
  42. package/dist/components/overlay/modal/provider/ModalProvider.js +24 -25
  43. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  44. package/dist/components/overlay/side-panel/SidePanel.js +60 -4
  45. package/dist/components/overlay/side-panel/SidePanel.module.css +151 -28
  46. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  47. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  48. package/dist/components/page-layout/PageLayout.js +0 -2
  49. package/dist/components/popover/Popover.js +33 -14
  50. package/dist/components/popover/Popover.module.css +0 -4
  51. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  52. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +16 -8
  53. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  54. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  55. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  56. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  57. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  58. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  59. package/dist/components/sidebar/providers/SidebarProvider.js +25 -46
  60. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  61. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  62. package/dist/components/state-page/StatePage.d.ts +9 -0
  63. package/dist/components/state-page/StatePage.js +20 -0
  64. package/dist/components/state-page/StatePage.module.css +9 -0
  65. package/dist/components/state-page/empty.d.ts +2 -0
  66. package/dist/components/state-page/empty.js +2 -0
  67. package/dist/components/state-page/error.d.ts +2 -0
  68. package/dist/components/state-page/error.js +2 -0
  69. package/dist/components/state-page/notFound.d.ts +2 -0
  70. package/dist/components/state-page/notFound.js +2 -0
  71. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  72. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  73. package/dist/components/table/Table.js +4 -4
  74. package/dist/components/table/Table.module.css +168 -60
  75. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  76. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  77. package/dist/components/toast/Toast.js +5 -1
  78. package/dist/components/toast/Toast.module.css +40 -15
  79. package/dist/components/toast/provider/ToastProvider.js +1 -0
  80. package/dist/hooks/useTimeDuration.js +9 -3
  81. package/dist/hooks/useViewportFill.js +1 -0
  82. package/dist/index.d.ts +6 -0
  83. package/dist/index.js +6 -1
  84. package/dist/src/styles/styles.css +22 -3
  85. package/dist/styles/styles.css +22 -3
  86. package/dist/styles/themes/dbc/dark.css +1 -1
  87. package/dist/styles/themes/dbc/light.css +2 -1
  88. package/package.json +1 -1
@@ -1,10 +1,66 @@
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';
5
7
  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 })] })] }));
8
+ export function SidePanel({ isOpen, onClose, children, header, headerAddition, actions, showBackdrop = true, severity, showHeaderMarker = true, width = '400px', details, detailsHeader = 'Output', detailsWidth = '420px', onCloseDetails, detailsHeaderAddition, ...props }) {
9
+ const [mounted, setMounted] = useState(false);
10
+ const [shouldRender, setShouldRender] = useState(isOpen);
11
+ const [isActive, setIsActive] = useState(false);
12
+ const panelRef = useRef(null);
13
+ useEffect(() => setMounted(true), []);
14
+ // OPEN: ensure rendered so animation can play
15
+ useEffect(() => {
16
+ if (isOpen)
17
+ setShouldRender(true);
18
+ }, [isOpen]);
19
+ // Two-phase OPEN/CLOSE class toggle (lets CSS transitions kick in reliably)
20
+ useEffect(() => {
21
+ if (!shouldRender)
22
+ return;
23
+ if (!isOpen) {
24
+ setIsActive(false);
25
+ return;
26
+ }
27
+ const raf = requestAnimationFrame(() => setIsActive(true));
28
+ return () => cancelAnimationFrame(raf);
29
+ }, [isOpen, shouldRender]);
30
+ // When closing: wait for transform transition end to unmount.
31
+ useEffect(() => {
32
+ if (!shouldRender)
33
+ return;
34
+ const el = panelRef.current;
35
+ if (!el)
36
+ return;
37
+ const onTransitionEnd = (e) => {
38
+ if (e.target !== el)
39
+ return;
40
+ if (e.propertyName !== 'transform')
41
+ return;
42
+ if (!isOpen)
43
+ setShouldRender(false);
44
+ };
45
+ el.addEventListener('transitionend', onTransitionEnd);
46
+ return () => el.removeEventListener('transitionend', onTransitionEnd);
47
+ }, [isOpen, shouldRender]);
48
+ if (!mounted)
49
+ return null;
50
+ if (!shouldRender)
51
+ return null;
52
+ const hasDetails = Boolean(details);
53
+ return createPortal(_jsxs(_Fragment, { children: [showBackdrop && (_jsx("div", { className: `${styles.backdrop} ${isActive ? styles.backdropOpen : ''}`, onClick: e => {
54
+ e.stopPropagation();
55
+ onClose(e);
56
+ } })), _jsxs("div", { ref: panelRef, ...props, className: `${styles.sidePanel} ${isActive ? styles.open : ''} ${hasDetails ? styles.withDetails : styles.noDetails}`, style: {
57
+ '--side-panel-width': width,
58
+ '--details-width': detailsWidth,
59
+ }, "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 => {
60
+ e.stopPropagation();
61
+ onCloseDetails();
62
+ }, 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 => {
63
+ e.stopPropagation();
64
+ onClose(e);
65
+ }, "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
66
  }
@@ -1,56 +1,179 @@
1
1
  .sidePanel {
2
- padding: 0 var(--spacing-md);
2
+ --col-pad: var(--spacing-md);
3
+
4
+ /* Dial these for “feel” */
5
+ --panel-dur: 220ms;
6
+ --panel-ease: cubic-bezier(0.22, 1, 0.36, 1); /* smooth spring-ish without overshoot */
7
+
8
+ /* Shadow + edge */
9
+ --shadow-opacity: 0.22;
10
+ --edge-opacity: 0.12;
11
+
12
+ box-sizing: border-box;
13
+ padding: 0;
3
14
  height: 100vh;
4
- width: var(--side-panel-width, 400px);
15
+
5
16
  position: fixed;
6
17
  right: 0;
7
18
  top: 0;
8
- display: flex;
9
- flex-direction: column;
10
- overflow: auto;
19
+
20
+ background-color: var(--color-bg-surface);
21
+ z-index: var(--z-drawer);
22
+
23
+ transform: translate3d(100%, 0, 0);
24
+ transition: transform var(--panel-dur) var(--panel-ease);
25
+ will-change: transform;
26
+
27
+ display: grid;
28
+ gap: var(--spacing-md);
29
+ align-items: stretch;
30
+
31
+ overflow: hidden;
32
+ pointer-events: auto;
33
+
34
+ /* Make pseudo-elements layer properly */
35
+ isolation: isolate;
36
+ }
37
+
38
+ /* “Lift” layer: drop-shadow + edge highlight (both fade in) */
39
+ .sidePanel::before {
40
+ content: '';
41
+ position: absolute;
42
+ inset: 0;
43
+ pointer-events: none;
44
+ z-index: -1; /* behind content but within isolated stacking context */
45
+
46
+ /* 1) drop-shadow = smoother than animating box-shadow
47
+ 2) edge gradient = premium depth cue */
48
+ filter: drop-shadow(0 16px 32px rgba(0, 0, 0, var(--shadow-opacity)));
49
+ opacity: 0;
50
+
51
+ /* Edge highlight from the left edge (panel’s leading edge) */
52
+ background: linear-gradient(
53
+ 90deg,
54
+ rgba(255, 255, 255, var(--edge-opacity)),
55
+ rgba(255, 255, 255, 0) 36%
56
+ );
57
+
58
+ transition: opacity var(--panel-dur) var(--panel-ease);
59
+
60
+ will-change: opacity;
61
+ }
62
+
63
+ /* Optional: super subtle “sheen” right as it settles (feels snappy) */
64
+ .sidePanel.open::before {
65
+ opacity: 1;
66
+ }
67
+
68
+ .sidePanel.open {
69
+ transform: translate3d(0, 0, 0);
70
+ }
71
+
72
+ /* MAIN TRACK WIDTH = content + padding*2 */
73
+ .sidePanel.noDetails {
74
+ grid-template-columns: calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2));
75
+ width: calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2));
76
+ }
77
+
78
+ /* when details show, main track stays the same; panel grows */
79
+ .sidePanel.withDetails {
80
+ grid-template-columns:
81
+ calc(var(--side-panel-width, 400px) + (var(--col-pad) * 2))
82
+ calc(var(--details-width, 420px) + (var(--col-pad) * 2));
83
+
84
+ width: calc(
85
+ (var(--side-panel-width, 400px) + (var(--col-pad) * 2)) +
86
+ (var(--details-width, 420px) + (var(--col-pad) * 2)) + var(--spacing-md)
87
+ );
88
+ }
89
+
90
+ /* columns get the padding */
91
+ .mainCol,
92
+ .detailsCol {
93
+ box-sizing: border-box;
94
+ padding: 0 var(--col-pad);
95
+
96
+ min-width: 0;
97
+ min-height: 0;
98
+
11
99
  display: flex;
12
100
  flex-direction: column;
13
101
  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;
102
+ }
103
+
104
+ .header {
105
+ padding: var(--spacing-md) 0;
20
106
  }
21
107
 
22
108
  .content {
23
109
  flex: 1;
110
+ min-height: 0;
111
+ overflow: auto;
24
112
  font-size: var(--font-size-sm);
25
113
  }
26
114
 
27
- .header {
28
- position: sticky;
29
- top: 0;
30
- padding: var(--spacing-md) 0;
31
- background: inherit;
32
- }
33
-
34
115
  .actions {
35
- position: sticky;
36
116
  padding: var(--spacing-md) 0;
37
- background: inherit;
38
- bottom: 0;
39
117
  display: flex;
40
118
  justify-content: flex-end;
41
119
  gap: var(--spacing-sm);
120
+ min-width: 0;
42
121
  }
43
122
 
44
- .sidePanel.open {
45
- transform: translateX(0);
123
+ /* details styling */
124
+ .detailsCol {
125
+ border: 1px solid var(--color-border-subtle);
126
+ border-radius: var(--radius-md);
127
+ overflow: hidden;
128
+ background: var(--color-bg-surface);
129
+ }
130
+
131
+ .detailsHeader {
132
+ padding: var(--spacing-sm) 0;
133
+ border-bottom: 1px solid var(--color-border-subtle);
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: space-between;
137
+ gap: var(--spacing-sm);
138
+ }
139
+
140
+ .detailsTitle {
141
+ font-weight: 600;
46
142
  }
47
143
 
144
+ .detailsHeaderActions {
145
+ display: flex;
146
+ align-items: center;
147
+ gap: var(--spacing-sm);
148
+ }
149
+
150
+ .detailsContent {
151
+ padding: var(--spacing-md) 0;
152
+ overflow: auto;
153
+ min-height: 0;
154
+ flex: 1 1 auto;
155
+ }
156
+
157
+ /* Backdrop with a nice fade */
48
158
  .backdrop {
49
159
  position: fixed;
50
- top: 0;
51
- left: 0;
52
- width: 100%;
53
- height: 100%;
54
- background: var(--overlay-scrim);
160
+ inset: 0;
161
+ background-color: rgba(0, 0, 0, 0.45);
55
162
  z-index: var(--z-backdrop);
163
+
164
+ opacity: 0;
165
+ transition: opacity var(--panel-dur) var(--panel-ease);
166
+ will-change: opacity;
167
+ }
168
+
169
+ .backdropOpen {
170
+ opacity: 1;
171
+ }
172
+
173
+ @media (prefers-reduced-motion: reduce) {
174
+ .sidePanel,
175
+ .sidePanel::before,
176
+ .backdrop {
177
+ transition: none !important;
178
+ }
56
179
  }
@@ -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 {
@@ -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,37 +1,49 @@
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 { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react';
5
6
  import styles from './Popover.module.css';
6
7
  export const Popover = forwardRef(function Popover({ trigger: Trigger, children, minWidth = '200px', matchTriggerWidth = true, viewportPadding = 8, edgeBuffer = 100, dataCy, }, ref) {
7
8
  const [pos, setPos] = useState({ top: 0, left: 0, width: 0, visible: false });
8
9
  const containerRef = useRef(null);
9
10
  const contentRef = useRef(null);
11
+ // avoid SSR/hydration mismatch
12
+ const [mounted, setMounted] = useState(false);
13
+ useEffect(() => setMounted(true), []);
10
14
  const computeAndSetPosition = useCallback((show) => {
11
15
  const container = containerRef.current;
12
16
  const content = contentRef.current;
13
17
  if (!container || !content)
14
18
  return;
15
19
  const triggerRect = container.getBoundingClientRect();
20
+ // Temporarily measure content size by forcing it into the layout.
16
21
  const prevVis = content.style.visibility;
17
22
  const prevDisp = content.style.display;
18
23
  const prevMinWidth = content.style.minWidth;
19
24
  const prevWidth = content.style.width;
25
+ const prevTop = content.style.top;
26
+ const prevLeft = content.style.left;
20
27
  content.style.visibility = 'hidden';
21
28
  content.style.display = 'block';
29
+ content.style.top = '0px';
30
+ content.style.left = '0px';
22
31
  content.style.minWidth = minWidth;
23
32
  content.style.width = 'auto';
24
33
  const minWidthPx = content.offsetWidth;
25
34
  const desiredWidthPx = Math.max(matchTriggerWidth ? triggerRect.width : 0, minWidthPx);
26
- // Now apply desired width and re-measure final size (height may depend on width).
35
+ // Apply desired width and re-measure final size (height may depend on width).
27
36
  content.style.minWidth = `${desiredWidthPx}px`;
28
37
  content.style.width = `${desiredWidthPx}px`;
29
38
  const contentWidth = content.offsetWidth;
30
39
  const contentHeight = content.offsetHeight;
40
+ // Restore previous inline styles
31
41
  content.style.visibility = prevVis;
32
42
  content.style.display = prevDisp;
33
43
  content.style.minWidth = prevMinWidth;
34
44
  content.style.width = prevWidth;
45
+ content.style.top = prevTop;
46
+ content.style.left = prevLeft;
35
47
  const vw = window.innerWidth;
36
48
  const vh = window.innerHeight;
37
49
  const spaceAbove = Math.max(0, triggerRect.top);
@@ -75,6 +87,12 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
75
87
  open: () => computeAndSetPosition(true),
76
88
  isOpen: () => !!pos.visible,
77
89
  }), [closePopover, computeAndSetPosition, pos.visible]);
90
+ // Recompute position after open to account for content becoming visible / measured.
91
+ useLayoutEffect(() => {
92
+ if (pos.visible)
93
+ computeAndSetPosition(true);
94
+ // eslint-disable-next-line react-hooks/exhaustive-deps
95
+ }, [pos.visible]);
78
96
  useEffect(() => {
79
97
  if (!pos.visible)
80
98
  return;
@@ -102,17 +120,18 @@ export const Popover = forwardRef(function Popover({ trigger: Trigger, children,
102
120
  window.removeEventListener('scroll', handleReposition, true);
103
121
  };
104
122
  }, [pos.visible, closePopover, computeAndSetPosition]);
105
- return (_jsxs("div", { className: styles.container, ref: containerRef, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'popover-content', children: [Trigger(openPopover, pos.visible ? _jsx(ChevronUp, { size: 20 }) : _jsx(ChevronDown, { size: 20 })), _jsx("div", { ref: contentRef, className: styles.content, style: {
106
- top: pos.top,
107
- left: pos.left,
108
- visibility: pos.visible ? 'visible' : 'hidden',
109
- minWidth: pos.width ? `${pos.width}px` : minWidth,
110
- width: pos.width ? `${pos.width}px` : undefined,
111
- maxWidth: `calc(100vw - ${viewportPadding * 2}px)`,
112
- maxHeight: `clamp(100px, calc(100vh - ${viewportPadding * 2}px), 400px)`,
113
- overflow: 'auto',
114
- }, role: "dialog", "aria-hidden": !pos.visible, children: typeof children === 'function'
115
- ? children(closePopover)
116
- : children })] }));
123
+ return (_jsxs("div", { className: styles.container, ref: containerRef, children: [Trigger(openPopover, pos.visible ? _jsx(ChevronUp, { size: 20 }) : _jsx(ChevronDown, { size: 20 })), mounted &&
124
+ createPortal(_jsx("div", { ref: contentRef, className: styles.content, style: {
125
+ top: pos.top,
126
+ left: pos.left,
127
+ visibility: pos.visible ? 'visible' : 'hidden',
128
+ minWidth: pos.width ? `${pos.width}px` : minWidth,
129
+ width: pos.width ? `${pos.width}px` : undefined,
130
+ maxWidth: `calc(100vw - ${viewportPadding * 2}px)`,
131
+ maxHeight: `clamp(100px, calc(100vh - ${viewportPadding * 2}px), 400px)`,
132
+ overflow: 'auto',
133
+ }, role: "dialog", "aria-hidden": !pos.visible, "data-cy": dataCy !== null && dataCy !== void 0 ? dataCy : 'popover-content', children: typeof children === 'function'
134
+ ? children(closePopover)
135
+ : children }), document.body)] }));
117
136
  });
118
137
  Popover.displayName = 'Popover';
@@ -4,15 +4,11 @@
4
4
 
5
5
  .content {
6
6
  position: fixed;
7
- top: 100%;
8
- left: 0;
9
7
  border: 1px solid var(--color-border-default);
10
8
  background-color: var(--color-bg-surface);
11
9
  border-radius: var(--border-radius-default);
12
10
  padding: var(--spacing-sm) 0;
13
11
  z-index: var(--z-popover);
14
- max-width: 80vw;
15
- max-height: 80vh;
16
12
  overflow: auto;
17
13
  box-shadow: var(--shadow-md);
18
14
  }
@@ -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 {};
@@ -7,6 +7,9 @@ import { Button } from '../../../button/Button';
7
7
  import { useSidebar } from '../../providers/SidebarProvider';
8
8
  import { SidebarItemContent } from '../sidebar-item-content/SidebarItemContent';
9
9
  import { SidebarItem } from '../SidebarItem';
10
+ import { ExpandableSidebarItem as ExpandableChild } from '../expandable-sidebar-item/ExpandableSidebarItem';
11
+ const isGroup = (item) => item.type === 'group';
12
+ const isExpandable = (item) => item.type === 'expandable';
10
13
  export function ExpandableSidebarItem({ items, label, icon, component: Component, href, }) {
11
14
  const { defaultExpanded, resetExpandAll, isSidebarCollapsed, handleSidebarCollapseChange, expandedItems, } = useSidebar();
12
15
  const [expanded, setExpanded] = useState(false);
@@ -21,9 +24,8 @@ export function ExpandableSidebarItem({ items, label, icon, component: Component
21
24
  }
22
25
  }, [expandedItems, href]);
23
26
  useEffect(() => {
24
- if (defaultExpanded === null) {
27
+ if (defaultExpanded === null)
25
28
  return;
26
- }
27
29
  setExpanded(defaultExpanded);
28
30
  }, [defaultExpanded]);
29
31
  const handleAnimationEnd = useCallback(() => {
@@ -45,10 +47,16 @@ export function ExpandableSidebarItem({ items, label, icon, component: Component
45
47
  setClosing(true);
46
48
  }
47
49
  }, [expanded, handleSidebarCollapseChange, isSidebarCollapsed, resetExpandAll]);
48
- if (!items)
49
- return null;
50
- if (!Component) {
51
- return null;
52
- }
53
- 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(({ component: Component, label, icon, href }, id) => (_jsx(SidebarItem, { component: Component, label: label, icon: icon, href: href }, id))) }))] }));
50
+ const renderNavItem = (item, key) => {
51
+ var _a, _b;
52
+ if (isGroup(item)) {
53
+ return (_jsxs("div", { className: styles.group, children: [_jsx("div", { className: styles.groupLabel, children: item.label }), (_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child, idx) => renderNavItem(child, `${key}-${idx}`))] }, key));
54
+ }
55
+ if (isExpandable(item)) {
56
+ return (_jsx(ExpandableChild, { items: (_b = item.children) !== null && _b !== void 0 ? _b : [], label: item.label, icon: item.icon, href: item.href, component: item.component }, key));
57
+ }
58
+ // Default item (type 'item' or undefined)
59
+ return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href }, key));
60
+ };
61
+ 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}`)) }))] }));
54
62
  }
@@ -1,6 +1,3 @@
1
- .button {
2
- }
3
-
4
1
  .container {
5
2
  position: relative;
6
3
  display: flex;
@@ -5,5 +5,7 @@ interface SidebarContainerProps {
5
5
  productLogo?: ReactNode;
6
6
  activeLink?: string;
7
7
  }
8
- export declare function SidebarContainer({ logo, productName, productLogo, activeLink, }: SidebarContainerProps): JSX.Element;
8
+ export declare function SidebarContainer({ logo, // DBC Digital (company)
9
+ productLogo, // DataIO (product)
10
+ activeLink, }: SidebarContainerProps): JSX.Element;
9
11
  export {};
@@ -2,12 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronLeft } from 'lucide-react';
3
3
  import { Logo } from '../../../../assets/logo';
4
4
  import { Button } from '../../../../components/button/Button';
5
- import { Headline } from '../../../../components/headline/Headline';
6
5
  import { SidebarItems } from '../../../../components/sidebar/components/sidebar-items/SidebarItems';
7
6
  import SidenavFiltering from '../../../../components/sidebar/components/sidenav-filteirng/SidenavFiltering';
8
7
  import { useSidebar } from '../../../../components/sidebar/providers/SidebarProvider';
9
8
  import styles from './SidebarContainer.module.css';
10
- export function SidebarContainer({ logo, productName, productLogo, activeLink, }) {
9
+ export function SidebarContainer({ logo, // DBC Digital (company)
10
+ productLogo, // DataIO (product)
11
+ activeLink, }) {
11
12
  const { isSidebarCollapsed, handleSidebarCollapseChange } = useSidebar();
12
- return (_jsxs("div", { className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''}`, children: [_jsxs("div", { className: styles.header, children: [_jsxs("span", { className: styles.logoContainer, children: [_jsx("div", { style: { textAlign: isSidebarCollapsed ? 'center' : 'right' }, children: _jsx(Button, { size: "sm", variant: "inline", icon: _jsx(ChevronLeft, { className: isSidebarCollapsed ? styles.collapsedIcon : '' }), onClick: () => handleSidebarCollapseChange(!isSidebarCollapsed) }) }), _jsx("div", { className: styles.logo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) })] }), productName && _jsx(Headline, { disableMargin: true, children: productName }), productLogo && _jsx("div", { className: styles.productLogo, children: productLogo })] }), _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 })] }));
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
14
  }