@dbcdk/react-components 0.0.9 → 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 (80) 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/circle/Circle.d.ts +4 -1
  6. package/dist/components/circle/Circle.js +2 -2
  7. package/dist/components/circle/Circle.module.css +54 -2
  8. package/dist/components/datetime-picker/DateTimePicker.d.ts +4 -8
  9. package/dist/components/datetime-picker/DateTimePicker.js +72 -92
  10. package/dist/components/datetime-picker/dateTimeHelpers.d.ts +14 -12
  11. package/dist/components/datetime-picker/dateTimeHelpers.js +25 -45
  12. package/dist/components/forms/checkbox/Checkbox.d.ts +2 -8
  13. package/dist/components/forms/checkbox/Checkbox.js +3 -5
  14. package/dist/components/forms/input/Input.d.ts +1 -0
  15. package/dist/components/forms/input/Input.js +2 -4
  16. package/dist/components/forms/input/Input.module.css +9 -11
  17. package/dist/components/forms/input-container/InputContainer.d.ts +2 -1
  18. package/dist/components/forms/input-container/InputContainer.js +3 -3
  19. package/dist/components/forms/input-container/InputContainer.module.css +65 -0
  20. package/dist/components/forms/radio-buttons/RadioButton.d.ts +36 -0
  21. package/dist/components/forms/radio-buttons/RadioButton.js +26 -0
  22. package/dist/components/forms/radio-buttons/RadioButtonGroup.d.ts +25 -0
  23. package/dist/components/forms/radio-buttons/RadioButtonGroup.js +19 -0
  24. package/dist/components/forms/radio-buttons/RadioButtons.module.css +117 -0
  25. package/dist/components/forms/select/Select.d.ts +1 -1
  26. package/dist/components/forms/select/Select.js +3 -3
  27. package/dist/components/forms/text-area/Textarea.js +3 -3
  28. package/dist/components/forms/text-area/Textarea.module.css +8 -1
  29. package/dist/components/headline/Headline.d.ts +2 -7
  30. package/dist/components/headline/Headline.js +5 -2
  31. package/dist/components/headline/Headline.module.css +61 -2
  32. package/dist/components/hyperlink/Hyperlink.d.ts +1 -0
  33. package/dist/components/hyperlink/Hyperlink.js +5 -1
  34. package/dist/components/icon/Icon.module.css +1 -0
  35. package/dist/components/interval-select/IntervalSelect.js +1 -1
  36. package/dist/components/nav-bar/NavBar.d.ts +24 -6
  37. package/dist/components/overlay/side-panel/SidePanel.d.ts +12 -4
  38. package/dist/components/overlay/side-panel/SidePanel.js +60 -4
  39. package/dist/components/overlay/side-panel/SidePanel.module.css +151 -28
  40. package/dist/components/overlay/side-panel/useSidePanel.d.ts +1 -1
  41. package/dist/components/overlay/side-panel/useSidePanel.js +2 -2
  42. package/dist/components/page-layout/PageLayout.js +0 -2
  43. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +5 -5
  44. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +16 -8
  45. package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.module.css +0 -3
  46. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +3 -1
  47. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +4 -3
  48. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +109 -79
  49. package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +16 -3
  50. package/dist/components/sidebar/components/sidebar-items/SidebarItems.module.css +20 -0
  51. package/dist/components/sidebar/providers/SidebarProvider.js +25 -46
  52. package/dist/components/skeleton-loader/SkeletonLoader.d.ts +1 -1
  53. package/dist/components/skeleton-loader/SkeletonLoader.js +15 -12
  54. package/dist/components/state-page/StatePage.d.ts +9 -0
  55. package/dist/components/state-page/StatePage.js +20 -0
  56. package/dist/components/state-page/StatePage.module.css +9 -0
  57. package/dist/components/state-page/empty.d.ts +2 -0
  58. package/dist/components/state-page/empty.js +2 -0
  59. package/dist/components/state-page/error.d.ts +2 -0
  60. package/dist/components/state-page/error.js +2 -0
  61. package/dist/components/state-page/notFound.d.ts +2 -0
  62. package/dist/components/state-page/notFound.js +2 -0
  63. package/dist/components/sticky-footer-layout/StickyFooterLayout.d.ts +19 -0
  64. package/dist/components/sticky-footer-layout/StickyFooterLayout.js +27 -0
  65. package/dist/components/table/Table.js +4 -4
  66. package/dist/components/table/Table.module.css +168 -60
  67. package/dist/components/table/components/empty-state/EmptyState.d.ts +1 -1
  68. package/dist/components/table/components/empty-state/EmptyState.js +6 -7
  69. package/dist/components/toast/Toast.js +5 -1
  70. package/dist/components/toast/Toast.module.css +40 -15
  71. package/dist/components/toast/provider/ToastProvider.js +1 -0
  72. package/dist/hooks/useTimeDuration.js +9 -3
  73. package/dist/hooks/useViewportFill.js +1 -0
  74. package/dist/index.d.ts +6 -0
  75. package/dist/index.js +6 -1
  76. package/dist/src/styles/styles.css +22 -3
  77. package/dist/styles/styles.css +22 -3
  78. package/dist/styles/themes/dbc/dark.css +1 -1
  79. package/dist/styles/themes/dbc/light.css +2 -1
  80. package/package.json +1 -1
