@dbcdk/react-components 0.0.65 → 0.0.67
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.
- package/dist/components/button-select/ButtonSelect.d.ts +16 -0
- package/dist/components/button-select/ButtonSelect.js +7 -0
- package/dist/components/button-select/ButtonSelect.module.css +40 -0
- package/dist/components/forms/input/Input.module.css +6 -5
- package/dist/components/nav-bar/NavBar.d.ts +1 -0
- package/dist/components/overlay/tooltip/Tooltip.module.css +0 -2
- package/dist/components/search-box/SearchBox.d.ts +1 -0
- package/dist/components/search-box/SearchBox.js +5 -2
- package/dist/components/sidebar/Sidebar.d.ts +6 -1
- package/dist/components/sidebar/Sidebar.js +2 -3
- package/dist/components/sidebar/components/SidebarItem.d.ts +2 -1
- package/dist/components/sidebar/components/SidebarItem.js +2 -2
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts +2 -1
- package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.js +3 -3
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.d.ts +7 -2
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +132 -2
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.module.css +67 -5
- package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.d.ts +2 -1
- package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.js +3 -2
- package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css +11 -0
- package/dist/components/sidebar/components/sidebar-items/SidebarItems.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles/themes/dbc/dark.css +5 -5
- package/dist/styles/themes/dbc/light.css +5 -5
- package/package.json +2 -6
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JSX } from 'react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import type { ButtonSize } from '../../components/button/Button';
|
|
4
|
+
export interface ButtonSelectOption<T extends string = string> {
|
|
5
|
+
value: T;
|
|
6
|
+
label: React.ReactNode;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface ButtonSelectProps<T extends string = string> {
|
|
10
|
+
options: ButtonSelectOption<T>[];
|
|
11
|
+
value: T;
|
|
12
|
+
onChange: (value: T) => void;
|
|
13
|
+
size?: ButtonSize;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function ButtonSelect<T extends string>({ options, value, onChange, size, disabled, }: ButtonSelectProps<T>): JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from '../../components/button/Button';
|
|
4
|
+
import styles from './ButtonSelect.module.css';
|
|
5
|
+
export function ButtonSelect({ options, value, onChange, size = 'md', disabled, }) {
|
|
6
|
+
return (_jsx("div", { className: styles.group, role: "group", children: options.map(option => (_jsx(Button, { className: option.value === value ? `${styles.btn} ${styles.btnSelected}` : styles.btn, variant: "outlined", shape: "default", size: size, active: option.value === value, disabled: disabled || option.disabled, onClick: () => onChange(option.value), children: option.label }, option.value))) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.group {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
flex-wrap: nowrap;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/* Collapse the doubled border between adjacent buttons */
|
|
7
|
+
.group .btn:not(:first-child) {
|
|
8
|
+
margin-left: -1px;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Flatten interior corners */
|
|
12
|
+
.group .btn:not(:first-child) {
|
|
13
|
+
border-top-left-radius: 0;
|
|
14
|
+
border-bottom-left-radius: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.group .btn:not(:last-child) {
|
|
18
|
+
border-top-right-radius: 0;
|
|
19
|
+
border-bottom-right-radius: 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/* Raise hovered / focused / selected button so its full border is visible above siblings */
|
|
23
|
+
.group .btn:hover,
|
|
24
|
+
.group .btn:focus-visible,
|
|
25
|
+
.group .btn:active,
|
|
26
|
+
.group .btnSelected {
|
|
27
|
+
position: relative;
|
|
28
|
+
z-index: 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Selected state follows the system's selection language (same as Chip) */
|
|
32
|
+
.group .btnSelected {
|
|
33
|
+
background-color: var(--color-bg-selected);
|
|
34
|
+
color: var(--color-brand);
|
|
35
|
+
border-color: var(--color-border-selected);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.group .btnSelected:hover {
|
|
39
|
+
background-color: var(--color-bg-selected);
|
|
40
|
+
}
|
|
@@ -163,9 +163,9 @@
|
|
|
163
163
|
|
|
164
164
|
.standalone {
|
|
165
165
|
background-color: var(--color-bg-surface);
|
|
166
|
-
border-color: var(--color-border-
|
|
166
|
+
border-color: var(--color-border-subtle);
|
|
167
167
|
border-radius: var(--border-radius-rounded);
|
|
168
|
-
box-shadow: var(--shadow-xs), var(--shadow-
|
|
168
|
+
box-shadow: var(--shadow-xs), var(--shadow-sm);
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
.standalone .input {
|
|
@@ -401,14 +401,15 @@
|
|
|
401
401
|
.withButton:has(.standalone) .trailingButton {
|
|
402
402
|
border-top-right-radius: var(--border-radius-rounded);
|
|
403
403
|
border-bottom-right-radius: var(--border-radius-rounded);
|
|
404
|
-
border-left-color: var(--color-border-
|
|
404
|
+
border-left-color: var(--color-border-subtle);
|
|
405
|
+
border-color: var(--color-border-subtle);
|
|
405
406
|
background-color: var(--color-bg-surface);
|
|
406
|
-
box-shadow: var(--shadow-xs), var(--shadow-
|
|
407
|
+
box-shadow: var(--shadow-xs), var(--shadow-sm);
|
|
407
408
|
}
|
|
408
409
|
|
|
409
410
|
.withButton:has(.standalone) .trailingButton:hover {
|
|
410
411
|
border-color: var(--color-border-strong);
|
|
411
|
-
box-shadow: var(--shadow-sm), var(--shadow-
|
|
412
|
+
box-shadow: var(--shadow-sm), var(--shadow-sm);
|
|
412
413
|
}
|
|
413
414
|
|
|
414
415
|
/* Date/time picker indicator (WebKit) */
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
position: fixed;
|
|
22
22
|
z-index: var(--z-tooltip);
|
|
23
23
|
pointer-events: none;
|
|
24
|
-
|
|
25
24
|
background: var(--color-fg-default);
|
|
26
25
|
color: var(--color-fg-on-strong);
|
|
27
26
|
font-size: var(--font-size-xs);
|
|
@@ -30,7 +29,6 @@
|
|
|
30
29
|
border-radius: var(--border-radius-default);
|
|
31
30
|
|
|
32
31
|
/*
|
|
33
|
-
✅ Width behavior:
|
|
34
32
|
- Don't let it run wild horizontally
|
|
35
33
|
- But don't clip: allow wrap
|
|
36
34
|
- Keep some relation to viewport
|
|
@@ -3,6 +3,7 @@ import { InputVariant } from '../../components/forms/input/Input';
|
|
|
3
3
|
import { Size } from '../../types/sizes.types';
|
|
4
4
|
type SearchBoxProps<T extends Record<string, unknown>> = {
|
|
5
5
|
inputWidth?: string | number;
|
|
6
|
+
maxWidth?: string | number;
|
|
6
7
|
inputSize?: Exclude<Size, 'xl'>;
|
|
7
8
|
variant?: InputVariant;
|
|
8
9
|
result?: T[];
|
|
@@ -7,7 +7,7 @@ import { Menu } from '../../components/menu/Menu';
|
|
|
7
7
|
import { Popover } from '../../components/popover/Popover';
|
|
8
8
|
import { SkeletonLoaderItem } from '../../components/skeleton-loader/skeleton-loader-item/SkeletonLoaderItem';
|
|
9
9
|
import styles from './SearchBox.module.css';
|
|
10
|
-
export const SearchBox = forwardRef(function SearchBoxInner({ inputWidth, inputSize, variant, result, debounce = true, debounceMs = 800, onSearch, onSelect, displayPopover, resultKeys, resultTemplate, initialTemplate, popoverMinWidth = '500px', noResultText = 'Ingen resultater', loading, enableHotkey = true, onButtonClick, buttonLabel, buttonIcon, fullWidth = false, value, onChange, ...rest }, ref) {
|
|
10
|
+
export const SearchBox = forwardRef(function SearchBoxInner({ inputWidth, maxWidth, inputSize, variant, result, debounce = true, debounceMs = 800, onSearch, onSelect, displayPopover, resultKeys, resultTemplate, initialTemplate, popoverMinWidth = '500px', noResultText = 'Ingen resultater', loading, enableHotkey = true, onButtonClick, buttonLabel, buttonIcon, fullWidth = false, value, onChange, ...rest }, ref) {
|
|
11
11
|
const isControlled = value !== undefined;
|
|
12
12
|
// What the user sees immediately in the textbox
|
|
13
13
|
const [draft, setDraft] = useState(() => (isControlled ? String(value !== null && value !== void 0 ? value : '') : ''));
|
|
@@ -156,5 +156,8 @@ export const SearchBox = forwardRef(function SearchBoxInner({ inputWidth, inputS
|
|
|
156
156
|
buttonIcon,
|
|
157
157
|
fullWidth,
|
|
158
158
|
]);
|
|
159
|
-
return _jsx("div", { style:
|
|
159
|
+
return (_jsx("div", { style: {
|
|
160
|
+
...(fullWidth ? { width: '100%' } : undefined),
|
|
161
|
+
...(maxWidth !== undefined ? { maxWidth } : undefined),
|
|
162
|
+
}, children: inputField }));
|
|
160
163
|
});
|
|
@@ -11,6 +11,11 @@ interface SidebarProps {
|
|
|
11
11
|
activeLink?: string;
|
|
12
12
|
version?: string | number;
|
|
13
13
|
hideSearch?: boolean;
|
|
14
|
+
footer?: React.ReactNode;
|
|
15
|
+
resizable?: boolean;
|
|
16
|
+
defaultWidth?: number;
|
|
17
|
+
minWidth?: number;
|
|
18
|
+
storageKey?: string;
|
|
14
19
|
}
|
|
15
|
-
export declare function Sidebar({ items, productLogo, activeLink, version, hideSearch, }: SidebarProps): JSX.Element;
|
|
20
|
+
export declare function Sidebar({ items, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarProps): JSX.Element;
|
|
16
21
|
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
'use client';
|
|
2
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import { SidebarContainer } from './components/sidebar-container/SidebarContainer';
|
|
4
3
|
import { SidebarProvider } from './providers/SidebarProvider';
|
|
5
|
-
export function Sidebar({ items, productLogo, activeLink, version, hideSearch, }) {
|
|
6
|
-
return (_jsx(SidebarProvider, { items: items, children: _jsx(SidebarContainer, { productLogo: productLogo, activeLink: activeLink, version: version, hideSearch: hideSearch }) }));
|
|
4
|
+
export function Sidebar({ items, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }) {
|
|
5
|
+
return (_jsx(SidebarProvider, { items: items, children: _jsx(SidebarContainer, { productLogo: productLogo, activeLink: activeLink, version: version, hideSearch: hideSearch, footer: footer, resizable: resizable, defaultWidth: defaultWidth, minWidth: minWidth, storageKey: storageKey }) }));
|
|
7
6
|
}
|
|
@@ -4,6 +4,7 @@ interface SidebarItemProps {
|
|
|
4
4
|
label: string;
|
|
5
5
|
icon: React.ReactNode;
|
|
6
6
|
href?: string;
|
|
7
|
+
truncateLabel?: boolean;
|
|
7
8
|
}
|
|
8
|
-
export declare function SidebarItem({ component: Component, label, icon, href, }: SidebarItemProps): React.ReactNode;
|
|
9
|
+
export declare function SidebarItem({ component: Component, label, icon, href, truncateLabel, }: SidebarItemProps): React.ReactNode;
|
|
9
10
|
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { SidebarItemContent } from './sidebar-item-content/SidebarItemContent';
|
|
3
|
-
export function SidebarItem({ component: Component, label, icon, href, }) {
|
|
3
|
+
export function SidebarItem({ component: Component, label, icon, href, truncateLabel, }) {
|
|
4
4
|
if (!Component) {
|
|
5
5
|
return null;
|
|
6
6
|
}
|
|
7
|
-
return (_jsx(Component, { children: _jsx(SidebarItemContent, { icon: icon, label: label, href: href }) }));
|
|
7
|
+
return (_jsx(Component, { children: _jsx(SidebarItemContent, { icon: icon, label: label, href: href, truncateLabel: truncateLabel }) }));
|
|
8
8
|
}
|
package/dist/components/sidebar/components/expandable-sidebar-item/ExpandableSidebarItem.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ type ExpandableSidebarItemProps = {
|
|
|
6
6
|
component: React.ElementType;
|
|
7
7
|
icon: React.ReactNode;
|
|
8
8
|
href: string;
|
|
9
|
+
truncateLabel?: boolean;
|
|
9
10
|
};
|
|
10
|
-
export declare function ExpandableSidebarItem({ items, label, icon, component: Component, href, }: ExpandableSidebarItemProps): React.ReactNode;
|
|
11
|
+
export declare function ExpandableSidebarItem({ items, label, icon, component: Component, href, truncateLabel, }: ExpandableSidebarItemProps): React.ReactNode;
|
|
11
12
|
export {};
|
|
@@ -10,7 +10,7 @@ import { SidebarItemContent } from '../sidebar-item-content/SidebarItemContent';
|
|
|
10
10
|
import { SidebarItem } from '../SidebarItem';
|
|
11
11
|
const isGroup = (item) => item.type === 'group';
|
|
12
12
|
const isExpandable = (item) => item.type === 'expandable';
|
|
13
|
-
export function ExpandableSidebarItem({ items, label, icon, component: Component, href, }) {
|
|
13
|
+
export function ExpandableSidebarItem({ items, label, icon, component: Component, href, truncateLabel, }) {
|
|
14
14
|
const { defaultExpanded, resetExpandAll, isSidebarCollapsed, handleSidebarCollapseChange, expandItem, collapseItem, isExpanded, } = useSidebar();
|
|
15
15
|
// Local-only state for animation coordination
|
|
16
16
|
const [closing, setClosing] = useState(false);
|
|
@@ -60,7 +60,7 @@ export function ExpandableSidebarItem({ items, label, icon, component: Component
|
|
|
60
60
|
if (isExpandable(item)) {
|
|
61
61
|
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));
|
|
62
62
|
}
|
|
63
|
-
return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href }, key));
|
|
63
|
+
return (_jsx(SidebarItem, { component: item.component, label: item.label, icon: item.icon, href: item.href, truncateLabel: item.truncateLabel }, key));
|
|
64
64
|
};
|
|
65
|
-
return (_jsxs("div", { className: `${styles.container}`, children: [_jsx(Component, { onClick: () => toggleAccordion(undefined, true), children: _jsx(SidebarItemContent, { headerStyle: expanded, icon: icon, label: label, href: href, disableActiveStyles: expanded, suffixIcon: isSidebarCollapsed ? null : (_jsx(Button, { variant: "outlined", onClick: toggleAccordion, children: _jsx(ChevronDown, { className: `${styles.chevron} ${expanded ? styles.chevronExpanded : ''}` }) })) }) }), expanded && !isSidebarCollapsed && (_jsx("div", { onAnimationEnd: handleAnimationEnd, className: `${styles.childrenContainer} ${closing ? 'animate--collapse' : ''} ${expanded ? 'animate--expand' : 'visually-hidden'}`, children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`)) }))] }));
|
|
65
|
+
return (_jsxs("div", { className: `${styles.container}`, children: [_jsx(Component, { onClick: () => toggleAccordion(undefined, true), children: _jsx(SidebarItemContent, { headerStyle: expanded, icon: icon, label: label, href: href, disableActiveStyles: expanded, truncateLabel: truncateLabel, suffixIcon: isSidebarCollapsed ? null : (_jsx(Button, { variant: "outlined", onClick: toggleAccordion, children: _jsx(ChevronDown, { className: `${styles.chevron} ${expanded ? styles.chevronExpanded : ''}` }) })) }) }), expanded && !isSidebarCollapsed && (_jsx("div", { onAnimationEnd: handleAnimationEnd, className: `${styles.childrenContainer} ${closing ? 'animate--collapse' : ''} ${expanded ? 'animate--expand' : 'visually-hidden'}`, children: items.map((item, idx) => renderNavItem(item, `${href}-${idx}`)) }))] }));
|
|
66
66
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { JSX, ReactNode } from 'react';
|
|
2
2
|
interface SidebarContainerProps {
|
|
3
3
|
logo?: ReactNode;
|
|
4
4
|
productName?: string;
|
|
@@ -6,6 +6,11 @@ interface SidebarContainerProps {
|
|
|
6
6
|
activeLink?: string;
|
|
7
7
|
version?: string | number;
|
|
8
8
|
hideSearch?: boolean;
|
|
9
|
+
footer?: ReactNode;
|
|
10
|
+
resizable?: boolean;
|
|
11
|
+
defaultWidth?: number;
|
|
12
|
+
minWidth?: number;
|
|
13
|
+
storageKey?: string;
|
|
9
14
|
}
|
|
10
|
-
export declare function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, }: SidebarContainerProps): JSX.Element;
|
|
15
|
+
export declare function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth, minWidth, storageKey, }: SidebarContainerProps): JSX.Element;
|
|
11
16
|
export {};
|
|
@@ -1,12 +1,142 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import { ChevronLeft } from 'lucide-react';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
5
|
import { Logo } from '../../../../assets/logo';
|
|
4
6
|
import { Button } from '../../../../components/button/Button';
|
|
5
7
|
import { SidebarItems } from '../../../../components/sidebar/components/sidebar-items/SidebarItems';
|
|
6
8
|
import SidenavFiltering from '../../../../components/sidebar/components/sidenav-filteirng/SidenavFiltering';
|
|
7
9
|
import { useSidebar } from '../../../../components/sidebar/providers/SidebarProvider';
|
|
8
10
|
import styles from './SidebarContainer.module.css';
|
|
9
|
-
|
|
11
|
+
function clamp(n, min, max) {
|
|
12
|
+
return Math.max(min, Math.min(max, n));
|
|
13
|
+
}
|
|
14
|
+
function readStoredWidth(key) {
|
|
15
|
+
try {
|
|
16
|
+
const raw = localStorage.getItem(key);
|
|
17
|
+
if (!raw)
|
|
18
|
+
return null;
|
|
19
|
+
const num = Number(raw);
|
|
20
|
+
return Number.isFinite(num) ? num : null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeStoredWidth(key, value) {
|
|
27
|
+
try {
|
|
28
|
+
localStorage.setItem(key, String(Math.round(value)));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function removeStoredWidth(key) {
|
|
35
|
+
try {
|
|
36
|
+
localStorage.removeItem(key);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function SidebarContainer({ logo, productLogo, activeLink, version, hideSearch, footer, resizable, defaultWidth = 240, minWidth = 160, storageKey, }) {
|
|
10
43
|
const { isSidebarCollapsed, handleSidebarCollapseChange } = useSidebar();
|
|
11
|
-
|
|
44
|
+
const initialStoredWidth = typeof window !== 'undefined' && storageKey ? readStoredWidth(storageKey) : null;
|
|
45
|
+
const [sidebarWidth, setSidebarWidth] = useState(() => initialStoredWidth !== null && initialStoredWidth !== void 0 ? initialStoredWidth : defaultWidth);
|
|
46
|
+
const [manualWidth, setManualWidth] = useState(() => initialStoredWidth);
|
|
47
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
48
|
+
const [ariaMaxWidth, setAriaMaxWidth] = useState(undefined);
|
|
49
|
+
const containerRef = useRef(null);
|
|
50
|
+
const draggingRef = useRef(false);
|
|
51
|
+
const pointerIdRef = useRef(null);
|
|
52
|
+
const startXRef = useRef(0);
|
|
53
|
+
const startWidthRef = useRef(0);
|
|
54
|
+
const maxWidthRef = useRef(Infinity);
|
|
55
|
+
// Use the viewport width as the ceiling — avoids the circular-dependency
|
|
56
|
+
// that occurs when the sidebar lives in an `auto`-width grid column whose
|
|
57
|
+
// size is determined by the sidebar itself (e.g. PageLayout vertical).
|
|
58
|
+
const getMaxWidth = useCallback(() => {
|
|
59
|
+
const viewportWidth = typeof window !== 'undefined'
|
|
60
|
+
? window.innerWidth || document.documentElement.clientWidth
|
|
61
|
+
: Infinity;
|
|
62
|
+
return Math.max(minWidth, viewportWidth - minWidth);
|
|
63
|
+
}, [minWidth]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!storageKey)
|
|
66
|
+
return;
|
|
67
|
+
if (manualWidth === null) {
|
|
68
|
+
removeStoredWidth(storageKey);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
writeStoredWidth(storageKey, manualWidth);
|
|
72
|
+
}, [manualWidth, storageKey]);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setAriaMaxWidth(getMaxWidth());
|
|
75
|
+
}, [getMaxWidth]);
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
return () => {
|
|
78
|
+
document.body.style.cursor = '';
|
|
79
|
+
document.body.style.userSelect = '';
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
const updateWidth = useCallback((nextWidth) => {
|
|
83
|
+
setManualWidth(nextWidth);
|
|
84
|
+
setSidebarWidth(nextWidth);
|
|
85
|
+
}, []);
|
|
86
|
+
const onResizePointerDown = useCallback((e) => {
|
|
87
|
+
const maxWidth = getMaxWidth();
|
|
88
|
+
if (maxWidth === null)
|
|
89
|
+
return;
|
|
90
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
91
|
+
maxWidthRef.current = maxWidth;
|
|
92
|
+
draggingRef.current = true;
|
|
93
|
+
pointerIdRef.current = e.pointerId;
|
|
94
|
+
startXRef.current = e.clientX;
|
|
95
|
+
startWidthRef.current = sidebarWidth;
|
|
96
|
+
setIsResizing(true);
|
|
97
|
+
document.body.style.userSelect = 'none';
|
|
98
|
+
document.body.style.cursor = 'col-resize';
|
|
99
|
+
}, [getMaxWidth, sidebarWidth]);
|
|
100
|
+
const onResizePointerMove = useCallback((e) => {
|
|
101
|
+
if (!draggingRef.current)
|
|
102
|
+
return;
|
|
103
|
+
if (pointerIdRef.current !== null && e.pointerId !== pointerIdRef.current)
|
|
104
|
+
return;
|
|
105
|
+
const next = startWidthRef.current + (e.clientX - startXRef.current);
|
|
106
|
+
updateWidth(clamp(next, minWidth, maxWidthRef.current));
|
|
107
|
+
}, [minWidth, updateWidth]);
|
|
108
|
+
const endResizeDrag = useCallback(() => {
|
|
109
|
+
if (!draggingRef.current)
|
|
110
|
+
return;
|
|
111
|
+
draggingRef.current = false;
|
|
112
|
+
pointerIdRef.current = null;
|
|
113
|
+
setIsResizing(false);
|
|
114
|
+
document.body.style.cursor = '';
|
|
115
|
+
document.body.style.userSelect = '';
|
|
116
|
+
}, []);
|
|
117
|
+
const resetWidth = useCallback(() => {
|
|
118
|
+
setManualWidth(null);
|
|
119
|
+
setSidebarWidth(clamp(defaultWidth, minWidth, getMaxWidth()));
|
|
120
|
+
}, [defaultWidth, getMaxWidth, minWidth]);
|
|
121
|
+
const onKeyDown = useCallback((e) => {
|
|
122
|
+
const maxWidth = getMaxWidth();
|
|
123
|
+
const step = e.shiftKey ? 32 : 8;
|
|
124
|
+
let next = null;
|
|
125
|
+
if (e.key === 'ArrowLeft')
|
|
126
|
+
next = sidebarWidth - step;
|
|
127
|
+
if (e.key === 'ArrowRight')
|
|
128
|
+
next = sidebarWidth + step;
|
|
129
|
+
if (e.key === 'Home')
|
|
130
|
+
next = minWidth;
|
|
131
|
+
if (e.key === 'End')
|
|
132
|
+
next = maxWidth;
|
|
133
|
+
if (next === null)
|
|
134
|
+
return;
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
updateWidth(clamp(next, minWidth, maxWidth));
|
|
137
|
+
}, [getMaxWidth, minWidth, sidebarWidth, updateWidth]);
|
|
138
|
+
const containerStyle = useMemo(() => ({
|
|
139
|
+
'--sidebar-width': `${sidebarWidth}px`,
|
|
140
|
+
}), [sidebarWidth]);
|
|
141
|
+
return (_jsxs("div", { ref: containerRef, role: "complementary", className: `${styles.container} ${isSidebarCollapsed ? styles.collapsed : ''} ${isResizing ? styles.resizing : ''}`, style: containerStyle, children: [_jsx("div", { className: styles.header, children: _jsxs("div", { className: styles.productHeader, children: [_jsx("div", { className: styles.productLogo, children: productLogo }), _jsx(Button, { size: "md", variant: "inline", shape: "round", "aria-label": "Collapse sidebar", icon: _jsx(ChevronLeft, { className: isSidebarCollapsed ? styles.collapsedIcon : '' }), onClick: () => handleSidebarCollapseChange(!isSidebarCollapsed) })] }) }), _jsxs("div", { className: styles.content, children: [!hideSearch && (_jsx("div", { className: styles.filter, children: _jsx(SidenavFiltering, {}) })), _jsx("div", { className: `${styles.links} hideScrollBar`, children: _jsx(SidebarItems, { activeLink: activeLink }) })] }), footer && _jsx("div", { className: styles.footerSlot, children: footer }), _jsxs("div", { className: styles.footer, children: [_jsx("div", { className: styles.companyLogo, children: logo !== null && logo !== void 0 ? logo : _jsx(Logo, {}) }), version && _jsx("div", { className: `${styles.version} dbc-muted-text dbc-sm-text`, children: version })] }), resizable && (_jsx("div", { className: styles.resizeHandle, role: "separator", "aria-label": "Resize sidebar", "aria-orientation": "vertical", "aria-valuemin": Math.round(minWidth), "aria-valuemax": ariaMaxWidth !== undefined ? Math.round(ariaMaxWidth) : undefined, "aria-valuenow": Math.round(sidebarWidth), tabIndex: isSidebarCollapsed ? -1 : 0, onPointerDown: onResizePointerDown, onPointerMove: onResizePointerMove, onPointerUp: endResizeDrag, onPointerCancel: endResizeDrag, onDoubleClick: resetWidth, onKeyDown: onKeyDown }))] }));
|
|
12
142
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.container {
|
|
2
|
+
position: relative;
|
|
2
3
|
flex-shrink: 1;
|
|
3
4
|
height: 100%;
|
|
4
5
|
overflow: auto;
|
|
@@ -14,6 +15,10 @@
|
|
|
14
15
|
inline-size var(--transition-fast) var(--ease-standard);
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
.container.resizing {
|
|
19
|
+
transition: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
/* Collapsed state */
|
|
18
23
|
.container.collapsed {
|
|
19
24
|
width: var(--component-size-lg);
|
|
@@ -37,10 +42,12 @@
|
|
|
37
42
|
/* HEADER (product + collapse) */
|
|
38
43
|
.header {
|
|
39
44
|
flex: 0 0 auto;
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
box-shadow: 0 1px 0 var(--color-border-default);
|
|
47
|
+
padding-inline: var(--spacing-xs);
|
|
42
48
|
min-block-size: 60px;
|
|
43
49
|
display: flex;
|
|
50
|
+
align-items: center;
|
|
44
51
|
justify-content: space-between;
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -57,15 +64,15 @@
|
|
|
57
64
|
display: flex;
|
|
58
65
|
align-items: center;
|
|
59
66
|
max-inline-size: 100%;
|
|
67
|
+
block-size: 25px;
|
|
60
68
|
min-width: 0;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
/* Keep product logo visible in expanded state */
|
|
64
72
|
.productLogo img,
|
|
65
73
|
.productLogo svg {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
block-size: auto;
|
|
74
|
+
block-size: 100%;
|
|
75
|
+
inline-size: 100%;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
/* Collapse button */
|
|
@@ -131,6 +138,61 @@
|
|
|
131
138
|
padding: 0;
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
/* RESIZE HANDLE */
|
|
142
|
+
.resizeHandle {
|
|
143
|
+
position: absolute;
|
|
144
|
+
top: 0;
|
|
145
|
+
bottom: 0;
|
|
146
|
+
right: 0;
|
|
147
|
+
width: 8px;
|
|
148
|
+
cursor: col-resize;
|
|
149
|
+
z-index: 1;
|
|
150
|
+
user-select: none;
|
|
151
|
+
touch-action: none;
|
|
152
|
+
outline: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.resizeHandle::after {
|
|
156
|
+
content: '';
|
|
157
|
+
position: absolute;
|
|
158
|
+
top: 0;
|
|
159
|
+
bottom: 0;
|
|
160
|
+
right: 0;
|
|
161
|
+
width: var(--border-width-thin);
|
|
162
|
+
background-color: var(--color-border-subtle);
|
|
163
|
+
opacity: 0;
|
|
164
|
+
transition:
|
|
165
|
+
opacity var(--transition-fast) var(--ease-standard),
|
|
166
|
+
background-color var(--transition-fast) var(--ease-standard);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.resizeHandle:hover::after,
|
|
170
|
+
.resizeHandle:active::after {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
background-color: var(--color-border-strong);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.resizeHandle:active::after {
|
|
176
|
+
background-color: var(--color-brand);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.resizeHandle:focus-visible {
|
|
180
|
+
box-shadow: var(--focus-ring);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.container.collapsed .resizeHandle {
|
|
184
|
+
display: none;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* CUSTOM FOOTER SLOT */
|
|
188
|
+
.footerSlot {
|
|
189
|
+
flex: 0 0 auto;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.container:not(.collapsed) .footerSlot {
|
|
193
|
+
padding-inline: var(--spacing-xs);
|
|
194
|
+
}
|
|
195
|
+
|
|
134
196
|
/* FOOTER (company logo) */
|
|
135
197
|
.footer {
|
|
136
198
|
flex: 0 0 auto;
|
|
@@ -7,5 +7,6 @@ export interface SidebarItemContentProps {
|
|
|
7
7
|
href?: string;
|
|
8
8
|
disableActiveStyles?: boolean;
|
|
9
9
|
headerStyle?: boolean;
|
|
10
|
+
truncateLabel?: boolean;
|
|
10
11
|
}
|
|
11
|
-
export declare function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles, headerStyle, }: SidebarItemContentProps): JSX.Element;
|
|
12
|
+
export declare function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles, headerStyle, truncateLabel, }: SidebarItemContentProps): JSX.Element;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
3
|
import styles from './SidebarItemContent.module.css';
|
|
3
4
|
import { useSidebar } from '../../providers/SidebarProvider';
|
|
4
|
-
export function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles = false, headerStyle, }) {
|
|
5
|
+
export function SidebarItemContent({ icon, label, suffixIcon, href, disableActiveStyles = false, headerStyle, truncateLabel = false, }) {
|
|
5
6
|
const { activeLink, isSidebarCollapsed } = useSidebar();
|
|
6
|
-
return (_jsxs("span", { className: `${styles.container} ${!disableActiveStyles && activeLink === href ? styles.active : ''} ${isSidebarCollapsed ? styles.collapsed : ''} ${headerStyle ? styles.headerStyle : ''}`, children: [_jsxs("span", { children: [_jsx("span", { className: styles.icon, children: icon }), !isSidebarCollapsed && _jsx("span", { className: styles.label, children: label })] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
|
|
7
|
+
return (_jsxs("span", { className: `${styles.container} ${!disableActiveStyles && activeLink === href ? styles.active : ''} ${isSidebarCollapsed ? styles.collapsed : ''} ${headerStyle ? styles.headerStyle : ''}`, children: [_jsxs("span", { children: [_jsx("span", { className: styles.icon, children: icon }), !isSidebarCollapsed && (_jsx("span", { className: `${styles.label} ${truncateLabel ? styles.truncate : ''}`, title: truncateLabel && typeof label === 'string' ? label : undefined, children: label }))] }), suffixIcon && !isSidebarCollapsed && _jsx("span", { className: styles.suffixIcon, children: suffixIcon })] }));
|
|
7
8
|
}
|
package/dist/components/sidebar/components/sidebar-item-content/SidebarItemContent.module.css
CHANGED
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
display: flex;
|
|
42
42
|
align-items: center;
|
|
43
43
|
gap: var(--spacing-sm);
|
|
44
|
+
min-inline-size: 0;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
.container:not(.active):hover {
|
|
@@ -83,3 +84,13 @@
|
|
|
83
84
|
letter-spacing: 0.04em;
|
|
84
85
|
transition: 0.15s ease-in-out;
|
|
85
86
|
}
|
|
87
|
+
|
|
88
|
+
.label {
|
|
89
|
+
min-inline-size: 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.truncate {
|
|
93
|
+
overflow: hidden;
|
|
94
|
+
text-overflow: ellipsis;
|
|
95
|
+
white-space: nowrap;
|
|
96
|
+
}
|
|
@@ -16,11 +16,11 @@ export function SidebarItems({ activeLink }) {
|
|
|
16
16
|
return isSidebarCollapsed ? ((_a = item.children) === null || _a === void 0 ? void 0 : _a.map((child, idx) => renderItem(child, `${key}-c${idx}`))) : (_jsxs("div", { className: styles.group, children: [_jsx("div", { className: styles.groupLabel, children: item.label }), (_b = item.children) === null || _b === void 0 ? void 0 : _b.map((child, idx) => renderItem(child, `${key}-c${idx}`))] }, key));
|
|
17
17
|
}
|
|
18
18
|
if (item.type === 'expandable') {
|
|
19
|
-
const { component: Component, label, icon, children, href } = item;
|
|
20
|
-
return (_jsx(ExpandableSidebarItem, { items: children, label: label, icon: icon, href: href, component: Component }, key));
|
|
19
|
+
const { component: Component, label, icon, children, href, truncateLabel } = item;
|
|
20
|
+
return (_jsx(ExpandableSidebarItem, { items: children, label: label, icon: icon, href: href, truncateLabel: truncateLabel, component: Component }, key));
|
|
21
21
|
}
|
|
22
|
-
const { component: Component, label, icon, href } = item;
|
|
23
|
-
return _jsx(SidebarItem, { component: Component, label: label, icon: icon, href: href }, key);
|
|
22
|
+
const { component: Component, label, icon, href, truncateLabel } = item;
|
|
23
|
+
return (_jsx(SidebarItem, { component: Component, label: label, icon: icon, href: href, truncateLabel: truncateLabel }, key));
|
|
24
24
|
};
|
|
25
25
|
return filteredItems === null || filteredItems === void 0 ? void 0 : filteredItems.map((item, idx) => renderItem(item, `nav-${idx}-${item.label}`));
|
|
26
26
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -181,11 +181,11 @@ html[data-theme='dark'] {
|
|
|
181
181
|
--opac-bg-dark-invert: rgba(0, 0, 0, 0.15);
|
|
182
182
|
|
|
183
183
|
/* Shadows */
|
|
184
|
-
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.
|
|
185
|
-
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.
|
|
186
|
-
--shadow-md: 0 2px
|
|
187
|
-
--shadow-lg: 0
|
|
188
|
-
--shadow-xl: 0
|
|
184
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.18);
|
|
185
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.24);
|
|
186
|
+
--shadow-md: 0 2px 6px rgba(0, 0, 0, 0.32);
|
|
187
|
+
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
188
|
+
--shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.48);
|
|
189
189
|
|
|
190
190
|
/* Data viz */
|
|
191
191
|
--viz-cat-1: #2563eb;
|
|
@@ -180,11 +180,11 @@ html[data-theme='light'] {
|
|
|
180
180
|
--opac-bg-dark-invert: rgba(255, 255, 255, 0.1);
|
|
181
181
|
|
|
182
182
|
/* Shadows */
|
|
183
|
-
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.
|
|
184
|
-
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.
|
|
185
|
-
--shadow-md: 0 2px
|
|
186
|
-
--shadow-lg: 0
|
|
187
|
-
--shadow-xl: 0
|
|
183
|
+
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
184
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
185
|
+
--shadow-md: 0 2px 6px rgba(0, 0, 0, 0.08);
|
|
186
|
+
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
187
|
+
--shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.12);
|
|
188
188
|
|
|
189
189
|
/* Data viz */
|
|
190
190
|
--viz-cat-1: #2563eb;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbcdk/react-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
4
4
|
"description": "Reusable React components for DBC projects",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"build": "npm run clean && npm run build:code && npm run postbuild",
|
|
47
47
|
"build:code": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
|
|
48
48
|
"dev": "tsc -p tsconfig.build.json --watch",
|
|
49
|
-
"test": "
|
|
49
|
+
"test": "vitest run",
|
|
50
50
|
"format": "prettier . --write",
|
|
51
51
|
"format:fix": "prettier . --write",
|
|
52
52
|
"lint": "eslint \"src/**/*.{ts,tsx}\" --max-warnings=0",
|
|
@@ -77,11 +77,9 @@
|
|
|
77
77
|
"@storybook/addon-docs": "^10.3.5",
|
|
78
78
|
"@storybook/react-vite": "^10.3.5",
|
|
79
79
|
"@swc/core": "^1.15.11",
|
|
80
|
-
"@swc/jest": "^0.2.39",
|
|
81
80
|
"@tanstack/react-table": "^8.20.0",
|
|
82
81
|
"@testing-library/jest-dom": "^6.9.1",
|
|
83
82
|
"@testing-library/react": "^16.3.2",
|
|
84
|
-
"@types/jest": "^30.0.0",
|
|
85
83
|
"@types/react": "^19.2.14",
|
|
86
84
|
"@types/react-dom": "^19.2.3",
|
|
87
85
|
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
|
@@ -98,8 +96,6 @@
|
|
|
98
96
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
99
97
|
"globals": "^17.3.0",
|
|
100
98
|
"identity-obj-proxy": "^3.0.0",
|
|
101
|
-
"jest": "^30.2.0",
|
|
102
|
-
"jest-environment-jsdom": "^30.2.0",
|
|
103
99
|
"jsdom": "^29.0.2",
|
|
104
100
|
"prettier": "^3.2.4",
|
|
105
101
|
"react": "19.2.4",
|