@flikk/ui 1.0.0-beta.25 → 1.0.0-beta.26
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/ai/CodeBlock/CodeBlock.theme.js +2 -2
- package/dist/components/ai/PromptInput/PromptInput.js +1 -0
- package/dist/components/ai/PromptInput/VoiceRecorder.js +1 -0
- package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
- package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
- package/dist/components/charts/BarChart/BarChart.js +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
- package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +1 -1
- package/dist/components/core/Button/Button.js +1 -1
- package/dist/components/core/Button/Button.theme.js +4 -4
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +1 -1
- package/dist/components/core/Drawer/Drawer.theme.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +2 -2
- package/dist/components/core/Empty/Empty.js +1 -1
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
- package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
- package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
- package/dist/components/core/Message/Message.theme.js +1 -1
- package/dist/components/core/Modal/Modal.theme.js +1 -1
- package/dist/components/core/NavItem/NavItem.d.ts +1 -1
- package/dist/components/core/NavItem/NavItem.js +171 -68
- package/dist/components/core/NavItem/NavItem.theme.js +20 -12
- package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
- package/dist/components/core/Pagination/Pagination.theme.js +3 -3
- package/dist/components/core/Popover/Popover.theme.js +1 -1
- package/dist/components/core/Progress/Progress.theme.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
- package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
- package/dist/components/core/Segmented/Segmented.js +8 -6
- package/dist/components/core/Segmented/Segmented.theme.js +3 -3
- package/dist/components/core/Sidebar/Sidebar.d.ts +1 -1
- package/dist/components/core/Sidebar/Sidebar.js +42 -13
- package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +33 -4
- package/dist/components/core/Sidebar/SidebarContent.js +6 -6
- package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
- package/dist/components/core/Sidebar/SidebarContext.js +42 -19
- package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
- package/dist/components/core/Sidebar/SidebarHeader.js +4 -9
- package/dist/components/core/Sidebar/SidebarLogo.js +3 -7
- package/dist/components/core/Sidebar/SidebarNav.js +4 -3
- package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
- package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
- package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
- package/dist/components/core/Sidebar/index.d.ts +2 -3
- package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
- package/dist/components/core/Spinner/Spinner.theme.js +1 -1
- package/dist/components/core/Tabs/Tabs.js +6 -1
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +5 -3
- package/dist/components/core/Tag/Tag.theme.js +1 -1
- package/dist/components/core/index.d.ts +1 -0
- package/dist/components/core/index.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
- package/dist/components/data-display/KPI/KPI.js +13 -3
- package/dist/components/data-display/KPI/KPI.theme.js +3 -3
- package/dist/components/data-display/Metric/Metric.theme.js +3 -3
- package/dist/components/data-display/Table/Table.js +4 -3
- package/dist/components/data-display/Table/Table.theme.js +11 -1
- package/dist/components/data-display/Table/Table.types.d.ts +15 -0
- package/dist/components/data-display/Table/TableColumnManager.js +1 -1
- package/dist/components/data-display/Table/index.d.ts +2 -2
- package/dist/components/effects/Aurora/Aurora.js +79 -40
- package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
- package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
- package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
- package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
- package/dist/components/effects/MorphingText/MorphingText.js +40 -7
- package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
- package/dist/components/effects/StripePattern/StripePattern.js +54 -0
- package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
- package/dist/components/effects/StripePattern/index.d.ts +2 -0
- package/dist/components/effects/index.d.ts +2 -0
- package/dist/components/effects/index.js +1 -0
- package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
- package/dist/components/forms/CronInput/CronInput.theme.js +16 -16
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
- package/dist/components/forms/FileUpload/FileUpload.js +1 -1
- package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
- package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
- package/dist/components/forms/Input/Input.theme.js +9 -11
- package/dist/components/forms/Mention/Mention.theme.js +3 -3
- package/dist/components/forms/Radio/Radio.theme.js +1 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
- package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
- package/dist/components/forms/Select/Select.js +1 -1
- package/dist/components/forms/Slider/Slider.theme.js +1 -1
- package/dist/components/forms/Switch/Switch.theme.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +1 -0
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
- package/dist/components/forms/forms.theme.js +12 -12
- package/dist/components/forms/index.js +1 -0
- package/dist/components/layout/FormLayout/FormLayout.js +6 -4
- package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
- package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
- package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
- package/dist/components/layout/PageLayout/PageLayout.js +23 -5
- package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
- package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
- package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
- package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
- package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
- package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
- package/dist/components/layout/PageLayout/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/styles/theme.css +127 -92
- package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
- package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { SidebarProps } from "./Sidebar.types";
|
|
3
3
|
export declare const Sidebar: React.ForwardRefExoticComponent<SidebarProps & React.RefAttributes<HTMLDivElement>> & {
|
|
4
|
+
Provider: React.FC<import("./Sidebar.types").SidebarProviderProps>;
|
|
4
5
|
Header: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarHeaderProps & React.RefAttributes<HTMLDivElement>>;
|
|
5
6
|
Content: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarContentProps & React.RefAttributes<HTMLDivElement>>;
|
|
6
7
|
Footer: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarFooterProps & React.RefAttributes<HTMLDivElement>>;
|
|
7
8
|
Nav: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarNavProps & React.RefAttributes<HTMLDivElement>>;
|
|
8
9
|
NavGroup: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarNavGroupProps & React.RefAttributes<HTMLDivElement>>;
|
|
9
|
-
Submenu: ({ children, isOpen, className, }: import("./SidebarSubmenu").SidebarSubmenuProps) => import("react/jsx-runtime").JSX.Element;
|
|
10
10
|
Toggle: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarToggleProps & React.RefAttributes<HTMLButtonElement>>;
|
|
11
11
|
MobileTrigger: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarMobileTriggerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
12
12
|
UserProfile: React.ForwardRefExoticComponent<import("./Sidebar.types").SidebarUserProfileProps & React.RefAttributes<HTMLDivElement>>;
|
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useMemo } from 'react';
|
|
2
|
+
import React__default, { useMemo, useRef, useEffect } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { motion, AnimatePresence } from 'motion/react';
|
|
5
5
|
import { sidebarTheme } from './Sidebar.theme.js';
|
|
6
|
+
import { useFocusTrap } from '../../../hooks/useFocusTrap.js';
|
|
6
7
|
import { useSidebarContext, SidebarProvider } from './SidebarContext.js';
|
|
7
8
|
import { SidebarHeader } from './SidebarHeader.js';
|
|
8
9
|
import { SidebarContent } from './SidebarContent.js';
|
|
9
10
|
import { SidebarFooter } from './SidebarFooter.js';
|
|
10
11
|
import { SidebarNav } from './SidebarNav.js';
|
|
11
12
|
import { SidebarNavGroup } from './SidebarNavGroup.js';
|
|
12
|
-
import { SidebarSubmenu } from './SidebarSubmenu.js';
|
|
13
13
|
import { SidebarToggle } from './SidebarToggle.js';
|
|
14
14
|
import { SidebarMobileTrigger } from './SidebarMobileTrigger.js';
|
|
15
15
|
import { SidebarUserProfile } from './SidebarUserProfile.js';
|
|
16
16
|
import { SidebarLogo } from './SidebarLogo.js';
|
|
17
17
|
|
|
18
|
+
// Matches the 200ms / cubic-bezier(0.4,0,0.2,1) timing used by Tailwind's
|
|
19
|
+
// `duration-200` transitions on the content + footer, so width, padding and
|
|
20
|
+
// icon animations stay in sync.
|
|
18
21
|
const SIDEBAR_TRANSITION = {
|
|
19
22
|
type: "tween",
|
|
20
|
-
duration: 0.
|
|
23
|
+
duration: 0.2,
|
|
21
24
|
ease: [0.4, 0, 0.2, 1],
|
|
22
25
|
};
|
|
23
|
-
const SidebarDesktop = React__default.forwardRef(({ side = "left", variant = "sidebar", width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
24
|
-
var _a, _b, _c;
|
|
26
|
+
const SidebarDesktop = React__default.forwardRef(({ side = "left", variant = "sidebar", density, width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
27
|
+
var _a, _b, _c, _d;
|
|
25
28
|
const context = useSidebarContext();
|
|
26
29
|
const collapsed = (_a = context === null || context === void 0 ? void 0 : context.collapsed) !== null && _a !== void 0 ? _a : false;
|
|
27
30
|
const expandedWidth = (width === null || width === void 0 ? void 0 : width.expanded) || ((_b = context === null || context === void 0 ? void 0 : context.width) === null || _b === void 0 ? void 0 : _b.expanded) || 280;
|
|
@@ -38,19 +41,41 @@ const SidebarDesktop = React__default.forwardRef(({ side = "left", variant = "si
|
|
|
38
41
|
width: collapsed ? collapsedWidth : expandedWidth,
|
|
39
42
|
maxWidth: "100vw",
|
|
40
43
|
}), [collapsed, collapsedWidth, expandedWidth]);
|
|
41
|
-
return (jsx(motion.
|
|
44
|
+
return (jsx(motion.aside, { ref: ref, className: sidebarClasses, "aria-label": "Sidebar", "data-side": side, "data-variant": variant, "data-density": (_d = density !== null && density !== void 0 ? density : context === null || context === void 0 ? void 0 : context.density) !== null && _d !== void 0 ? _d : "comfortable", "data-collapsed": collapsed, initial: false, animate: animateValue, transition: SIDEBAR_TRANSITION, ...motionSafeProps, children: children }));
|
|
42
45
|
});
|
|
43
46
|
SidebarDesktop.displayName = "SidebarDesktop";
|
|
44
47
|
const SidebarMobile = ({ children, className, theme: customTheme = {} }) => {
|
|
48
|
+
var _a, _b, _c, _d;
|
|
45
49
|
const context = useSidebarContext();
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const { isMobileOpen, setMobileOpen } = context;
|
|
50
|
+
const panelRef = useRef(null);
|
|
51
|
+
// Hooks must run unconditionally — keep above any early return
|
|
49
52
|
const theme = useMemo(() => ({
|
|
50
53
|
...sidebarTheme,
|
|
51
54
|
...customTheme,
|
|
52
55
|
}), [customTheme]);
|
|
53
|
-
|
|
56
|
+
const isMobileOpen = (_a = context === null || context === void 0 ? void 0 : context.isMobileOpen) !== null && _a !== void 0 ? _a : false;
|
|
57
|
+
const side = (_b = context === null || context === void 0 ? void 0 : context.side) !== null && _b !== void 0 ? _b : "left";
|
|
58
|
+
const panelWidth = (_d = (_c = context === null || context === void 0 ? void 0 : context.width) === null || _c === void 0 ? void 0 : _c.expanded) !== null && _d !== void 0 ? _d : 280;
|
|
59
|
+
// Trap focus inside the open drawer; focus returns to trigger on close.
|
|
60
|
+
useFocusTrap(panelRef, isMobileOpen);
|
|
61
|
+
// Close on Escape while the drawer is open.
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!isMobileOpen)
|
|
64
|
+
return;
|
|
65
|
+
const onKeyDown = (e) => {
|
|
66
|
+
if (e.key === "Escape")
|
|
67
|
+
context === null || context === void 0 ? void 0 : context.setMobileOpen(false);
|
|
68
|
+
};
|
|
69
|
+
document.addEventListener("keydown", onKeyDown);
|
|
70
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
71
|
+
}, [isMobileOpen, context]);
|
|
72
|
+
if (!context)
|
|
73
|
+
return null;
|
|
74
|
+
const { setMobileOpen } = context;
|
|
75
|
+
const offscreen = side === "left" ? "-100%" : "100%";
|
|
76
|
+
return (jsx(AnimatePresence, { children: isMobileOpen && (jsxs(Fragment, { children: [jsx(motion.div, { className: cn(theme.mobileBackdrop), initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2 }, onClick: () => setMobileOpen(false), "aria-hidden": "true" }), jsx(motion.div, { ref: panelRef, className: cn(theme.mobileOverlay, side === "left"
|
|
77
|
+
? "left-0 rounded-r-xl border-r"
|
|
78
|
+
: "right-0 rounded-l-xl border-l rounded-r-none border-r-0", className), style: { width: panelWidth }, initial: { x: offscreen }, animate: { x: 0 }, exit: { x: offscreen }, transition: { type: "spring", stiffness: 400, damping: 38 }, role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", children: children })] })) }));
|
|
54
79
|
};
|
|
55
80
|
const SidebarContainer = React__default.forwardRef((props, ref) => {
|
|
56
81
|
const context = useSidebarContext();
|
|
@@ -60,17 +85,21 @@ const SidebarContainer = React__default.forwardRef((props, ref) => {
|
|
|
60
85
|
return jsx(SidebarDesktop, { ref: ref, ...props });
|
|
61
86
|
});
|
|
62
87
|
SidebarContainer.displayName = "SidebarContainer";
|
|
63
|
-
const SidebarRoot = React__default.forwardRef(({ side = "left", variant = "sidebar", defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileBreakpoint, mobileOpen, onMobileOpenChange, width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
64
|
-
|
|
88
|
+
const SidebarRoot = React__default.forwardRef(({ side = "left", variant = "sidebar", defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, density = "comfortable", mobileBreakpoint, mobileOpen, onMobileOpenChange, width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
89
|
+
const existingContext = useSidebarContext();
|
|
90
|
+
if (existingContext) {
|
|
91
|
+
return (jsx(SidebarContainer, { ref: ref, className: className, theme: customTheme, side: side, variant: variant, density: density, width: width, ...props, children: children }));
|
|
92
|
+
}
|
|
93
|
+
return (jsx(SidebarProvider, { defaultCollapsed: defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange: onCollapsedChange, density: density, mobileBreakpoint: mobileBreakpoint, mobileOpen: mobileOpen, onMobileOpenChange: onMobileOpenChange, width: width, side: side, variant: variant, children: jsx(SidebarContainer, { ref: ref, className: className, theme: customTheme, side: side, variant: variant, density: density, width: width, ...props, children: children }) }));
|
|
65
94
|
});
|
|
66
95
|
SidebarRoot.displayName = "Sidebar";
|
|
67
96
|
const Sidebar = Object.assign(SidebarRoot, {
|
|
97
|
+
Provider: SidebarProvider,
|
|
68
98
|
Header: SidebarHeader,
|
|
69
99
|
Content: SidebarContent,
|
|
70
100
|
Footer: SidebarFooter,
|
|
71
101
|
Nav: SidebarNav,
|
|
72
102
|
NavGroup: SidebarNavGroup,
|
|
73
|
-
Submenu: SidebarSubmenu,
|
|
74
103
|
Toggle: SidebarToggle,
|
|
75
104
|
MobileTrigger: SidebarMobileTrigger,
|
|
76
105
|
UserProfile: SidebarUserProfile,
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
const sidebarTheme = {
|
|
2
|
-
baseStyle: 'flex flex-col h-full bg-[var(--color-background)] overflow-
|
|
2
|
+
baseStyle: 'group flex flex-col h-full bg-[var(--color-background)] overflow-visible transition-colors duration-200 relative',
|
|
3
3
|
variants: {
|
|
4
4
|
sidebar: 'relative',
|
|
5
|
-
floating: '
|
|
6
|
-
inset: '
|
|
5
|
+
floating: 'relative m-3 h-[calc(100%-1.5rem)] rounded-xl border border-[var(--color-border)] bg-[color-mix(in_oklab,var(--color-background)_85%,transparent)] backdrop-blur-md shadow-[0_8px_30px_rgba(0,0,0,0.03),inset_0_1px_0_rgba(255,255,255,0.05)]',
|
|
6
|
+
inset: 'relative m-3 h-[calc(100%-1.5rem)] rounded-xl bg-[var(--color-background-secondary)] border border-[var(--color-border)] shadow-sm',
|
|
7
7
|
},
|
|
8
8
|
sides: {
|
|
9
9
|
left: 'border-r border-[var(--color-border)]',
|
|
10
10
|
right: 'border-l border-[var(--color-border)]',
|
|
11
11
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
mobileOverlay: 'fixed inset-0 z-50 flex flex-col bg-[var(--color-background)]',
|
|
15
|
-
mobileBackdrop: 'fixed inset-0 z-40 bg-black/50 backdrop-blur-sm',
|
|
12
|
+
mobileOverlay: 'fixed inset-y-0 z-50 flex flex-col bg-[var(--color-background)] shadow-2xl max-w-[85vw] border-[var(--color-border)]',
|
|
13
|
+
mobileBackdrop: 'fixed inset-0 z-40 bg-black/40 backdrop-blur-sm',
|
|
16
14
|
};
|
|
17
15
|
|
|
18
16
|
export { sidebarTheme };
|
|
@@ -2,6 +2,7 @@ import { ReactNode, HTMLAttributes } from 'react';
|
|
|
2
2
|
export type SidebarSide = 'left' | 'right';
|
|
3
3
|
export type SidebarVariant = 'sidebar' | 'floating' | 'inset';
|
|
4
4
|
export type SidebarUserStatus = 'online' | 'offline' | 'away' | 'busy';
|
|
5
|
+
export type SidebarDensity = 'comfortable' | 'compact';
|
|
5
6
|
export interface SidebarContextValue {
|
|
6
7
|
collapsed: boolean;
|
|
7
8
|
setCollapsed: (collapsed: boolean) => void;
|
|
@@ -12,6 +13,7 @@ export interface SidebarContextValue {
|
|
|
12
13
|
};
|
|
13
14
|
side: SidebarSide;
|
|
14
15
|
variant: SidebarVariant;
|
|
16
|
+
density: SidebarDensity;
|
|
15
17
|
/** Whether the sidebar is currently showing as mobile overlay */
|
|
16
18
|
isMobileOpen: boolean;
|
|
17
19
|
setMobileOpen: (open: boolean) => void;
|
|
@@ -29,6 +31,11 @@ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
29
31
|
* @default "sidebar"
|
|
30
32
|
*/
|
|
31
33
|
variant?: SidebarVariant;
|
|
34
|
+
/**
|
|
35
|
+
* Navigation density
|
|
36
|
+
* @default "comfortable"
|
|
37
|
+
*/
|
|
38
|
+
density?: SidebarDensity;
|
|
32
39
|
/**
|
|
33
40
|
* Whether the sidebar is collapsed by default
|
|
34
41
|
* @default false
|
|
@@ -103,6 +110,10 @@ export interface SidebarNavProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
103
110
|
* @default false
|
|
104
111
|
*/
|
|
105
112
|
accordion?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Navigation density. Inherits from Sidebar when omitted.
|
|
115
|
+
*/
|
|
116
|
+
density?: SidebarDensity;
|
|
106
117
|
children: ReactNode;
|
|
107
118
|
}
|
|
108
119
|
export interface SidebarNavGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -157,6 +168,12 @@ export interface SidebarUserProfileProps extends React.HTMLAttributes<HTMLDivEle
|
|
|
157
168
|
* Content to render when the profile is clicked (e.g. dropdown menu)
|
|
158
169
|
*/
|
|
159
170
|
dropdownContent?: ReactNode;
|
|
171
|
+
/**
|
|
172
|
+
* Custom content for the expandable region, replacing the default name/email block.
|
|
173
|
+
* The avatar and all collapse choreography (reveal animation, tooltip, dropdown
|
|
174
|
+
* placement) are preserved — only the text area is overridden. Copy-paste friendly.
|
|
175
|
+
*/
|
|
176
|
+
children?: ReactNode;
|
|
160
177
|
}
|
|
161
178
|
export interface SidebarLogoProps extends HTMLAttributes<HTMLDivElement> {
|
|
162
179
|
/** Always-visible icon/mark (shown in both expanded and collapsed states) */
|
|
@@ -164,6 +181,22 @@ export interface SidebarLogoProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
164
181
|
/** Text shown only when expanded — animates in/out automatically */
|
|
165
182
|
text?: ReactNode;
|
|
166
183
|
}
|
|
184
|
+
export interface SidebarProviderProps {
|
|
185
|
+
children: React.ReactNode;
|
|
186
|
+
defaultCollapsed?: boolean;
|
|
187
|
+
collapsed?: boolean;
|
|
188
|
+
onCollapsedChange?: (collapsed: boolean) => void;
|
|
189
|
+
mobileBreakpoint?: number;
|
|
190
|
+
mobileOpen?: boolean;
|
|
191
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
192
|
+
width?: {
|
|
193
|
+
expanded?: number;
|
|
194
|
+
collapsed?: number;
|
|
195
|
+
};
|
|
196
|
+
side?: SidebarSide;
|
|
197
|
+
variant?: SidebarVariant;
|
|
198
|
+
density?: SidebarDensity;
|
|
199
|
+
}
|
|
167
200
|
export interface SidebarTheme {
|
|
168
201
|
baseStyle: string;
|
|
169
202
|
variants: {
|
|
@@ -172,8 +205,6 @@ export interface SidebarTheme {
|
|
|
172
205
|
sides: {
|
|
173
206
|
[K in SidebarSide]: string;
|
|
174
207
|
};
|
|
175
|
-
collapsed: string;
|
|
176
|
-
expanded: string;
|
|
177
208
|
mobileOverlay: string;
|
|
178
209
|
mobileBackdrop: string;
|
|
179
210
|
}
|
|
@@ -185,8 +216,6 @@ export interface SidebarThemeOverrides {
|
|
|
185
216
|
sides?: Partial<{
|
|
186
217
|
[K in SidebarSide]: string;
|
|
187
218
|
}>;
|
|
188
|
-
collapsed?: string;
|
|
189
|
-
expanded?: string;
|
|
190
219
|
mobileOverlay?: string;
|
|
191
220
|
mobileBackdrop?: string;
|
|
192
221
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
-
import {
|
|
4
|
+
import { useSidebarCollapsed, useSidebarDensity } from './SidebarContext.js';
|
|
5
|
+
import { ScrollArea } from '../ScrollArea/ScrollArea.js';
|
|
6
|
+
import '../ScrollArea/smooth/SmoothScrollEngine.js';
|
|
5
7
|
|
|
6
8
|
const SidebarContent = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const isCollapsedView = collapsed && !isMobile;
|
|
11
|
-
return (jsx("div", { ref: ref, className: cn("flex-1 overflow-y-auto overflow-x-hidden py-3 transition-all duration-200", isCollapsedView ? "px-2" : "px-2.5", className), ...props, children: children }));
|
|
9
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
10
|
+
const density = useSidebarDensity();
|
|
11
|
+
return (jsx(ScrollArea, { ref: ref, type: "none", size: "thin", className: "flex-1 min-h-0", ...props, children: jsx("div", { className: cn("py-3 transition-[padding] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", density === "compact" && "py-2", isCollapsedView ? "px-2" : "px-2.5", className), children: children }) }));
|
|
12
12
|
});
|
|
13
13
|
SidebarContent.displayName = "SidebarContent";
|
|
14
14
|
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { SidebarContextValue } from './Sidebar.types';
|
|
2
|
+
import { SidebarContextValue, SidebarDensity, SidebarProviderProps } from './Sidebar.types';
|
|
3
3
|
export declare const useSidebarContext: () => SidebarContextValue | null;
|
|
4
|
+
export declare const useSidebarDensity: () => SidebarDensity;
|
|
5
|
+
export declare const SidebarDensityProvider: React.FC<{
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
density?: SidebarDensity;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Derived collapsed-view state, shared by all Sidebar sub-components.
|
|
11
|
+
* Returns falsy defaults outside a Sidebar so it can be called unconditionally.
|
|
12
|
+
*/
|
|
13
|
+
export declare const useSidebarCollapsed: () => {
|
|
14
|
+
collapsed: boolean;
|
|
15
|
+
isMobile: boolean;
|
|
16
|
+
isMobileOpen: boolean;
|
|
17
|
+
/** Collapsed desktop rail — true only when collapsed AND not the mobile overlay */
|
|
18
|
+
isCollapsedView: boolean;
|
|
19
|
+
};
|
|
4
20
|
/** Accordion nav context: allows SidebarNav to coordinate which submenu is open */
|
|
5
21
|
interface AccordionNavContextValue {
|
|
6
22
|
openId: string | null;
|
|
@@ -12,20 +28,5 @@ export declare const AccordionNavProvider: React.FC<{
|
|
|
12
28
|
children: React.ReactNode;
|
|
13
29
|
accordion?: boolean;
|
|
14
30
|
}>;
|
|
15
|
-
interface SidebarProviderProps {
|
|
16
|
-
children: React.ReactNode;
|
|
17
|
-
defaultCollapsed?: boolean;
|
|
18
|
-
collapsed?: boolean;
|
|
19
|
-
onCollapsedChange?: (collapsed: boolean) => void;
|
|
20
|
-
mobileBreakpoint?: number;
|
|
21
|
-
mobileOpen?: boolean;
|
|
22
|
-
onMobileOpenChange?: (open: boolean) => void;
|
|
23
|
-
width?: {
|
|
24
|
-
expanded?: number;
|
|
25
|
-
collapsed?: number;
|
|
26
|
-
};
|
|
27
|
-
side?: 'left' | 'right';
|
|
28
|
-
variant?: 'sidebar' | 'floating' | 'inset';
|
|
29
|
-
}
|
|
30
31
|
export declare const SidebarProvider: React.FC<SidebarProviderProps>;
|
|
31
32
|
export {};
|
|
@@ -1,12 +1,42 @@
|
|
|
1
1
|
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
|
|
3
|
+
import { useMediaQuery } from '../../../hooks/useMediaQuery.js';
|
|
3
4
|
|
|
4
5
|
const SidebarContext = createContext(null);
|
|
6
|
+
const SidebarDensityContext = createContext(null);
|
|
5
7
|
const useSidebarContext = () => {
|
|
6
8
|
const context = useContext(SidebarContext);
|
|
7
9
|
// Return null if not in sidebar context (allows NavItem to work elsewhere)
|
|
8
10
|
return context;
|
|
9
11
|
};
|
|
12
|
+
const useSidebarDensity = () => {
|
|
13
|
+
var _a;
|
|
14
|
+
const navDensity = useContext(SidebarDensityContext);
|
|
15
|
+
const sidebarContext = useContext(SidebarContext);
|
|
16
|
+
return (_a = navDensity !== null && navDensity !== void 0 ? navDensity : sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.density) !== null && _a !== void 0 ? _a : 'comfortable';
|
|
17
|
+
};
|
|
18
|
+
const SidebarDensityProvider = ({ children, density }) => {
|
|
19
|
+
if (!density)
|
|
20
|
+
return jsx(Fragment, { children: children });
|
|
21
|
+
return (jsx(SidebarDensityContext.Provider, { value: density, children: children }));
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Derived collapsed-view state, shared by all Sidebar sub-components.
|
|
25
|
+
* Returns falsy defaults outside a Sidebar so it can be called unconditionally.
|
|
26
|
+
*/
|
|
27
|
+
const useSidebarCollapsed = () => {
|
|
28
|
+
var _a, _b, _c;
|
|
29
|
+
const context = useContext(SidebarContext);
|
|
30
|
+
const collapsed = (_a = context === null || context === void 0 ? void 0 : context.collapsed) !== null && _a !== void 0 ? _a : false;
|
|
31
|
+
const isMobile = (_b = context === null || context === void 0 ? void 0 : context.isMobile) !== null && _b !== void 0 ? _b : false;
|
|
32
|
+
return {
|
|
33
|
+
collapsed,
|
|
34
|
+
isMobile,
|
|
35
|
+
isMobileOpen: (_c = context === null || context === void 0 ? void 0 : context.isMobileOpen) !== null && _c !== void 0 ? _c : false,
|
|
36
|
+
/** Collapsed desktop rail — true only when collapsed AND not the mobile overlay */
|
|
37
|
+
isCollapsedView: collapsed && !isMobile,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
10
40
|
const AccordionNavContext = createContext(null);
|
|
11
41
|
const useAccordionNavContext = () => useContext(AccordionNavContext);
|
|
12
42
|
const AccordionNavProvider = ({ children, accordion = false }) => {
|
|
@@ -22,26 +52,18 @@ const AccordionNavProvider = ({ children, accordion = false }) => {
|
|
|
22
52
|
return jsx(Fragment, { children: children });
|
|
23
53
|
return (jsx(AccordionNavContext.Provider, { value: value, children: children }));
|
|
24
54
|
};
|
|
25
|
-
const SidebarProvider = ({ children, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileBreakpoint = 1024, mobileOpen: controlledMobileOpen, onMobileOpenChange, width = {}, side = 'left', variant = 'sidebar', }) => {
|
|
55
|
+
const SidebarProvider = ({ children, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileBreakpoint = 1024, mobileOpen: controlledMobileOpen, onMobileOpenChange, width = {}, side = 'left', variant = 'sidebar', density = 'comfortable', }) => {
|
|
26
56
|
const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
|
|
27
57
|
const [internalMobileOpen, setInternalMobileOpen] = useState(false);
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
// Detect mobile breakpoint via the shared (SSR-safe) media-query hook
|
|
59
|
+
const isMobile = useMediaQuery(`(max-width: ${mobileBreakpoint - 1}px)`);
|
|
60
|
+
// Close the mobile drawer when leaving the mobile breakpoint
|
|
30
61
|
useEffect(() => {
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!e.matches) {
|
|
37
|
-
setInternalMobileOpen(false);
|
|
38
|
-
onMobileOpenChange === null || onMobileOpenChange === void 0 ? void 0 : onMobileOpenChange(false);
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
handler(mql);
|
|
42
|
-
mql.addEventListener('change', handler);
|
|
43
|
-
return () => mql.removeEventListener('change', handler);
|
|
44
|
-
}, [mobileBreakpoint, onMobileOpenChange]);
|
|
62
|
+
if (!isMobile) {
|
|
63
|
+
setInternalMobileOpen(false);
|
|
64
|
+
onMobileOpenChange === null || onMobileOpenChange === void 0 ? void 0 : onMobileOpenChange(false);
|
|
65
|
+
}
|
|
66
|
+
}, [isMobile, onMobileOpenChange]);
|
|
45
67
|
// Collapsed state
|
|
46
68
|
const isCollapsedControlled = controlledCollapsed !== undefined;
|
|
47
69
|
const collapsed = isCollapsedControlled ? controlledCollapsed : internalCollapsed;
|
|
@@ -80,11 +102,12 @@ const SidebarProvider = ({ children, defaultCollapsed = false, collapsed: contro
|
|
|
80
102
|
},
|
|
81
103
|
side,
|
|
82
104
|
variant,
|
|
105
|
+
density,
|
|
83
106
|
isMobileOpen,
|
|
84
107
|
setMobileOpen,
|
|
85
108
|
isMobile,
|
|
86
|
-
}), [collapsed, setCollapsed, toggleCollapsed, width.expanded, width.collapsed, side, variant, isMobileOpen, setMobileOpen, isMobile]);
|
|
109
|
+
}), [collapsed, setCollapsed, toggleCollapsed, width.expanded, width.collapsed, side, variant, density, isMobileOpen, setMobileOpen, isMobile]);
|
|
87
110
|
return (jsx(SidebarContext.Provider, { value: contextValue, children: children }));
|
|
88
111
|
};
|
|
89
112
|
|
|
90
|
-
export { AccordionNavProvider, SidebarProvider, useAccordionNavContext, useSidebarContext };
|
|
113
|
+
export { AccordionNavProvider, SidebarDensityProvider, SidebarProvider, useAccordionNavContext, useSidebarCollapsed, useSidebarContext, useSidebarDensity };
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
-
import {
|
|
4
|
+
import { useSidebarCollapsed, useSidebarDensity } from './SidebarContext.js';
|
|
5
5
|
|
|
6
6
|
const SidebarFooter = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const isCollapsedView = collapsed && !isMobile;
|
|
11
|
-
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-t border-[var(--color-border)] transition-all duration-200", isCollapsedView ? "p-2 flex justify-center" : "p-3", className), ...props, children: children }));
|
|
7
|
+
useSidebarCollapsed();
|
|
8
|
+
useSidebarDensity();
|
|
9
|
+
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-t border-[var(--color-border)] p-2", className), ...props, children: children }));
|
|
12
10
|
});
|
|
13
11
|
SidebarFooter.displayName = "SidebarFooter";
|
|
14
12
|
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import { jsx, jsxs
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
|
-
import { useSidebarContext } from './SidebarContext.js';
|
|
5
|
+
import { useSidebarContext, useSidebarCollapsed } from './SidebarContext.js';
|
|
6
6
|
import { SidebarToggle } from './SidebarToggle.js';
|
|
7
7
|
import { Button } from '../Button/Button.js';
|
|
8
8
|
|
|
9
9
|
const SidebarHeader = React__default.forwardRef(({ logo, showToggle = true, customToggle, children, className, ...props }, ref) => {
|
|
10
10
|
const sidebarContext = useSidebarContext();
|
|
11
|
-
const
|
|
12
|
-
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
13
|
-
const isMobileOpen = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobileOpen) || false;
|
|
11
|
+
const { isMobile, isMobileOpen, isCollapsedView: isCollapsedDesktop } = useSidebarCollapsed();
|
|
14
12
|
// If children are provided, use them instead of default layout
|
|
15
13
|
if (children) {
|
|
16
14
|
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-b border-[var(--color-border)]", className), ...props, children: children }));
|
|
17
15
|
}
|
|
18
|
-
|
|
19
|
-
return (jsx("div", { ref: ref, className: cn("flex items-center flex-shrink-0 h-14 border-b border-[var(--color-border)] overflow-hidden", isCollapsedDesktop ? "justify-center px-2" : "justify-between px-3", className), ...props, children: isCollapsedDesktop ? (
|
|
20
|
-
/* Collapsed: only show the toggle (expand) button centered */
|
|
21
|
-
showToggle && (customToggle || jsx(SidebarToggle, {}))) : (jsxs(Fragment, { children: [jsx("div", { className: "flex items-center min-w-0 flex-1", children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "ghost", size: "sm", className: "size-7 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-4" }) })), !isMobile && showToggle && (jsx("div", { className: "flex-shrink-0", children: customToggle || jsx(SidebarToggle, {}) }))] })) }));
|
|
16
|
+
return (jsxs("div", { ref: ref, className: cn("flex items-center flex-shrink-0 h-14 border-b border-[var(--color-border)] overflow-visible relative transition-[padding,background-color,border-color] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]", isCollapsedDesktop ? "px-4" : "px-3", className), ...props, children: [jsx("div", { className: cn("flex items-center min-w-0 flex-1"), children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "ghost", size: "sm", className: "size-7 p-0 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-4" }) })), !isMobile && showToggle && (customToggle || jsx(SidebarToggle, {}))] }));
|
|
22
17
|
});
|
|
23
18
|
SidebarHeader.displayName = "SidebarHeader";
|
|
24
19
|
|
|
@@ -2,15 +2,11 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
5
|
-
import {
|
|
5
|
+
import { useSidebarCollapsed } from './SidebarContext.js';
|
|
6
6
|
|
|
7
7
|
const SidebarLogo = React__default.forwardRef(({ icon, text, className, ...props }, ref) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const collapsed = (_a = context === null || context === void 0 ? void 0 : context.collapsed) !== null && _a !== void 0 ? _a : false;
|
|
11
|
-
const isMobile = (_b = context === null || context === void 0 ? void 0 : context.isMobile) !== null && _b !== void 0 ? _b : false;
|
|
12
|
-
const isCollapsedView = collapsed && !isMobile;
|
|
13
|
-
return (jsxs("div", { ref: ref, className: cn("flex items-center gap-2 overflow-hidden", className), ...props, children: [jsx("div", { className: "flex-shrink-0", children: icon }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && text && (jsx(motion.div, { className: "overflow-hidden whitespace-nowrap", initial: { opacity: 0, x: -8 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -8 }, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] }, children: typeof text === "string" ? (jsx("span", { className: "text-lg font-bold text-[var(--color-text-primary)]", children: text })) : (text) }, "logo-text")) })] }));
|
|
8
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
9
|
+
return (jsxs("div", { ref: ref, className: cn("flex items-center gap-2 overflow-hidden", className), ...props, children: [jsx("div", { className: "flex-shrink-0", children: icon }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && text && (jsx(motion.div, { className: "overflow-hidden whitespace-nowrap", initial: { opacity: 0, x: -8 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -8 }, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] }, children: typeof text === "string" ? (jsx("span", { className: "text-base font-semibold tracking-tight text-[var(--color-text-primary)]", children: text })) : (text) }, "logo-text")) })] }));
|
|
14
10
|
});
|
|
15
11
|
SidebarLogo.displayName = "SidebarLogo";
|
|
16
12
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
-
import { AccordionNavProvider } from './SidebarContext.js';
|
|
4
|
+
import { useSidebarDensity, SidebarDensityProvider, AccordionNavProvider } from './SidebarContext.js';
|
|
5
5
|
|
|
6
|
-
const SidebarNav = React__default.forwardRef(({ accordion = false, children, className, ...props }, ref) => {
|
|
7
|
-
|
|
6
|
+
const SidebarNav = React__default.forwardRef(({ accordion = false, density, children, className, ...props }, ref) => {
|
|
7
|
+
const resolvedDensity = density !== null && density !== void 0 ? density : useSidebarDensity();
|
|
8
|
+
return (jsx("nav", { ref: ref, className: cn('flex-1', resolvedDensity === 'compact' ? 'space-y-0.5' : 'space-y-1', className), "aria-label": "Sidebar", "data-density": resolvedDensity, ...props, children: jsx(SidebarDensityProvider, { density: density, children: jsx(AccordionNavProvider, { accordion: accordion, children: children }) }) }));
|
|
8
9
|
});
|
|
9
10
|
SidebarNav.displayName = 'SidebarNav';
|
|
10
11
|
|
|
@@ -2,15 +2,12 @@ import { jsxs, jsx } from 'react/jsx-runtime';
|
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
5
|
-
import {
|
|
5
|
+
import { useSidebarCollapsed } from './SidebarContext.js';
|
|
6
6
|
|
|
7
7
|
const SidebarNavGroup = React__default.forwardRef(({ title, showTitleWhenCollapsed = false, children, className, ...props }, ref) => {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
const isCollapsedView = collapsed && !isMobile;
|
|
12
|
-
const showTitle = title && (!isCollapsedView || showTitleWhenCollapsed);
|
|
13
|
-
return (jsxs("div", { ref: ref, className: cn("space-y-1", className), ...props, children: [jsx(AnimatePresence, { initial: false, children: showTitle && (jsx(motion.div, { initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, transition: { duration: 0.15, ease: "easeInOut" }, className: "overflow-hidden", children: jsx("div", { className: "px-2 py-1", children: jsx("h3", { className: "text-xs font-semibold text-[var(--color-text-muted)] uppercase tracking-wider", children: title }) }) }, "title")) }), jsx(AnimatePresence, { initial: false, children: isCollapsedView && !showTitleWhenCollapsed && (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.15 }, className: "h-px bg-[var(--color-border)] mx-2 my-1" }, "sep")) }), jsx("div", { className: "space-y-1", children: children })] }));
|
|
8
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
9
|
+
const showTitleText = title && (!isCollapsedView || showTitleWhenCollapsed);
|
|
10
|
+
return (jsxs("div", { ref: ref, className: cn("space-y-1", className), ...props, children: [title && (jsx("div", { className: "h-6 flex items-center relative overflow-hidden", children: jsx(AnimatePresence, { initial: false, children: showTitleText ? (jsx(motion.div, { initial: { opacity: 0, x: -4 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -4 }, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, className: "w-full px-2 py-1 absolute left-0", children: jsx("h3", { className: "text-[10px] font-bold text-[var(--color-text-muted)]/70 uppercase", children: title }) }, "title-text")) : (jsx(motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, className: "w-full px-2 absolute left-0", children: jsx("div", { className: "h-px bg-[var(--color-border)] w-full" }) }, "title-separator")) }) })), jsx("div", { className: "space-y-1", children: children })] }));
|
|
14
11
|
});
|
|
15
12
|
SidebarNavGroup.displayName = "SidebarNavGroup";
|
|
16
13
|
|
|
@@ -7,6 +7,7 @@ import { useSidebarContext } from './SidebarContext.js';
|
|
|
7
7
|
import { Button } from '../Button/Button.js';
|
|
8
8
|
|
|
9
9
|
const SidebarToggle = React__default.forwardRef(({ expandIcon, collapseIcon, className, onClick, ...props }, ref) => {
|
|
10
|
+
var _a;
|
|
10
11
|
const sidebarContext = useSidebarContext();
|
|
11
12
|
if (!sidebarContext) {
|
|
12
13
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -20,7 +21,11 @@ const SidebarToggle = React__default.forwardRef(({ expandIcon, collapseIcon, cla
|
|
|
20
21
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
21
22
|
};
|
|
22
23
|
const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, color: _color, ...buttonProps } = props;
|
|
23
|
-
|
|
24
|
+
const side = (_a = sidebarContext.side) !== null && _a !== void 0 ? _a : "left";
|
|
25
|
+
const positionClasses = side === "left"
|
|
26
|
+
? "absolute right-0 translate-x-1/2 top-4 z-50"
|
|
27
|
+
: "absolute left-0 -translate-x-1/2 top-4 z-50";
|
|
28
|
+
return (jsx(Button, { ref: ref, iconOnly: true, variant: "ghost", className: cn("size-6 p-0 rounded-full border border-[var(--color-border)] bg-[var(--color-background)]/50 backdrop-blur-sm text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:shadow-md hover:bg-[var(--color-surface-hover)] opacity-100 transition-all duration-400", positionClasses, className), onClick: handleClick, "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar", title: collapsed ? "Expand sidebar" : "Collapse sidebar", ...buttonProps, children: jsx(motion.div, { initial: false, animate: { rotate: collapsed ? 180 : 0 }, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, className: "flex items-center justify-center", children: jsx(ChevronLeftIcon, { className: "size-3", strokeWidth: 2.5 }) }) }));
|
|
24
29
|
});
|
|
25
30
|
SidebarToggle.displayName = "SidebarToggle";
|
|
26
31
|
|
|
@@ -1,30 +1,46 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
|
+
import { ChevronUpDownIcon } from '@heroicons/react/20/solid';
|
|
3
4
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
5
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
6
|
+
import { useSidebarCollapsed } from './SidebarContext.js';
|
|
6
7
|
import { Avatar } from '../Avatar/Avatar.js';
|
|
7
8
|
import { Tooltip } from '../Tooltip/Tooltip.js';
|
|
8
9
|
import '../Tooltip/Tooltip.animations.js';
|
|
10
|
+
import { Dropdown } from '../Dropdown/Dropdown.js';
|
|
11
|
+
import '../Dropdown/DropdownTrigger.js';
|
|
12
|
+
import '../Dropdown/DropdownMenu.js';
|
|
13
|
+
import '../Dropdown/DropdownItem.js';
|
|
14
|
+
import '../Dropdown/DropdownSection.js';
|
|
15
|
+
import '../Dropdown/DropdownSeparator.js';
|
|
16
|
+
import '../Dropdown/Dropdown.theme.js';
|
|
9
17
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const SidebarUserProfile = React__default.forwardRef(({ name, email, avatar, status, dropdownContent, className, onClick, ...props }, ref) => {
|
|
17
|
-
const sidebarContext = useSidebarContext();
|
|
18
|
-
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
19
|
-
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
20
|
-
const isCollapsedView = collapsed && !isMobile;
|
|
21
|
-
const avatarElement = (jsxs("div", { className: "relative flex-shrink-0", children: [jsx(Avatar, { src: avatar, name: name, size: "sm" }), status && (jsx("span", { className: cn("absolute -bottom-0.5 -right-0.5 size-3 rounded-full ring-2 ring-[var(--color-background)]", statusColors[status]), "aria-label": `Status: ${status}` }))] }));
|
|
22
|
-
const content = (jsxs("div", { ref: ref, className: cn("flex items-center gap-3 rounded-[var(--radius-base)] cursor-pointer transition-colors duration-150 hover:bg-[var(--color-background-secondary)]", isCollapsedView ? "justify-center p-2" : "px-3 py-2", className), role: "button", tabIndex: 0, onClick: onClick, onKeyDown: (e) => {
|
|
18
|
+
const SidebarUserProfile = React__default.forwardRef(({ name, email, avatar, status, dropdownContent, children, className, onClick, ...props }, ref) => {
|
|
19
|
+
const { isCollapsedView } = useSidebarCollapsed();
|
|
20
|
+
// Strip drag/animation DOM handlers that collide with motion's prop types
|
|
21
|
+
const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, ...motionSafeProps } = props;
|
|
22
|
+
const avatarElement = (jsx(Avatar, { src: avatar, name: name, size: isCollapsedView ? "sm" : "md", status: status !== null && status !== void 0 ? status : "none" }));
|
|
23
|
+
const content = (jsxs(motion.div, { ref: ref, className: cn("flex w-full items-center rounded-[var(--radius-base)] cursor-pointer border border-transparent transition-[padding,gap,background-color,border-color,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] ", isCollapsedView ? "justify-center gap-0 px-1 py-2" : "gap-2 ", className), role: "button", tabIndex: 0, onClick: onClick, onKeyDown: (e) => {
|
|
23
24
|
if ((e.key === "Enter" || e.key === " ") && onClick) {
|
|
24
25
|
e.preventDefault();
|
|
25
26
|
e.currentTarget.click();
|
|
26
27
|
}
|
|
27
|
-
},
|
|
28
|
+
},
|
|
29
|
+
// whileHover={{ scale: 1.01, x: isCollapsedView ? 0 : 2 }}
|
|
30
|
+
whileTap: { scale: 0.98 }, transition: { type: "spring", stiffness: 400, damping: 25 }, ...motionSafeProps, children: [jsx(motion.div
|
|
31
|
+
// whileHover={{ scale: 1.05 }}
|
|
32
|
+
// transition={{ type: "spring", stiffness: 400, damping: 20 }}
|
|
33
|
+
, {
|
|
34
|
+
// whileHover={{ scale: 1.05 }}
|
|
35
|
+
// transition={{ type: "spring", stiffness: 400, damping: 20 }}
|
|
36
|
+
className: "flex-shrink-0", children: avatarElement }), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && (jsxs(motion.div, { className: "flex flex-row items-center gap-2 flex-1 min-w-0 overflow-hidden whitespace-nowrap", initial: { opacity: 0, x: -4 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -4 }, transition: {
|
|
37
|
+
opacity: { duration: 0.15, ease: [0.4, 0, 0.2, 1] },
|
|
38
|
+
x: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }
|
|
39
|
+
}, children: [children !== null && children !== void 0 ? children : (jsxs("div", { className: "flex-1 min-w-0", children: [jsx("div", { className: "text-sm font-medium text-[var(--color-text-primary)] truncate", children: name }), email && (jsx("div", { className: "text-xs text-[var(--color-text-secondary)]/80 truncate", children: email }))] })), dropdownContent && (jsx(ChevronUpDownIcon, { className: "size-4 flex-shrink-0 text-[var(--color-text-muted)]" }))] }, "profile-text")) })] }));
|
|
40
|
+
// When dropdownContent is provided, the profile acts as a menu trigger.
|
|
41
|
+
if (dropdownContent) {
|
|
42
|
+
return (jsxs(Dropdown, { placement: "right-end", offset: 8, children: [jsx(Dropdown.Trigger, { children: content }), jsx(Dropdown.Menu, { "aria-label": `${name} menu`, children: dropdownContent })] }));
|
|
43
|
+
}
|
|
28
44
|
return (jsx(Tooltip, { content: name, placement: "right", disabled: !isCollapsedView, triggerClassName: "block", children: content }));
|
|
29
45
|
});
|
|
30
46
|
SidebarUserProfile.displayName = "SidebarUserProfile";
|