@@ -10,13 +10,8 @@ interface HeadlineProps extends React.AriaAttributes {
10
10
  subHeadline?: string | JSX.Element;
11
11
  addition?: React.ReactNode;
12
12
  icon?: React.ReactNode;
13
- /**
14
- * Optional visual tone override:
15
- * - dark: force normal foreground colours
16
- * - light: force on-strong/light text
17
- * If omitted, the headline simply inherits its colour from its parent.
18
- */
13
+ allowWrap?: boolean;
19
14
  tone?: HeadlineTone;
20
15
  }
21
- export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
16
+ export declare function Headline({ size, marker, disableMargin, children, severity, weight, subHeadline, addition, icon, tone, allowWrap, }: PropsWithChildren<HeadlineProps>): React.ReactNode;
22
17
  export {};
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import styles from './Headline.module.css';
4
4
  import { SeverityBgColor } from '../../constants/severity';
5
5
  import { Icon } from '../icon/Icon';
6
- export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, }) {
6
+ export function Headline({ size = 2, marker, disableMargin, children, severity, weight = 600, subHeadline, addition, icon, tone, allowWrap = true, }) {
7
7
  const Tag = `h${size}`;
8
8
  const containerClassName = [styles.headlineContainer, tone ? styles[`tone-${tone}`] : '']
9
9
  .filter(Boolean)
@@ -15,8 +15,11 @@ export function Headline({ size = 2, marker, disableMargin, children, severity,
15
15
  ]
16
16
  .filter(Boolean)
17
17
  .join(' ');
18
+ const textClassName = [styles.text, allowWrap ? styles.wrap : styles.truncate]
19
+ .filter(Boolean)
20
+ .join(' ');
18
21
  return (_jsxs(_Fragment, { children: [_jsxs("div", { className: containerClassName, children: [_jsxs(Tag, { style: {
19
22
  '--font-weight': weight,
20
23
  '--marker-color': severity ? SeverityBgColor[severity] : undefined,
21
- }, className: headlineClassName, children: [icon || (severity && !marker) ? _jsx(Icon, { customIcon: icon, severity: severity }) : null, children] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
24
+ }, className: headlineClassName, children: [icon || (severity && !marker) ? (_jsx("span", { className: styles.icon, children: _jsx(Icon, { customIcon: icon, severity: severity }) })) : null, _jsx("span", { className: textClassName, children: children })] }), addition] }), subHeadline && _jsx("div", { className: styles.subHeadline, children: subHeadline })] }));
22
25
  }
@@ -3,6 +3,7 @@
3
3
  align-items: center;
4
4
  gap: var(--spacing-lg);
5
5
  flex-wrap: wrap;
6
+ max-width: 100%;
6
7
  }
7
8
 
8
9
  /* Base headline: inherit colour from parent surface */
@@ -18,8 +19,6 @@
18
19
  transition: color var(--transition-fast) var(--ease-standard);
19
20
  }
20
21
 
21
- /* Optional tone overrides for special cases (hero sections, etc.) */
22
-
23
22
  .tone-dark .headline {
24
23
  color: var(--color-fg-default);
25
24
  }
