@flikk/ui 1.0.0-beta.13 → 1.0.0-beta.14
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/PromptInput/PromptInput.js +1 -1
- package/dist/components/ai/PromptInput/VoiceRecorder.js +1 -1
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +1 -1
- package/dist/components/charts/FunnelChart/FunnelChart.js +2 -2
- package/dist/components/core/Badge/Badge.js +3 -3
- package/dist/components/core/Badge/Badge.theme.js +5 -5
- package/dist/components/core/Badge/Badge.types.d.ts +1 -1
- package/dist/components/core/Button/Button.ripple.js +1 -1
- package/dist/components/core/Button/Button.theme.d.ts +1 -1
- package/dist/components/core/Button/Button.theme.js +3 -3
- package/dist/components/core/Button/Button.types.d.ts +1 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +2 -2
- package/dist/components/core/Drawer/DrawerHeader.js +1 -1
- package/dist/components/core/Link/Link.js +1 -1
- package/dist/components/core/Modal/ModalHeader.js +1 -1
- package/dist/components/core/NavItem/NavItem.js +100 -52
- package/dist/components/core/NavItem/NavItem.theme.js +6 -3
- package/dist/components/core/NavItem/NavItem.types.d.ts +44 -30
- package/dist/components/core/NavItem/index.d.ts +1 -1
- package/dist/components/core/Pill/Pill.theme.js +1 -1
- package/dist/components/core/Sidebar/Sidebar.d.ts +11 -1
- package/dist/components/core/Sidebar/Sidebar.js +49 -16
- package/dist/components/core/Sidebar/Sidebar.theme.js +9 -7
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +57 -0
- package/dist/components/core/Sidebar/SidebarContent.js +3 -1
- package/dist/components/core/Sidebar/SidebarContext.d.ts +14 -0
- package/dist/components/core/Sidebar/SidebarContext.js +63 -11
- package/dist/components/core/Sidebar/SidebarFooter.js +3 -1
- package/dist/components/core/Sidebar/SidebarHeader.js +6 -2
- package/dist/components/core/Sidebar/SidebarMobileTrigger.d.ts +3 -0
- package/dist/components/core/Sidebar/SidebarMobileTrigger.js +28 -0
- package/dist/components/core/Sidebar/SidebarNav.js +3 -2
- package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -2
- package/dist/components/core/Sidebar/SidebarToggle.js +1 -1
- package/dist/components/core/Sidebar/SidebarUserProfile.d.ts +3 -0
- package/dist/components/core/Sidebar/SidebarUserProfile.js +35 -0
- package/dist/components/core/Sidebar/index.d.ts +3 -1
- package/dist/components/core/Tabs/Tabs.theme.d.ts +1 -1
- package/dist/components/core/Tabs/Tabs.theme.js +3 -3
- package/dist/components/core/index.js +2 -0
- package/dist/components/data-display/GanttChart/GanttToolbar.js +1 -1
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +10 -10
- package/dist/index.js +2 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/global.scss +1 -0
- package/src/styles/theme.css +48 -48
|
@@ -1,33 +1,35 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import React__default, { useMemo } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
-
import { motion } from 'motion/react';
|
|
4
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
5
5
|
import { sidebarTheme } from './Sidebar.theme.js';
|
|
6
6
|
import { useSidebarContext, SidebarProvider } from './SidebarContext.js';
|
|
7
|
+
import { SidebarHeader } from './SidebarHeader.js';
|
|
8
|
+
import { SidebarContent } from './SidebarContent.js';
|
|
9
|
+
import { SidebarFooter } from './SidebarFooter.js';
|
|
10
|
+
import { SidebarNav } from './SidebarNav.js';
|
|
11
|
+
import { SidebarNavGroup } from './SidebarNavGroup.js';
|
|
12
|
+
import { SidebarSubmenu } from './SidebarSubmenu.js';
|
|
13
|
+
import { SidebarToggle } from './SidebarToggle.js';
|
|
14
|
+
import { SidebarMobileTrigger } from './SidebarMobileTrigger.js';
|
|
15
|
+
import { SidebarUserProfile } from './SidebarUserProfile.js';
|
|
7
16
|
|
|
8
17
|
const SIDEBAR_TRANSITION = {
|
|
9
18
|
type: "spring",
|
|
10
19
|
stiffness: 300,
|
|
11
20
|
damping: 30,
|
|
12
21
|
};
|
|
13
|
-
const
|
|
22
|
+
const SidebarDesktop = React__default.forwardRef(({ side = "left", variant = "sidebar", width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
14
23
|
var _a, _b, _c;
|
|
15
24
|
const context = useSidebarContext();
|
|
16
25
|
const collapsed = (_a = context === null || context === void 0 ? void 0 : context.collapsed) !== null && _a !== void 0 ? _a : false;
|
|
17
|
-
// Default widths
|
|
18
26
|
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;
|
|
19
|
-
const collapsedWidth = (width === null || width === void 0 ? void 0 : width.collapsed) || ((_c = context === null || context === void 0 ? void 0 : context.width) === null || _c === void 0 ? void 0 : _c.collapsed) ||
|
|
27
|
+
const collapsedWidth = (width === null || width === void 0 ? void 0 : width.collapsed) || ((_c = context === null || context === void 0 ? void 0 : context.width) === null || _c === void 0 ? void 0 : _c.collapsed) || 64;
|
|
20
28
|
const theme = useMemo(() => ({
|
|
21
29
|
...sidebarTheme,
|
|
22
30
|
...customTheme,
|
|
23
|
-
variants: {
|
|
24
|
-
|
|
25
|
-
...customTheme.variants,
|
|
26
|
-
},
|
|
27
|
-
sides: {
|
|
28
|
-
...sidebarTheme.sides,
|
|
29
|
-
...customTheme.sides,
|
|
30
|
-
},
|
|
31
|
+
variants: { ...sidebarTheme.variants, ...customTheme.variants },
|
|
32
|
+
sides: { ...sidebarTheme.sides, ...customTheme.sides },
|
|
31
33
|
}), [customTheme]);
|
|
32
34
|
const sidebarClasses = cn(theme.baseStyle, theme.variants[variant], theme.sides[side], className);
|
|
33
35
|
const animateValue = useMemo(() => ({
|
|
@@ -36,9 +38,40 @@ const SidebarContainer = React__default.forwardRef(({ side = "left", variant = "
|
|
|
36
38
|
}), [collapsed, collapsedWidth, expandedWidth]);
|
|
37
39
|
return (jsx(motion.div, { ref: ref, className: sidebarClasses, "data-side": side, "data-variant": variant, "data-collapsed": collapsed, initial: false, animate: animateValue, transition: SIDEBAR_TRANSITION, ...props, children: children }));
|
|
38
40
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
SidebarDesktop.displayName = "SidebarDesktop";
|
|
42
|
+
const SidebarMobile = ({ children, className, theme: customTheme = {} }) => {
|
|
43
|
+
const context = useSidebarContext();
|
|
44
|
+
if (!context)
|
|
45
|
+
return null;
|
|
46
|
+
const { isMobileOpen, setMobileOpen } = context;
|
|
47
|
+
const theme = useMemo(() => ({
|
|
48
|
+
...sidebarTheme,
|
|
49
|
+
...customTheme,
|
|
50
|
+
}), [customTheme]);
|
|
51
|
+
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, { className: cn(theme.mobileOverlay, className), initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 20 }, transition: { type: "spring", stiffness: 400, damping: 35 }, role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", children: children })] })) }));
|
|
52
|
+
};
|
|
53
|
+
const SidebarContainer = React__default.forwardRef((props, ref) => {
|
|
54
|
+
const context = useSidebarContext();
|
|
55
|
+
if (context === null || context === void 0 ? void 0 : context.isMobile) {
|
|
56
|
+
return (jsx(SidebarMobile, { className: props.className, theme: props.theme, children: props.children }));
|
|
57
|
+
}
|
|
58
|
+
return jsx(SidebarDesktop, { ref: ref, ...props });
|
|
59
|
+
});
|
|
60
|
+
SidebarContainer.displayName = "SidebarContainer";
|
|
61
|
+
const SidebarRoot = React__default.forwardRef(({ side = "left", variant = "sidebar", defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileBreakpoint, mobileOpen, onMobileOpenChange, width, children, className, theme: customTheme = {}, ...props }, ref) => {
|
|
62
|
+
return (jsx(SidebarProvider, { defaultCollapsed: defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange: onCollapsedChange, 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, width: width, ...props, children: children }) }));
|
|
63
|
+
});
|
|
64
|
+
SidebarRoot.displayName = "Sidebar";
|
|
65
|
+
const Sidebar = Object.assign(SidebarRoot, {
|
|
66
|
+
Header: SidebarHeader,
|
|
67
|
+
Content: SidebarContent,
|
|
68
|
+
Footer: SidebarFooter,
|
|
69
|
+
Nav: SidebarNav,
|
|
70
|
+
NavGroup: SidebarNavGroup,
|
|
71
|
+
Submenu: SidebarSubmenu,
|
|
72
|
+
Toggle: SidebarToggle,
|
|
73
|
+
MobileTrigger: SidebarMobileTrigger,
|
|
74
|
+
UserProfile: SidebarUserProfile,
|
|
41
75
|
});
|
|
42
|
-
Sidebar.displayName = "Sidebar";
|
|
43
76
|
|
|
44
77
|
export { Sidebar };
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
const sidebarTheme = {
|
|
2
|
-
baseStyle: 'flex flex-col h-full bg-
|
|
2
|
+
baseStyle: 'flex flex-col h-full bg-[var(--color-background)] overflow-hidden',
|
|
3
3
|
variants: {
|
|
4
4
|
sidebar: 'relative',
|
|
5
|
-
floating: 'rounded-[var(--radius-base)] shadow-lg border border-
|
|
6
|
-
inset: 'rounded-[var(--radius-base)] bg-
|
|
5
|
+
floating: 'rounded-[var(--radius-base)] shadow-lg border border-[var(--color-border)]',
|
|
6
|
+
inset: 'rounded-[var(--radius-base)] bg-[var(--color-background-secondary)] border border-[var(--color-border)]',
|
|
7
7
|
},
|
|
8
8
|
sides: {
|
|
9
|
-
left: 'border-r',
|
|
10
|
-
right: 'border-l',
|
|
9
|
+
left: 'border-r border-[var(--color-border)]',
|
|
10
|
+
right: 'border-l border-[var(--color-border)]',
|
|
11
11
|
},
|
|
12
|
-
collapsed: 'w-
|
|
13
|
-
expanded: 'w-[280px]',
|
|
12
|
+
collapsed: 'w-16',
|
|
13
|
+
expanded: 'w-[280px]',
|
|
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',
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
export { sidebarTheme };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
export type SidebarSide = 'left' | 'right';
|
|
3
3
|
export type SidebarVariant = 'sidebar' | 'floating' | 'inset';
|
|
4
|
+
export type SidebarUserStatus = 'online' | 'offline' | 'away' | 'busy';
|
|
4
5
|
export interface SidebarContextValue {
|
|
5
6
|
collapsed: boolean;
|
|
6
7
|
setCollapsed: (collapsed: boolean) => void;
|
|
@@ -11,6 +12,11 @@ export interface SidebarContextValue {
|
|
|
11
12
|
};
|
|
12
13
|
side: SidebarSide;
|
|
13
14
|
variant: SidebarVariant;
|
|
15
|
+
/** Whether the sidebar is currently showing as mobile overlay */
|
|
16
|
+
isMobileOpen: boolean;
|
|
17
|
+
setMobileOpen: (open: boolean) => void;
|
|
18
|
+
/** Whether we're below the mobile breakpoint */
|
|
19
|
+
isMobile: boolean;
|
|
14
20
|
}
|
|
15
21
|
export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
16
22
|
/**
|
|
@@ -43,6 +49,19 @@ export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
43
49
|
expanded?: number;
|
|
44
50
|
collapsed?: number;
|
|
45
51
|
};
|
|
52
|
+
/**
|
|
53
|
+
* Mobile breakpoint in pixels. Below this, sidebar becomes a full-screen overlay.
|
|
54
|
+
* @default 1024
|
|
55
|
+
*/
|
|
56
|
+
mobileBreakpoint?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Controlled mobile open state
|
|
59
|
+
*/
|
|
60
|
+
mobileOpen?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Callback when mobile open state changes
|
|
63
|
+
*/
|
|
64
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
46
65
|
/**
|
|
47
66
|
* Children components
|
|
48
67
|
*/
|
|
@@ -78,6 +97,12 @@ export interface SidebarFooterProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
78
97
|
children: ReactNode;
|
|
79
98
|
}
|
|
80
99
|
export interface SidebarNavProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
100
|
+
/**
|
|
101
|
+
* When true, only one submenu can be open at a time within this nav.
|
|
102
|
+
* Opening a new submenu auto-closes the previously open one.
|
|
103
|
+
* @default false
|
|
104
|
+
*/
|
|
105
|
+
accordion?: boolean;
|
|
81
106
|
children: ReactNode;
|
|
82
107
|
}
|
|
83
108
|
export interface SidebarNavGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
@@ -105,6 +130,34 @@ export interface SidebarToggleProps extends React.HTMLAttributes<HTMLButtonEleme
|
|
|
105
130
|
*/
|
|
106
131
|
collapseIcon?: ReactNode;
|
|
107
132
|
}
|
|
133
|
+
export interface SidebarMobileTriggerProps extends React.HTMLAttributes<HTMLButtonElement> {
|
|
134
|
+
/**
|
|
135
|
+
* Custom icon for the trigger
|
|
136
|
+
*/
|
|
137
|
+
icon?: ReactNode;
|
|
138
|
+
}
|
|
139
|
+
export interface SidebarUserProfileProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
140
|
+
/**
|
|
141
|
+
* User display name
|
|
142
|
+
*/
|
|
143
|
+
name: string;
|
|
144
|
+
/**
|
|
145
|
+
* User email
|
|
146
|
+
*/
|
|
147
|
+
email?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Avatar image URL
|
|
150
|
+
*/
|
|
151
|
+
avatar?: string;
|
|
152
|
+
/**
|
|
153
|
+
* Online status indicator
|
|
154
|
+
*/
|
|
155
|
+
status?: SidebarUserStatus;
|
|
156
|
+
/**
|
|
157
|
+
* Content to render when the profile is clicked (e.g. dropdown menu)
|
|
158
|
+
*/
|
|
159
|
+
dropdownContent?: ReactNode;
|
|
160
|
+
}
|
|
108
161
|
export interface SidebarTheme {
|
|
109
162
|
baseStyle: string;
|
|
110
163
|
variants: {
|
|
@@ -115,6 +168,8 @@ export interface SidebarTheme {
|
|
|
115
168
|
};
|
|
116
169
|
collapsed: string;
|
|
117
170
|
expanded: string;
|
|
171
|
+
mobileOverlay: string;
|
|
172
|
+
mobileBackdrop: string;
|
|
118
173
|
}
|
|
119
174
|
export interface SidebarThemeOverrides {
|
|
120
175
|
baseStyle?: string;
|
|
@@ -126,4 +181,6 @@ export interface SidebarThemeOverrides {
|
|
|
126
181
|
}>;
|
|
127
182
|
collapsed?: string;
|
|
128
183
|
expanded?: string;
|
|
184
|
+
mobileOverlay?: string;
|
|
185
|
+
mobileBackdrop?: string;
|
|
129
186
|
}
|
|
@@ -6,7 +6,9 @@ import { useSidebarContext } from './SidebarContext.js';
|
|
|
6
6
|
const SidebarContent = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
7
7
|
const sidebarContext = useSidebarContext();
|
|
8
8
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
9
|
-
|
|
9
|
+
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
10
|
+
const isCollapsedView = collapsed && !isMobile;
|
|
11
|
+
return (jsx("div", { ref: ref, className: cn("flex-1 overflow-y-auto overflow-x-hidden py-2 transition-all duration-200", isCollapsedView ? "px-2" : "px-3", className), ...props, children: children }));
|
|
10
12
|
});
|
|
11
13
|
SidebarContent.displayName = "SidebarContent";
|
|
12
14
|
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { SidebarContextValue } from './Sidebar.types';
|
|
3
3
|
export declare const useSidebarContext: () => SidebarContextValue | null;
|
|
4
|
+
/** Accordion nav context: allows SidebarNav to coordinate which submenu is open */
|
|
5
|
+
interface AccordionNavContextValue {
|
|
6
|
+
openId: string | null;
|
|
7
|
+
register: (id: string) => void;
|
|
8
|
+
unregister: (id: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const useAccordionNavContext: () => AccordionNavContextValue | null;
|
|
11
|
+
export declare const AccordionNavProvider: React.FC<{
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
accordion?: boolean;
|
|
14
|
+
}>;
|
|
4
15
|
interface SidebarProviderProps {
|
|
5
16
|
children: React.ReactNode;
|
|
6
17
|
defaultCollapsed?: boolean;
|
|
7
18
|
collapsed?: boolean;
|
|
8
19
|
onCollapsedChange?: (collapsed: boolean) => void;
|
|
20
|
+
mobileBreakpoint?: number;
|
|
21
|
+
mobileOpen?: boolean;
|
|
22
|
+
onMobileOpenChange?: (open: boolean) => void;
|
|
9
23
|
width?: {
|
|
10
24
|
expanded?: number;
|
|
11
25
|
collapsed?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { createContext, useContext, useState, useCallback, useMemo } from 'react';
|
|
1
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
|
|
3
3
|
|
|
4
4
|
const SidebarContext = createContext(null);
|
|
5
5
|
const useSidebarContext = () => {
|
|
@@ -7,32 +7,84 @@ const useSidebarContext = () => {
|
|
|
7
7
|
// Return null if not in sidebar context (allows NavItem to work elsewhere)
|
|
8
8
|
return context;
|
|
9
9
|
};
|
|
10
|
-
const
|
|
10
|
+
const AccordionNavContext = createContext(null);
|
|
11
|
+
const useAccordionNavContext = () => useContext(AccordionNavContext);
|
|
12
|
+
const AccordionNavProvider = ({ children, accordion = false }) => {
|
|
13
|
+
const [openId, setOpenId] = useState(null);
|
|
14
|
+
const register = useCallback((id) => {
|
|
15
|
+
setOpenId(id);
|
|
16
|
+
}, []);
|
|
17
|
+
const unregister = useCallback((id) => {
|
|
18
|
+
setOpenId(prev => (prev === id ? null : prev));
|
|
19
|
+
}, []);
|
|
20
|
+
const value = useMemo(() => ({ openId, register, unregister }), [openId, register, unregister]);
|
|
21
|
+
if (!accordion)
|
|
22
|
+
return jsx(Fragment, { children: children });
|
|
23
|
+
return (jsx(AccordionNavContext.Provider, { value: value, children: children }));
|
|
24
|
+
};
|
|
25
|
+
const SidebarProvider = ({ children, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, mobileBreakpoint = 1024, mobileOpen: controlledMobileOpen, onMobileOpenChange, width = {}, side = 'left', variant = 'sidebar', }) => {
|
|
11
26
|
const [internalCollapsed, setInternalCollapsed] = useState(defaultCollapsed);
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
27
|
+
const [internalMobileOpen, setInternalMobileOpen] = useState(false);
|
|
28
|
+
const [isMobile, setIsMobile] = useState(false);
|
|
29
|
+
// Detect mobile breakpoint
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (typeof window === 'undefined')
|
|
32
|
+
return;
|
|
33
|
+
const mql = window.matchMedia(`(max-width: ${mobileBreakpoint - 1}px)`);
|
|
34
|
+
const handler = (e) => {
|
|
35
|
+
setIsMobile(e.matches);
|
|
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]);
|
|
45
|
+
// Collapsed state
|
|
46
|
+
const isCollapsedControlled = controlledCollapsed !== undefined;
|
|
47
|
+
const collapsed = isCollapsedControlled ? controlledCollapsed : internalCollapsed;
|
|
15
48
|
const setCollapsed = useCallback((newCollapsed) => {
|
|
16
|
-
if (!
|
|
49
|
+
if (!isCollapsedControlled) {
|
|
17
50
|
setInternalCollapsed(newCollapsed);
|
|
18
51
|
}
|
|
19
52
|
onCollapsedChange === null || onCollapsedChange === void 0 ? void 0 : onCollapsedChange(newCollapsed);
|
|
20
|
-
}, [
|
|
53
|
+
}, [isCollapsedControlled, onCollapsedChange]);
|
|
21
54
|
const toggleCollapsed = useCallback(() => {
|
|
22
55
|
setCollapsed(!collapsed);
|
|
23
56
|
}, [collapsed, setCollapsed]);
|
|
57
|
+
// Mobile open state
|
|
58
|
+
const isMobileOpenControlled = controlledMobileOpen !== undefined;
|
|
59
|
+
const isMobileOpen = isMobileOpenControlled ? controlledMobileOpen : internalMobileOpen;
|
|
60
|
+
const setMobileOpen = useCallback((open) => {
|
|
61
|
+
if (!isMobileOpenControlled) {
|
|
62
|
+
setInternalMobileOpen(open);
|
|
63
|
+
}
|
|
64
|
+
onMobileOpenChange === null || onMobileOpenChange === void 0 ? void 0 : onMobileOpenChange(open);
|
|
65
|
+
}, [isMobileOpenControlled, onMobileOpenChange]);
|
|
66
|
+
// Lock body scroll when mobile overlay is open
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (isMobile && isMobileOpen) {
|
|
69
|
+
document.body.style.overflow = 'hidden';
|
|
70
|
+
return () => { document.body.style.overflow = ''; };
|
|
71
|
+
}
|
|
72
|
+
}, [isMobile, isMobileOpen]);
|
|
24
73
|
const contextValue = useMemo(() => ({
|
|
25
74
|
collapsed,
|
|
26
75
|
setCollapsed,
|
|
27
76
|
toggleCollapsed,
|
|
28
77
|
width: {
|
|
29
78
|
expanded: width.expanded || 280,
|
|
30
|
-
collapsed: width.collapsed ||
|
|
79
|
+
collapsed: width.collapsed || 64,
|
|
31
80
|
},
|
|
32
81
|
side,
|
|
33
82
|
variant,
|
|
34
|
-
|
|
83
|
+
isMobileOpen,
|
|
84
|
+
setMobileOpen,
|
|
85
|
+
isMobile,
|
|
86
|
+
}), [collapsed, setCollapsed, toggleCollapsed, width.expanded, width.collapsed, side, variant, isMobileOpen, setMobileOpen, isMobile]);
|
|
35
87
|
return (jsx(SidebarContext.Provider, { value: contextValue, children: children }));
|
|
36
88
|
};
|
|
37
89
|
|
|
38
|
-
export { SidebarProvider, useSidebarContext };
|
|
90
|
+
export { AccordionNavProvider, SidebarProvider, useAccordionNavContext, useSidebarContext };
|
|
@@ -6,7 +6,9 @@ import { useSidebarContext } from './SidebarContext.js';
|
|
|
6
6
|
const SidebarFooter = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
7
7
|
const sidebarContext = useSidebarContext();
|
|
8
8
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
9
|
-
|
|
9
|
+
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
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-4", className), ...props, children: children }));
|
|
10
12
|
});
|
|
11
13
|
SidebarFooter.displayName = "SidebarFooter";
|
|
12
14
|
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
|
+
import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
3
4
|
import { cn } from '../../../utils/cn.js';
|
|
4
5
|
import { useSidebarContext } from './SidebarContext.js';
|
|
5
6
|
import { SidebarToggle } from './SidebarToggle.js';
|
|
7
|
+
import { Button } from '../Button/Button.js';
|
|
6
8
|
|
|
7
9
|
const SidebarHeader = React__default.forwardRef(({ logo, showToggle = true, customToggle, children, className, ...props }, ref) => {
|
|
8
10
|
const sidebarContext = useSidebarContext();
|
|
9
11
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
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;
|
|
10
14
|
// If children are provided, use them instead of default layout
|
|
11
15
|
if (children) {
|
|
12
|
-
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-b border-
|
|
16
|
+
return (jsx("div", { ref: ref, className: cn("flex-shrink-0 border-b border-[var(--color-border)]", className), ...props, children: children }));
|
|
13
17
|
}
|
|
14
|
-
return (jsxs("div", { ref: ref, className: cn("flex items-center justify-between flex-shrink-0 h-16 border-b border-[var(--color-border)]
|
|
18
|
+
return (jsxs("div", { ref: ref, className: cn("flex items-center justify-between flex-shrink-0 h-16 border-b border-[var(--color-border)] transition-all duration-200", collapsed && !isMobile ? "px-3" : "px-4", className), ...props, children: [jsx("div", { className: cn("flex items-center min-w-0 transition-all duration-200", collapsed && !isMobile ? "flex-shrink-0" : "flex-1"), children: logo }), isMobile && isMobileOpen && (jsx(Button, { variant: "soft", size: "sm", className: "size-8 p-0", onClick: () => sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.setMobileOpen(false), "aria-label": "Close navigation", children: jsx(XMarkIcon, { className: "size-5" }) })), !isMobile && showToggle && (jsx("div", { className: "flex-shrink-0", children: customToggle || jsx(SidebarToggle, {}) }))] }));
|
|
15
19
|
});
|
|
16
20
|
SidebarHeader.displayName = "SidebarHeader";
|
|
17
21
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
import { Bars3Icon } from '@heroicons/react/24/outline';
|
|
4
|
+
import { cn } from '../../../utils/cn.js';
|
|
5
|
+
import { useSidebarContext } from './SidebarContext.js';
|
|
6
|
+
import { Button } from '../Button/Button.js';
|
|
7
|
+
|
|
8
|
+
const SidebarMobileTrigger = React__default.forwardRef(({ icon, className, onClick, ...props }, ref) => {
|
|
9
|
+
const sidebarContext = useSidebarContext();
|
|
10
|
+
if (!sidebarContext) {
|
|
11
|
+
if (process.env.NODE_ENV !== "production") {
|
|
12
|
+
console.warn("SidebarMobileTrigger must be used within a Sidebar component");
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const { isMobile, setMobileOpen } = sidebarContext;
|
|
17
|
+
// Only render on mobile
|
|
18
|
+
if (!isMobile)
|
|
19
|
+
return null;
|
|
20
|
+
const handleClick = (e) => {
|
|
21
|
+
setMobileOpen(true);
|
|
22
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
23
|
+
};
|
|
24
|
+
return (jsx(Button, { ref: ref, variant: "soft", size: "sm", className: cn("size-9 p-0", className), onClick: handleClick, "aria-label": "Open navigation menu", ...props, children: icon || jsx(Bars3Icon, { className: "size-5" }) }));
|
|
25
|
+
});
|
|
26
|
+
SidebarMobileTrigger.displayName = "SidebarMobileTrigger";
|
|
27
|
+
|
|
28
|
+
export { SidebarMobileTrigger };
|
|
@@ -1,9 +1,10 @@
|
|
|
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
5
|
|
|
5
|
-
const SidebarNav = React__default.forwardRef(({ children, className, ...props }, ref) => {
|
|
6
|
-
return (jsx("nav", { ref: ref, className: cn('flex-1 space-y-1 p-4', className), role: "navigation", ...props, children: children }));
|
|
6
|
+
const SidebarNav = React__default.forwardRef(({ accordion = false, children, className, ...props }, ref) => {
|
|
7
|
+
return (jsx("nav", { ref: ref, className: cn('flex-1 space-y-1 p-4', className), role: "navigation", ...props, children: jsx(AccordionNavProvider, { accordion: accordion, children: children }) }));
|
|
7
8
|
});
|
|
8
9
|
SidebarNav.displayName = 'SidebarNav';
|
|
9
10
|
|
|
@@ -6,8 +6,10 @@ import { useSidebarContext } from './SidebarContext.js';
|
|
|
6
6
|
const SidebarNavGroup = React__default.forwardRef(({ title, showTitleWhenCollapsed = false, children, className, ...props }, ref) => {
|
|
7
7
|
const sidebarContext = useSidebarContext();
|
|
8
8
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
9
|
-
const
|
|
10
|
-
|
|
9
|
+
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
10
|
+
const isCollapsedView = collapsed && !isMobile;
|
|
11
|
+
const showTitle = title && (!isCollapsedView || showTitleWhenCollapsed);
|
|
12
|
+
return (jsxs("div", { ref: ref, className: cn("space-y-1", className), ...props, children: [showTitle && (jsx("div", { className: "px-2 py-2", children: jsx("h3", { className: "text-sm font-semibold text-[var(--color-text-muted)] uppercase tracking-wider", children: title }) })), isCollapsedView && !showTitleWhenCollapsed && (jsx("div", { className: "h-px bg-[var(--color-border)] mx-2 my-2" })), jsx("div", { className: "space-y-1", children: children })] }));
|
|
11
13
|
});
|
|
12
14
|
SidebarNavGroup.displayName = "SidebarNavGroup";
|
|
13
15
|
|
|
@@ -19,7 +19,7 @@ const SidebarToggle = React__default.forwardRef(({ expandIcon, collapseIcon, cla
|
|
|
19
19
|
toggleCollapsed();
|
|
20
20
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
21
21
|
};
|
|
22
|
-
return (jsx(Button, { ref: ref, variant: "
|
|
22
|
+
return (jsx(Button, { ref: ref, variant: "soft", size: "sm", className: cn("size-8 p-0", className), onClick: handleClick, "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar", title: collapsed ? "Expand sidebar" : "Collapse sidebar", ...props, children: jsx(motion.div, { initial: false, animate: { rotate: collapsed ? 180 : 0 }, transition: { duration: 0.2 }, children: collapsed
|
|
23
23
|
? expandIcon || jsx(ChevronRightIcon, { className: "size-4" })
|
|
24
24
|
: collapseIcon || jsx(ChevronLeftIcon, { className: "size-4" }) }) }));
|
|
25
25
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import React__default from 'react';
|
|
3
|
+
import { cn } from '../../../utils/cn.js';
|
|
4
|
+
import { useSidebarContext } from './SidebarContext.js';
|
|
5
|
+
import { Avatar } from '../Avatar/Avatar.js';
|
|
6
|
+
import { Tooltip } from '../Tooltip/Tooltip.js';
|
|
7
|
+
import '../Tooltip/Tooltip.animations.js';
|
|
8
|
+
|
|
9
|
+
const statusColors = {
|
|
10
|
+
online: "bg-[var(--color-success)]",
|
|
11
|
+
offline: "bg-[var(--color-background-quaternary)]",
|
|
12
|
+
away: "bg-[var(--color-warning)]",
|
|
13
|
+
busy: "bg-[var(--color-danger)]",
|
|
14
|
+
};
|
|
15
|
+
const SidebarUserProfile = React__default.forwardRef(({ name, email, avatar, status, dropdownContent, className, onClick, ...props }, ref) => {
|
|
16
|
+
const sidebarContext = useSidebarContext();
|
|
17
|
+
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
18
|
+
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
19
|
+
const isCollapsedView = collapsed && !isMobile;
|
|
20
|
+
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}` }))] }));
|
|
21
|
+
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) => {
|
|
22
|
+
if ((e.key === "Enter" || e.key === " ") && onClick) {
|
|
23
|
+
e.preventDefault();
|
|
24
|
+
onClick(e);
|
|
25
|
+
}
|
|
26
|
+
}, ...props, children: [avatarElement, !isCollapsedView && (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-muted)] truncate", children: email }))] }))] }));
|
|
27
|
+
// Tooltip when collapsed
|
|
28
|
+
if (isCollapsedView) {
|
|
29
|
+
return (jsx(Tooltip, { content: name, placement: "right", children: content }));
|
|
30
|
+
}
|
|
31
|
+
return content;
|
|
32
|
+
});
|
|
33
|
+
SidebarUserProfile.displayName = "SidebarUserProfile";
|
|
34
|
+
|
|
35
|
+
export { SidebarUserProfile };
|
|
@@ -6,5 +6,7 @@ export { SidebarNav } from "./SidebarNav";
|
|
|
6
6
|
export { SidebarNavGroup } from "./SidebarNavGroup";
|
|
7
7
|
export { SidebarSubmenu } from "./SidebarSubmenu";
|
|
8
8
|
export { SidebarToggle } from "./SidebarToggle";
|
|
9
|
+
export { SidebarMobileTrigger } from "./SidebarMobileTrigger";
|
|
10
|
+
export { SidebarUserProfile } from "./SidebarUserProfile";
|
|
9
11
|
export { useSidebarContext } from "./SidebarContext";
|
|
10
|
-
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarSide, SidebarVariant, SidebarContextValue, SidebarTheme, SidebarThemeOverrides, } from "./Sidebar.types";
|
|
12
|
+
export type { SidebarProps, SidebarHeaderProps, SidebarContentProps, SidebarFooterProps, SidebarNavProps, SidebarNavGroupProps, SidebarToggleProps, SidebarMobileTriggerProps, SidebarUserProfileProps, SidebarUserStatus, SidebarSide, SidebarVariant, SidebarContextValue, SidebarTheme, SidebarThemeOverrides, } from "./Sidebar.types";
|
|
@@ -7,11 +7,11 @@ const tabsTheme = {
|
|
|
7
7
|
// TabsList styles with proper design tokens
|
|
8
8
|
listStyle: "relative inline-flex items-center flex-row border-b border-[var(--color-border)] gap-2",
|
|
9
9
|
// TabsTrigger base styles - no individual borders, underline will be handled separately
|
|
10
|
-
triggerStyle: "relative inline-flex items-center justify-center whitespace-nowrap font-
|
|
10
|
+
triggerStyle: "relative inline-flex items-center justify-center whitespace-nowrap font-semibold transition-all duration-300 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] data-[state=active]:text-[var(--color-primary)]",
|
|
11
11
|
// TabsTrigger size variants using CSS variables where applicable
|
|
12
12
|
triggerSizes: {
|
|
13
|
-
sm: "px-2 py-1.5 text-base
|
|
14
|
-
md: "px-2 py-2 text-base
|
|
13
|
+
sm: "px-2 py-1.5 text-base",
|
|
14
|
+
md: "px-2 py-2 text-base",
|
|
15
15
|
},
|
|
16
16
|
// TabsBody styles with proper CSS variables
|
|
17
17
|
bodyStyle: "focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2",
|
|
@@ -89,6 +89,8 @@ export { SidebarNav } from './Sidebar/SidebarNav.js';
|
|
|
89
89
|
export { SidebarNavGroup } from './Sidebar/SidebarNavGroup.js';
|
|
90
90
|
export { SidebarSubmenu } from './Sidebar/SidebarSubmenu.js';
|
|
91
91
|
export { SidebarToggle } from './Sidebar/SidebarToggle.js';
|
|
92
|
+
export { SidebarMobileTrigger } from './Sidebar/SidebarMobileTrigger.js';
|
|
93
|
+
export { SidebarUserProfile } from './Sidebar/SidebarUserProfile.js';
|
|
92
94
|
export { useSidebarContext } from './Sidebar/SidebarContext.js';
|
|
93
95
|
export { OfflineIndicator } from './OfflineIndicator/OfflineIndicator.js';
|
|
94
96
|
export { offlineIndicatorTheme } from './OfflineIndicator/OfflineIndicator.theme.js';
|
|
@@ -23,7 +23,7 @@ const GanttToolbar = () => {
|
|
|
23
23
|
const targetScroll = todayOffset * dayWidth - container.clientWidth / 2;
|
|
24
24
|
container.scrollTo({ left: targetScroll, behavior: "smooth" });
|
|
25
25
|
};
|
|
26
|
-
return (jsxs("div", { className: cn(theme.toolbarStyle), children: [jsxs("div", { className: "flex items-center gap-3", children: [todayOffset !== null && (jsxs(Button, { variant: "outline", color: "neutral", size: "sm", onClick: handleScrollToToday, children: [jsx(CalendarIcon, { className: "size-4" }), "Today"] })), jsx(Segmented, { value: zoom, onChange: (val) => setZoom(val), size: "sm", children: ZOOM_OPTIONS.map((opt) => (jsx(Segmented.Item, { value: opt.value, children: opt.label }, opt.value))) }), jsx(Checkbox, { id: "gantt-hide-completed", name: "hideCompleted", value: "hideCompleted", checked: hideCompleted, onChange: (checked) => setHideCompleted(checked), label: "Hide completed", size: "sm" })] }), jsx("div", { className: "flex items-center gap-2", children: onGroupAdd && !readonly && (jsxs(Button, { variant: "
|
|
26
|
+
return (jsxs("div", { className: cn(theme.toolbarStyle), children: [jsxs("div", { className: "flex items-center gap-3", children: [todayOffset !== null && (jsxs(Button, { variant: "outline", color: "neutral", size: "sm", onClick: handleScrollToToday, children: [jsx(CalendarIcon, { className: "size-4" }), "Today"] })), jsx(Segmented, { value: zoom, onChange: (val) => setZoom(val), size: "sm", children: ZOOM_OPTIONS.map((opt) => (jsx(Segmented.Item, { value: opt.value, children: opt.label }, opt.value))) }), jsx(Checkbox, { id: "gantt-hide-completed", name: "hideCompleted", value: "hideCompleted", checked: hideCompleted, onChange: (checked) => setHideCompleted(checked), label: "Hide completed", size: "sm" })] }), jsx("div", { className: "flex items-center gap-2", children: onGroupAdd && !readonly && (jsxs(Button, { variant: "soft", size: "sm", onClick: onGroupAdd, children: [jsx(PlusIcon, { className: "size-3.5" }), "Add group"] })) })] }));
|
|
27
27
|
};
|
|
28
28
|
|
|
29
29
|
export { GanttToolbar };
|