@@ -58,3 +57,63 @@
58
57
  margin-block-start: calc(var(--spacing-2xs) * -1);
59
58
  line-height: var(--line-height-normal);
60
59
  }
60
+
61
+ .headline {
62
+ position: relative;
63
+ display: inline-flex;
64
+ align-items: center;
65
+ gap: var(--spacing-xs);
66
+ font-weight: var(--font-weight, var(--font-weight-bold));
67
+ letter-spacing: var(--letter-spacing-tight);
68
+ color: inherit;
69
+ line-height: var(--line-height-tight);
70
+ transition: color var(--transition-fast) var(--ease-standard);
71
+
72
+ /* Needed so truncation works inside flex parents */
73
+ min-width: 0;
74
+ }
75
+
76
+ /* Truncated variant (single line with ellipsis) */
77
+ .truncate {
78
+ white-space: nowrap;
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ }
82
+
83
+ .headline {
84
+ position: relative;
85
+ display: inline-flex;
86
+ align-items: center;
87
+ gap: var(--spacing-xs);
88
+ font-weight: var(--font-weight, var(--font-weight-bold));
89
+ letter-spacing: var(--letter-spacing-tight);
90
+ color: inherit;
91
+ line-height: var(--line-height-tight);
92
+ transition: color var(--transition-fast) var(--ease-standard);
93
+
94
+ /* helps inside flex parents */
95
+ min-width: 0;
96
+ }
97
+
98
+ .icon {
99
+ flex: 0 0 auto;
100
+ display: inline-flex;
101
+ align-items: center;
102
+ }
103
+
104
+ /* text wrapper must be the shrinkable element */
105
+ .text {
106
+ min-width: 0;
107
+ flex: 1 1 auto;
108
+ }
109
+
110
+ .truncate {
111
+ overflow: hidden;
112
+ white-space: nowrap;
113
+ text-overflow: ellipsis;
114
+ }
115
+
116
+ .wrap {
117
+ white-space: normal;
118
+ overflow: visible;
119
+ }
@@ -4,6 +4,7 @@ interface HyperlinkProps {
4
4
  className?: string;
5
5
  icon?: React.ReactNode;
6
6
  disableIcon?: boolean;
7
+ onClick?: (e: React.MouseEvent) => void;
7
8
  }
8
9
  export declare function Hyperlink({ component, icon }: HyperlinkProps): React.ReactElement;
9
10
  export {};
@@ -6,6 +6,10 @@ export function Hyperlink({ component, icon }) {
6
6
  return React.cloneElement(component, {
7
7
  ...originalProps,
8
8
  className: styles.link,
9
- onClick: (e) => e.stopPropagation(),
9
+ onClick: (e) => {
10
+ var _a;
11
+ e.stopPropagation();
12
+ (_a = originalProps === null || originalProps === void 0 ? void 0 : originalProps.onClick) === null || _a === void 0 ? void 0 : _a.call(originalProps, e);
13
+ },
10
14
  }, _jsxs(_Fragment, { children: [_jsx("span", { className: styles.content, children: originalProps.children }), icon && _jsx("span", { className: styles.icon, children: icon })] }));
11
15
  }
@@ -4,6 +4,7 @@
4
4
  vertical-align: middle;
5
5
  gap: var(--spacing-xxs);
6
6
  color: var(--color-fg-subtle);
7
+ font-size: var(--font-size-sm);
7
8
  }
8
9
 
9
10
  .icon {
@@ -10,7 +10,7 @@ import { useTooltipTrigger } from '../../components/overlay/tooltip/useTooltipTr
10
10
  import { Popover } from '../../components/popover/Popover';
11
11
  export function IntervalSelect({
12
12
  // InputContainer props
13
- label, error, helpText, orientation = 'vertical', labelWidth = '120px', fullWidth = true, required,
13
+ label, error, helpText, orientation = 'vertical', labelWidth = '160px', fullWidth = true, required,
14
14
  // tooltip
15
15
  tooltip, tooltipPlacement = 'right',
16
16
  // IntervalSelect props
@@ -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
  }
@@ -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,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,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
  }