@flikk/ui 1.0.0-beta.12 → 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/ai/StreamingResponse/StreamingResponse.types.d.ts +7 -7
- package/dist/components/charts/FunnelChart/FunnelChart.js +2 -2
- package/dist/components/core/Accordion/Accordion.types.d.ts +1 -7
- package/dist/components/core/Accordion/AccordionTrigger.js +4 -6
- package/dist/components/core/Alert/Alert.types.d.ts +1 -1
- package/dist/components/core/Avatar/Avatar.js +22 -18
- package/dist/components/core/Avatar/Avatar.theme.js +1 -1
- package/dist/components/core/Avatar/Avatar.types.d.ts +9 -8
- package/dist/components/core/AvatarGroup/AvatarGroup.js +2 -2
- package/dist/components/core/Badge/Badge.js +10 -8
- package/dist/components/core/Badge/Badge.theme.js +14 -7
- package/dist/components/core/Badge/Badge.types.d.ts +3 -3
- 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 +5 -5
- package/dist/components/core/Button/Button.types.d.ts +1 -1
- package/dist/components/core/Calendar/CalendarEvent/CalendarEvent.types.d.ts +5 -6
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +2 -2
- package/dist/components/core/Drawer/DrawerHeader.js +1 -1
- package/dist/components/core/FileTypeIcon/FileTypeIcon.d.ts +3 -0
- package/dist/components/core/FileTypeIcon/FileTypeIcon.js +33 -0
- package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.d.ts +2 -0
- package/dist/components/core/FileTypeIcon/FileTypeIcon.theme.js +12 -0
- package/dist/components/core/FileTypeIcon/FileTypeIcon.types.d.ts +19 -0
- package/dist/components/core/FileTypeIcon/fileTypes.d.ts +7 -0
- package/dist/components/core/FileTypeIcon/fileTypes.js +30 -0
- package/dist/components/core/FileTypeIcon/index.d.ts +2 -0
- package/dist/components/core/Link/Link.js +1 -1
- package/dist/components/core/MenuItem/MenuItem.theme.js +1 -1
- package/dist/components/core/Message/Message.js +1 -1
- package/dist/components/core/Message/Message.theme.js +2 -2
- package/dist/components/core/Message/Message.types.d.ts +6 -6
- package/dist/components/core/Message/MessageAvatar.d.ts +2 -2
- package/dist/components/core/Message/MessageAvatar.js +3 -3
- 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/Popover/PopoverBody.js +19 -8
- 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/Tag/Tag.d.ts +1 -1
- package/dist/components/core/Tag/Tag.js +2 -2
- package/dist/components/core/Tag/Tag.types.d.ts +1 -1
- package/dist/components/core/index.d.ts +1 -1
- package/dist/components/core/index.js +3 -1
- package/dist/components/data-display/Feed/Feed.animations.d.ts +40 -0
- package/dist/components/data-display/Feed/Feed.animations.js +26 -0
- package/dist/components/data-display/Feed/Feed.d.ts +5 -1
- package/dist/components/data-display/Feed/Feed.js +87 -60
- package/dist/components/data-display/Feed/Feed.theme.js +11 -13
- package/dist/components/data-display/Feed/Feed.types.d.ts +35 -34
- package/dist/components/data-display/Feed/index.d.ts +1 -1
- package/dist/components/data-display/GanttChart/GanttTaskBar.js +11 -9
- package/dist/components/data-display/GanttChart/GanttToolbar.js +1 -1
- package/dist/components/data-display/Timeline/TimelineMarker.js +2 -2
- package/dist/components/effects/3d/index.d.ts +1 -1
- package/dist/components/effects/AIOrb/AIOrb.d.ts +1 -0
- package/dist/components/effects/AIOrb/AIOrb.js +3 -2
- package/dist/components/effects/AIOrb/AIOrb.types.d.ts +1 -0
- package/dist/components/effects/AIOrb/hooks/index.d.ts +2 -0
- package/dist/components/effects/AIOrb/hooks/useOrbState.d.ts +20 -0
- package/dist/components/effects/AIOrb/hooks/useOrbState.js +27 -0
- package/dist/components/effects/AIOrb/index.d.ts +2 -2
- package/dist/components/effects/AIOrb/variants/Aura/Aura.d.ts +3 -0
- package/dist/components/effects/AIOrb/variants/Aura/Aura.js +75 -0
- package/dist/components/effects/AIOrb/variants/Aura/Aura.shaders.d.ts +28 -0
- package/dist/components/effects/AIOrb/variants/Aura/Aura.shaders.js +191 -0
- package/dist/components/effects/AIOrb/variants/Aura/Aura.types.d.ts +53 -0
- package/dist/components/effects/AIOrb/variants/Aura/ShaderCanvas.d.ts +17 -0
- package/dist/components/effects/AIOrb/variants/Aura/ShaderCanvas.js +149 -0
- package/dist/components/effects/AIOrb/variants/Aura/index.d.ts +2 -0
- package/dist/components/effects/AIOrb/variants/DotSphere/DotSphere.js +38 -7
- package/dist/components/effects/AIOrb/variants/DotSphere/DotSphere.types.d.ts +6 -0
- package/dist/components/effects/AIOrb/variants/Liquid/Liquid.js +34 -8
- package/dist/components/effects/AIOrb/variants/Liquid/Liquid.types.d.ts +6 -0
- package/dist/components/effects/AIOrb/variants/index.d.ts +2 -0
- package/dist/components/effects/CustomCursor/CustomCursor.d.ts +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.js +50 -47
- package/dist/components/effects/GlassSurface/GlassSurface.js +24 -12
- package/dist/components/effects/GlassSurface/GlassSurface.types.d.ts +8 -0
- package/dist/components/forms/Mention/Mention.js +2 -2
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +10 -10
- package/dist/index.js +3 -1
- package/dist/styles.css +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/paletteGenerator.d.ts +46 -0
- package/package.json +1 -1
- package/src/global.scss +1 -0
- package/src/styles/theme.css +50 -54
- package/dist/components/core/Loader/Loader.d.ts +0 -24
- package/dist/components/core/Loader/Loader.js +0 -57
- package/dist/components/core/Loader/Loader.theme.d.ts +0 -27
- package/dist/components/core/Loader/Loader.theme.js +0 -60
- package/dist/components/core/Loader/Loader.types.d.ts +0 -71
- package/dist/components/core/Loader/index.d.ts +0 -2
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type FileTypeName = 'pdf' | 'doc' | 'docx' | 'txt' | 'xls' | 'xlsx' | 'csv' | 'ppt' | 'mp3' | 'wav' | 'mp4' | 'mov' | 'jpg' | 'png' | 'svg' | 'gif' | 'zip' | 'rar';
|
|
3
|
+
export type FileTypeIconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
4
|
+
export type FileTypeIconColorMode = 'color' | 'mono';
|
|
5
|
+
export interface FileTypeIconTheme {
|
|
6
|
+
baseStyle: string;
|
|
7
|
+
sizes: Record<FileTypeIconSize, number>;
|
|
8
|
+
}
|
|
9
|
+
export interface FileTypeIconThemeOverrides {
|
|
10
|
+
baseStyle?: string;
|
|
11
|
+
sizes?: Partial<Record<FileTypeIconSize, number>>;
|
|
12
|
+
}
|
|
13
|
+
export interface FileTypeIconProps extends React.SVGProps<SVGSVGElement> {
|
|
14
|
+
type: FileTypeName;
|
|
15
|
+
size?: FileTypeIconSize | number;
|
|
16
|
+
colorMode?: FileTypeIconColorMode;
|
|
17
|
+
className?: string;
|
|
18
|
+
theme?: FileTypeIconThemeOverrides;
|
|
19
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fileTypes = {
|
|
2
|
+
// PDF
|
|
3
|
+
pdf: { label: 'PDF', color: '#E5252A', category: 'pdf' },
|
|
4
|
+
// Documents
|
|
5
|
+
doc: { label: 'DOC', color: '#2B7CD3', category: 'document' },
|
|
6
|
+
docx: { label: 'DOCX', color: '#2B7CD3', category: 'document' },
|
|
7
|
+
txt: { label: 'TXT', color: '#4B5563', category: 'document' },
|
|
8
|
+
// Spreadsheets
|
|
9
|
+
xls: { label: 'XLS', color: '#1D6F42', category: 'spreadsheet' },
|
|
10
|
+
xlsx: { label: 'XLSX', color: '#1D6F42', category: 'spreadsheet' },
|
|
11
|
+
csv: { label: 'CSV', color: '#1D6F42', category: 'spreadsheet' },
|
|
12
|
+
// Presentations
|
|
13
|
+
ppt: { label: 'PPT', color: '#D04423', category: 'presentation' },
|
|
14
|
+
// Audio
|
|
15
|
+
mp3: { label: 'MP3', color: '#8B5CF6', category: 'audio' },
|
|
16
|
+
wav: { label: 'WAV', color: '#8B5CF6', category: 'audio' },
|
|
17
|
+
// Video
|
|
18
|
+
mp4: { label: 'MP4', color: '#7C3AED', category: 'video' },
|
|
19
|
+
mov: { label: 'MOV', color: '#7C3AED', category: 'video' },
|
|
20
|
+
// Images
|
|
21
|
+
jpg: { label: 'JPG', color: '#F59E0B', category: 'image' },
|
|
22
|
+
png: { label: 'PNG', color: '#F59E0B', category: 'image' },
|
|
23
|
+
svg: { label: 'SVG', color: '#F59E0B', category: 'image' },
|
|
24
|
+
gif: { label: 'GIF', color: '#F59E0B', category: 'image' },
|
|
25
|
+
// Archives
|
|
26
|
+
zip: { label: 'ZIP', color: '#CA8A04', category: 'archive' },
|
|
27
|
+
rar: { label: 'RAR', color: '#CA8A04', category: 'archive' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export { fileTypes };
|
|
@@ -33,7 +33,7 @@ const Link = React__default.forwardRef(({ children, className = "", size = "inhe
|
|
|
33
33
|
}, [shouldReduceMotion]);
|
|
34
34
|
return (jsx(motion.a, { ref: ref, href: disabled ? undefined : href, className: cn(buttonTheme.baseStyle, themeClasses.colorClasses, themeClasses.variantClasses, themeClasses.sizeClasses, themeClasses.stateClasses,
|
|
35
35
|
// Special case for link variant to have an underline
|
|
36
|
-
"underline underline-offset-4 rounded-none", className), "data-color": color, "data-variant": resolvedVariant, "data-size": size, "aria-disabled": disabled || undefined, ...(disabled ? {} : animationProps), ...props, children: children }));
|
|
36
|
+
"underline underline-offset-4 rounded-none font-semibold", className), "data-color": color, "data-variant": resolvedVariant, "data-size": size, "aria-disabled": disabled || undefined, ...(disabled ? {} : animationProps), ...props, children: children }));
|
|
37
37
|
});
|
|
38
38
|
Link.displayName = "Link";
|
|
39
39
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const menuItemTheme = {
|
|
2
2
|
// Base item styling with hover, focus, and disabled states
|
|
3
|
-
baseStyle: "relative flex items-center gap-2 px-
|
|
3
|
+
baseStyle: "relative flex items-center gap-2 px-3 py-1.5 text-base font-[500] text-[var(--color-text-primary)] cursor-pointer outline-none select-none rounded-[var(--radius-base)] transition-colors duration-300 " +
|
|
4
4
|
"hover:bg-[var(--color-background-secondary)] focus:bg-[var(--color-background-secondary)] " +
|
|
5
5
|
"data-[focused=true]:bg-[var(--color-background-secondary)] " +
|
|
6
6
|
"data-[selected=true]:bg-[var(--color-primary)]/5 data-[selected=true]:text-[var(--color-primary)] " +
|
|
@@ -100,7 +100,7 @@ const MessageRoot = React__default.forwardRef(({ role, variant: variantProp, con
|
|
|
100
100
|
if (variant === "system") {
|
|
101
101
|
return (jsx("div", { ref: ref, className: cn("w-full max-w-full", className), "data-variant": "system", "data-role": role, ...props, children: jsxs("div", { className: containerStyle, children: [jsx("div", { className: bubbleStyle, children: typeof renderedContent === "string" ? (jsx("p", { className: "text-base", children: renderedContent })) : (renderedContent) }), jsx(MessageFooter, { isStreaming: isStreaming, streamingIndicator: streamingIndicator, showTimestamp: showTimestamp, timestamp: timestamp, actions: undefined, timestampStyle: timestampStyle, streamingIndicatorStyle: cn(messageTheme.streamingIndicatorStyle, customTheme.streamingIndicatorStyle), actionsContainerStyle: undefined, variant: "system" })] }) }));
|
|
102
102
|
}
|
|
103
|
-
return (jsx("div", { ref: ref, className: cn("w-full max-w-full group", className), "data-variant": variant, "data-role": role, ...props, children: jsxs("div", { className: containerStyle, children: [avatar && (jsx("div", { className: "flex-shrink-0", children: jsx(Avatar, { src: avatar.src,
|
|
103
|
+
return (jsx("div", { ref: ref, className: cn("w-full max-w-full group", className), "data-variant": variant, "data-role": role, ...props, children: jsxs("div", { className: containerStyle, children: [avatar && (jsx("div", { className: "flex-shrink-0", children: jsx(Avatar, { src: avatar.src, name: avatar.name, fallback: avatar.fallback, size: "xs" }) })), jsxs("div", { className: cn("flex flex-col", variant === "sender" ? "items-end" : "items-start"), children: [jsx("div", { className: bubbleStyle, children: typeof renderedContent === "string" ? (jsx("p", { className: "break-words", children: renderedContent })) : (renderedContent) }), jsx(MessageFooter, { isStreaming: isStreaming, streamingIndicator: streamingIndicator, showTimestamp: showTimestamp, timestamp: timestamp, actions: finalActions, timestampStyle: timestampStyle, streamingIndicatorStyle: cn(messageTheme.streamingIndicatorStyle, customTheme.streamingIndicatorStyle), actionsContainerStyle: cn(messageTheme.actionsContainerStyle, customTheme.actionsContainerStyle), variant: variant })] })] }) }));
|
|
104
104
|
});
|
|
105
105
|
MessageRoot.displayName = "Message";
|
|
106
106
|
// Attach compound components
|
|
@@ -6,12 +6,12 @@ const messageTheme = {
|
|
|
6
6
|
// Shared base styles (apply to all variants)
|
|
7
7
|
bubbleBaseStyle: "max-w-[80%] sm:max-w-[85%] rounded-[calc(var(--radius-base)*1.5)] px-3 py-2 w-fit min-w-fit h-fit",
|
|
8
8
|
containerBaseStyle: "flex items-start gap-3 h-fit text-base",
|
|
9
|
-
timestampStyle: "text-
|
|
9
|
+
timestampStyle: "text-xs text-[var(--color-text-placeholder)] dark:text-[var(--color-neutral-500)]",
|
|
10
10
|
// Variant-specific modifiers (only differences)
|
|
11
11
|
variants: {
|
|
12
12
|
sender: {
|
|
13
13
|
containerModifier: "flex-row-reverse", // Reverse direction for right alignment
|
|
14
|
-
bubbleStyle: "bg-[var(--color-
|
|
14
|
+
bubbleStyle: "bg-[var(--color-neutral-900)] text-[var(--color-neutral-100)] " +
|
|
15
15
|
"rounded-tr-xs " +
|
|
16
16
|
// Dark mode - slightly darker gradient for better contrast
|
|
17
17
|
" ",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { ReactNode } from
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
2
|
/**
|
|
3
3
|
* Message role types for AI conversations
|
|
4
4
|
*/
|
|
5
|
-
export type MessageRole =
|
|
5
|
+
export type MessageRole = "user" | "assistant" | "system";
|
|
6
6
|
/**
|
|
7
7
|
* Message variant types for chat UI
|
|
8
8
|
*/
|
|
9
|
-
export type MessageVariant =
|
|
9
|
+
export type MessageVariant = "sender" | "receiver" | "system";
|
|
10
10
|
/**
|
|
11
11
|
* Avatar configuration for message sender
|
|
12
12
|
*/
|
|
@@ -16,9 +16,9 @@ export interface MessageAvatarProps {
|
|
|
16
16
|
*/
|
|
17
17
|
src?: string;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* The name for the avatar (used for initials and alt text)
|
|
20
20
|
*/
|
|
21
|
-
|
|
21
|
+
name: string;
|
|
22
22
|
/**
|
|
23
23
|
* Fallback content to display when image fails to load
|
|
24
24
|
*/
|
|
@@ -28,7 +28,7 @@ export interface MessageAvatarProps {
|
|
|
28
28
|
* Message component props
|
|
29
29
|
* Supports both props-based API and compound component API
|
|
30
30
|
*/
|
|
31
|
-
export interface MessageProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
31
|
+
export interface MessageProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "content"> {
|
|
32
32
|
/**
|
|
33
33
|
* The role of the message sender (AI semantic naming)
|
|
34
34
|
* Takes precedence over variant if both are provided
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import React from "react";
|
|
2
2
|
export interface MessageAvatarComponentProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
3
|
/**
|
|
4
4
|
* The source URL for the avatar image
|
|
5
5
|
*/
|
|
6
6
|
src?: string;
|
|
7
7
|
/**
|
|
8
|
-
* The name
|
|
8
|
+
* The name for the avatar
|
|
9
9
|
*/
|
|
10
10
|
name: string;
|
|
11
11
|
/**
|
|
@@ -6,11 +6,11 @@ import { useMessageContext } from './MessageContext.js';
|
|
|
6
6
|
const MessageAvatar = React__default.forwardRef(({ src, name, fallback, className, ...props }, ref) => {
|
|
7
7
|
const { variant } = useMessageContext();
|
|
8
8
|
// Don't render avatar for system messages
|
|
9
|
-
if (variant ===
|
|
9
|
+
if (variant === "system") {
|
|
10
10
|
return null;
|
|
11
11
|
}
|
|
12
|
-
return (jsx("div", { ref: ref, className: className, ...props, children: jsx(Avatar, { src: src,
|
|
12
|
+
return (jsx("div", { ref: ref, className: className, ...props, children: jsx(Avatar, { src: src, name: name, fallback: fallback, size: "xs" }) }));
|
|
13
13
|
});
|
|
14
|
-
MessageAvatar.displayName =
|
|
14
|
+
MessageAvatar.displayName = "Message.Avatar";
|
|
15
15
|
|
|
16
16
|
export { MessageAvatar };
|
|
@@ -8,7 +8,7 @@ import { XMarkIcon } from '@heroicons/react/24/outline';
|
|
|
8
8
|
const ModalHeader = ({ children, icon, showCloseButton, className, ...props }) => {
|
|
9
9
|
const { onClose, showCloseButton: contextShowCloseButton, theme, isBodyScrollable, } = useModalContext();
|
|
10
10
|
const shouldShowCloseButton = showCloseButton !== null && showCloseButton !== void 0 ? showCloseButton : contextShowCloseButton;
|
|
11
|
-
return (jsxs("div", { className: cn(theme.header, isBodyScrollable && "border-[var(--color-border)] relative", className), ...props, children: [jsxs("div", { className: cn(theme.headerContent), children: [icon && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-secondary)]", children: icon })), jsx("div", { className: "flex-1", children: children })] }), shouldShowCloseButton && (jsx("div", { className: "", children: jsx(Button, { color: "neutral", variant: "
|
|
11
|
+
return (jsxs("div", { className: cn(theme.header, isBodyScrollable && "border-[var(--color-border)] relative", className), ...props, children: [jsxs("div", { className: cn(theme.headerContent), children: [icon && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-secondary)]", children: icon })), jsx("div", { className: "flex-1", children: children })] }), shouldShowCloseButton && (jsx("div", { className: "", children: jsx(Button, { color: "neutral", variant: "soft", iconOnly: true, size: "sm", onClick: onClose, "aria-label": "Close modal", className: "rounded-full", children: jsx(XMarkIcon, { className: "size-5" }) }) }))] }));
|
|
12
12
|
};
|
|
13
13
|
ModalHeader.displayName = "Modal.Header";
|
|
14
14
|
|
|
@@ -1,53 +1,85 @@
|
|
|
1
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useMemo } from 'react';
|
|
1
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import React__default, { useId, useState, useCallback, useMemo } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
4
|
import { navItemTheme } from './NavItem.theme.js';
|
|
5
|
-
import { useSidebarContext } from '../Sidebar/SidebarContext.js';
|
|
5
|
+
import { useSidebarContext, useAccordionNavContext } from '../Sidebar/SidebarContext.js';
|
|
6
6
|
import { Tooltip } from '../Tooltip/Tooltip.js';
|
|
7
7
|
import '../Tooltip/Tooltip.animations.js';
|
|
8
8
|
import { Badge } from '../Badge/Badge.js';
|
|
9
9
|
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
|
10
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
10
11
|
|
|
11
12
|
const NavItem = React__default.forwardRef(({ children, startContent: propStartContent, endContent: propEndContent, icon, // Legacy
|
|
12
13
|
badge, // Legacy
|
|
13
|
-
description, shortcut, href, onClick, active = false, disabled = false, isDisabled: propIsDisabled, isDanger = false, hasSubmenu = false, expanded = false,
|
|
14
|
+
notification, description, shortcut, href, onClick, active = false, disabled = false, isDisabled: propIsDisabled, isDanger = false, hasSubmenu: propHasSubmenu = false, expanded: controlledExpanded, onToggleExpanded, items, defaultExpanded = false, level = 0, showTooltipWhenCollapsed = true, className, theme: customTheme = {}, ...props }, ref) => {
|
|
14
15
|
var _a, _b;
|
|
15
16
|
const sidebarContext = useSidebarContext();
|
|
17
|
+
const accordionNav = useAccordionNavContext();
|
|
18
|
+
const stableId = useId();
|
|
16
19
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
17
20
|
const isInSidebar = !!sidebarContext;
|
|
21
|
+
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
22
|
+
// Self-managed submenu state
|
|
23
|
+
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
24
|
+
// Determine if this item has a submenu
|
|
25
|
+
const hasSubmenu = propHasSubmenu || (items && items.length > 0);
|
|
26
|
+
// Determine expanded state: controlled vs accordion vs internal
|
|
27
|
+
const isAccordionManaged = !!accordionNav;
|
|
28
|
+
const isControlled = controlledExpanded !== undefined;
|
|
29
|
+
let isExpanded;
|
|
30
|
+
if (isControlled) {
|
|
31
|
+
isExpanded = controlledExpanded;
|
|
32
|
+
}
|
|
33
|
+
else if (isAccordionManaged && hasSubmenu) {
|
|
34
|
+
isExpanded = accordionNav.openId === stableId;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
isExpanded = internalExpanded;
|
|
38
|
+
}
|
|
39
|
+
const handleToggleExpanded = useCallback(() => {
|
|
40
|
+
if (isControlled) {
|
|
41
|
+
onToggleExpanded === null || onToggleExpanded === void 0 ? void 0 : onToggleExpanded();
|
|
42
|
+
}
|
|
43
|
+
else if (isAccordionManaged) {
|
|
44
|
+
if (isExpanded) {
|
|
45
|
+
accordionNav.unregister(stableId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
accordionNav.register(stableId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setInternalExpanded(prev => !prev);
|
|
53
|
+
}
|
|
54
|
+
}, [isControlled, isAccordionManaged, isExpanded, onToggleExpanded, accordionNav, stableId]);
|
|
18
55
|
// Map legacy props
|
|
19
56
|
const startContent = propStartContent || icon;
|
|
20
57
|
const actualIsDisabled = propIsDisabled || disabled;
|
|
21
|
-
//
|
|
58
|
+
// Build end content
|
|
22
59
|
let endContent = propEndContent;
|
|
23
60
|
if (!endContent && badge) {
|
|
24
|
-
endContent = (jsx(Badge, { color: "neutral", variant: "
|
|
25
|
-
}
|
|
26
|
-
if (!endContent && hasSubmenu) {
|
|
27
|
-
endContent = (jsx(ChevronDownIcon, { className: cn("size-4 transition-transform duration-300", expanded && "rotate-180") }));
|
|
61
|
+
endContent = (jsx(Badge, { color: "neutral", variant: "soft", size: "sm", children: badge }));
|
|
28
62
|
}
|
|
63
|
+
// Notification element
|
|
64
|
+
const notificationElement = useMemo(() => {
|
|
65
|
+
if (notification === undefined || notification === false)
|
|
66
|
+
return null;
|
|
67
|
+
if (notification === true) {
|
|
68
|
+
return (jsx("span", { className: cn(navItemTheme.notificationDot, customTheme.notificationDot) }));
|
|
69
|
+
}
|
|
70
|
+
if (typeof notification === "number" && notification > 0) {
|
|
71
|
+
return (jsx("span", { className: cn(navItemTheme.notificationBadge, customTheme.notificationBadge), children: notification > 99 ? "99+" : notification }));
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}, [notification, customTheme.notificationDot, customTheme.notificationBadge]);
|
|
29
75
|
const theme = useMemo(() => ({
|
|
30
76
|
...navItemTheme,
|
|
31
77
|
...customTheme,
|
|
32
|
-
states: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
...navItemTheme.iconStates,
|
|
38
|
-
...customTheme.iconStates,
|
|
39
|
-
},
|
|
40
|
-
levels: {
|
|
41
|
-
...navItemTheme.levels,
|
|
42
|
-
...customTheme.levels,
|
|
43
|
-
},
|
|
44
|
-
collapsed: {
|
|
45
|
-
...navItemTheme.collapsed,
|
|
46
|
-
...customTheme.collapsed,
|
|
47
|
-
},
|
|
48
|
-
danger: customTheme.danger ||
|
|
49
|
-
navItemTheme.danger ||
|
|
50
|
-
"text-[var(--color-danger)] hover:bg-[var(--color-danger-50)]",
|
|
78
|
+
states: { ...navItemTheme.states, ...customTheme.states },
|
|
79
|
+
iconStates: { ...navItemTheme.iconStates, ...customTheme.iconStates },
|
|
80
|
+
levels: { ...navItemTheme.levels, ...customTheme.levels },
|
|
81
|
+
collapsed: { ...navItemTheme.collapsed, ...customTheme.collapsed },
|
|
82
|
+
danger: customTheme.danger || navItemTheme.danger || "text-[var(--color-danger)] hover:bg-[var(--color-danger-50)]",
|
|
51
83
|
}), [customTheme]);
|
|
52
84
|
// Handle click events
|
|
53
85
|
const handleClick = (e) => {
|
|
@@ -55,19 +87,27 @@ description, shortcut, href, onClick, active = false, disabled = false, isDisabl
|
|
|
55
87
|
e.preventDefault();
|
|
56
88
|
return;
|
|
57
89
|
}
|
|
58
|
-
if (hasSubmenu
|
|
90
|
+
if (hasSubmenu) {
|
|
59
91
|
e.preventDefault();
|
|
60
|
-
|
|
92
|
+
handleToggleExpanded();
|
|
61
93
|
return;
|
|
62
94
|
}
|
|
63
95
|
if (onClick) {
|
|
64
96
|
e.preventDefault();
|
|
65
97
|
onClick();
|
|
98
|
+
// Close mobile sidebar after clicking a nav item
|
|
99
|
+
if (isMobile && sidebarContext) {
|
|
100
|
+
sidebarContext.setMobileOpen(false);
|
|
101
|
+
}
|
|
66
102
|
return;
|
|
67
103
|
}
|
|
68
|
-
//
|
|
104
|
+
// Close mobile sidebar after navigation
|
|
105
|
+
if (href && isMobile && sidebarContext) {
|
|
106
|
+
sidebarContext.setMobileOpen(false);
|
|
107
|
+
}
|
|
69
108
|
};
|
|
70
109
|
const isActive = active;
|
|
110
|
+
const isCollapsedView = isInSidebar && collapsed && !isMobile;
|
|
71
111
|
// Get state classes
|
|
72
112
|
const getStateClasses = () => {
|
|
73
113
|
if (actualIsDisabled)
|
|
@@ -78,8 +118,10 @@ description, shortcut, href, onClick, active = false, disabled = false, isDisabl
|
|
|
78
118
|
};
|
|
79
119
|
// Danger overrides
|
|
80
120
|
const dangerClasses = isDanger ? theme.danger : "";
|
|
121
|
+
// Active indicator
|
|
122
|
+
const activeIndicatorClasses = isActive && level === 0 ? theme.activeIndicator : "";
|
|
81
123
|
// Build container classes
|
|
82
|
-
const containerClasses = cn(theme.baseStyle, getStateClasses(), theme.levels[level],
|
|
124
|
+
const containerClasses = cn(theme.baseStyle, getStateClasses(), theme.levels[level], isCollapsedView && ((_a = theme.collapsed) === null || _a === void 0 ? void 0 : _a.container), dangerClasses, activeIndicatorClasses, className);
|
|
83
125
|
// Get icon state classes
|
|
84
126
|
const getIconStateClasses = () => {
|
|
85
127
|
var _a, _b, _c;
|
|
@@ -90,35 +132,41 @@ description, shortcut, href, onClick, active = false, disabled = false, isDisabl
|
|
|
90
132
|
return (_c = theme.iconStates) === null || _c === void 0 ? void 0 : _c.default;
|
|
91
133
|
};
|
|
92
134
|
// Build icon classes
|
|
93
|
-
const iconClasses = cn(
|
|
135
|
+
const iconClasses = cn(isCollapsedView ? (_b = theme.collapsed) === null || _b === void 0 ? void 0 : _b.icon : theme.icon, getIconStateClasses());
|
|
94
136
|
const textClasses = cn(theme.text);
|
|
137
|
+
// Chevron for submenu
|
|
138
|
+
const chevron = hasSubmenu && !isCollapsedView ? (jsx(ChevronDownIcon, { className: cn("size-4 transition-transform duration-200 text-[var(--color-text-muted)]", isExpanded && "rotate-180") })) : null;
|
|
95
139
|
// Content component
|
|
96
|
-
const content = (jsxs("div", { ref: ref, className: containerClasses, role: href ? "link" : "button", tabIndex: actualIsDisabled ? -1 : 0, "aria-disabled": actualIsDisabled, "aria-expanded": hasSubmenu ?
|
|
140
|
+
const content = (jsxs("div", { ref: ref, className: containerClasses, role: href ? "link" : "button", tabIndex: actualIsDisabled ? -1 : 0, "aria-disabled": actualIsDisabled, "aria-expanded": hasSubmenu ? isExpanded : undefined, "data-active": isActive, "data-disabled": actualIsDisabled, "data-level": level, onClick: handleClick, onKeyDown: (e) => {
|
|
97
141
|
if (e.key === "Enter" || e.key === " ") {
|
|
98
142
|
e.preventDefault();
|
|
99
143
|
handleClick(e);
|
|
100
144
|
}
|
|
101
|
-
}, ...props, children: [startContent && (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
className: cn(
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
}, ...props, children: [startContent && (jsxs("div", { className: "flex items-center justify-center flex-shrink-0 relative", children: [React__default.isValidElement(startContent)
|
|
146
|
+
? React__default.cloneElement(startContent, {
|
|
147
|
+
className: cn(iconClasses, startContent.props.className),
|
|
148
|
+
})
|
|
149
|
+
: startContent, isCollapsedView && notificationElement && (jsx("span", { className: "absolute -top-1 -right-1", children: notificationElement }))] })), !isCollapsedView && (jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: cn("truncate", textClasses), children: children }), shortcut && (jsx("kbd", { className: "hidden group-hover:inline-block text-sm text-[var(--color-text-muted)] font-sans border border-[var(--color-border)] rounded px-1.5 py-0.5", children: shortcut }))] }), description && (jsx("div", { className: "text-sm text-[var(--color-text-muted)] mt-0.5 truncate", children: description }))] })), !isCollapsedView && (jsxs("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [notificationElement, endContent, chevron] })), isCollapsedView && (jsx("span", { className: "sr-only", children: children }))] }));
|
|
150
|
+
// Self-contained submenu
|
|
151
|
+
const submenu = hasSubmenu && items && items.length > 0 && !isCollapsedView ? (jsx(AnimatePresence, { initial: false, children: isExpanded && (jsx(motion.div, { initial: "collapsed", animate: "open", exit: "collapsed", variants: {
|
|
152
|
+
open: { opacity: 1, height: "auto" },
|
|
153
|
+
collapsed: { opacity: 0, height: 0 },
|
|
154
|
+
}, transition: { duration: 0.2, ease: "easeInOut" }, className: "overflow-hidden", children: items.map((item, idx) => (jsx(NavItem, { level: Math.min((level + 1), 2), startContent: item.startContent, active: item.active, isDisabled: item.isDisabled, badge: item.badge, notification: item.notification, href: item.href, onClick: item.onClick, children: item.children }, idx))) })) })) : null;
|
|
108
155
|
// Wrap with link if href is provided
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (isInSidebar && collapsed && showTooltipWhenCollapsed) {
|
|
113
|
-
return (jsx(Tooltip, { content: children, placement: "right", children: LinkContent }));
|
|
156
|
+
const renderWrapped = (node) => {
|
|
157
|
+
if (href && !actualIsDisabled) {
|
|
158
|
+
return (jsx("a", { href: href, className: "block group", children: node }));
|
|
114
159
|
}
|
|
115
|
-
return
|
|
116
|
-
}
|
|
160
|
+
return node;
|
|
161
|
+
};
|
|
117
162
|
// Show tooltip in collapsed sidebar mode
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
163
|
+
const renderWithTooltip = (node) => {
|
|
164
|
+
if (isCollapsedView && showTooltipWhenCollapsed) {
|
|
165
|
+
return (jsx(Tooltip, { content: children, placement: "right", children: node }));
|
|
166
|
+
}
|
|
167
|
+
return node;
|
|
168
|
+
};
|
|
169
|
+
return (jsxs(Fragment, { children: [renderWithTooltip(renderWrapped(content)), submenu] }));
|
|
122
170
|
});
|
|
123
171
|
NavItem.displayName = "NavItem";
|
|
124
172
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const navItemTheme = {
|
|
2
|
-
baseStyle: "relative flex items-center gap-2 px-2 py-1.5 text-base font-medium cursor-pointer select-none transition-all duration-
|
|
2
|
+
baseStyle: "relative flex items-center gap-2 px-2 py-1.5 text-base font-medium cursor-pointer select-none transition-all duration-150 w-full rounded-[var(--nav-item-radius)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] group",
|
|
3
3
|
icon: "size-4 text-[var(--color-text-secondary)]",
|
|
4
4
|
text: "flex-1 min-w-0 truncate",
|
|
5
5
|
states: {
|
|
6
|
-
default: "text-[var(--color-text-primary)] hover:bg-
|
|
6
|
+
default: "text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)]",
|
|
7
7
|
active: "bg-[var(--color-primary)]/10 text-[var(--color-primary)] font-semibold",
|
|
8
8
|
disabled: "opacity-60 cursor-not-allowed pointer-events-none text-[var(--color-text-placeholder)]",
|
|
9
|
-
hover: "hover:bg-
|
|
9
|
+
hover: "hover:bg-[var(--color-background-secondary)]",
|
|
10
10
|
},
|
|
11
11
|
iconStates: {
|
|
12
12
|
default: "text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]",
|
|
@@ -22,6 +22,9 @@ const navItemTheme = {
|
|
|
22
22
|
container: "!gap-0 justify-center p-2 aspect-square",
|
|
23
23
|
icon: "size-5",
|
|
24
24
|
},
|
|
25
|
+
activeIndicator: "before:absolute before:left-0 before:top-1/2 before:-translate-y-1/2 before:h-5 before:w-[3px] before:rounded-full before:bg-[var(--color-primary)]",
|
|
26
|
+
notificationDot: "size-2 rounded-full bg-[var(--color-danger)]",
|
|
27
|
+
notificationBadge: "min-w-[18px] h-[18px] px-1 text-[10px] font-semibold rounded-full bg-[var(--color-danger)] text-[var(--color-danger-contrast)] flex items-center justify-center leading-none",
|
|
25
28
|
};
|
|
26
29
|
|
|
27
30
|
export { navItemTheme };
|
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import { ReactNode } from "react";
|
|
2
|
+
export interface NavItemSubmenuItem {
|
|
3
|
+
/** Label text */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/** Link href */
|
|
6
|
+
href?: string;
|
|
7
|
+
/** Click handler */
|
|
8
|
+
onClick?: () => void;
|
|
9
|
+
/** Whether this sub-item is active */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
/** Whether this sub-item is disabled */
|
|
12
|
+
isDisabled?: boolean;
|
|
13
|
+
/** Badge text/number on the sub-item */
|
|
14
|
+
badge?: string | number;
|
|
15
|
+
/** Notification count/dot on the sub-item */
|
|
16
|
+
notification?: number | boolean;
|
|
17
|
+
/** Start content (icon) for the sub-item */
|
|
18
|
+
startContent?: ReactNode;
|
|
19
|
+
}
|
|
2
20
|
export interface NavItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
3
21
|
/**
|
|
4
22
|
* Content of the navigation item
|
|
@@ -30,6 +48,10 @@ export interface NavItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
30
48
|
* @deprecated Use endContent instead
|
|
31
49
|
*/
|
|
32
50
|
badge?: string | number;
|
|
51
|
+
/**
|
|
52
|
+
* Notification indicator. Pass a number for a count badge, or true for a dot.
|
|
53
|
+
*/
|
|
54
|
+
notification?: number | boolean;
|
|
33
55
|
/**
|
|
34
56
|
* Link href (for navigation)
|
|
35
57
|
*/
|
|
@@ -58,19 +80,31 @@ export interface NavItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
58
80
|
*/
|
|
59
81
|
isDanger?: boolean;
|
|
60
82
|
/**
|
|
61
|
-
* Whether the item has a submenu
|
|
83
|
+
* Whether the item has a submenu (manual mode).
|
|
84
|
+
* If `items` is provided, this is set automatically.
|
|
62
85
|
* @default false
|
|
63
86
|
*/
|
|
64
87
|
hasSubmenu?: boolean;
|
|
65
88
|
/**
|
|
66
|
-
* Whether the submenu is expanded (for
|
|
89
|
+
* Whether the submenu is expanded (controlled mode for manual submenus).
|
|
67
90
|
* @default false
|
|
68
91
|
*/
|
|
69
92
|
expanded?: boolean;
|
|
70
93
|
/**
|
|
71
|
-
* Callback when submenu is toggled
|
|
94
|
+
* Callback when submenu is toggled (manual mode)
|
|
72
95
|
*/
|
|
73
96
|
onToggleExpanded?: () => void;
|
|
97
|
+
/**
|
|
98
|
+
* Self-contained submenu items. When provided, NavItem manages its own
|
|
99
|
+
* expand/collapse state internally. Children items automatically get
|
|
100
|
+
* level={parentLevel + 1}.
|
|
101
|
+
*/
|
|
102
|
+
items?: NavItemSubmenuItem[];
|
|
103
|
+
/**
|
|
104
|
+
* Whether the self-contained submenu is expanded by default
|
|
105
|
+
* @default false
|
|
106
|
+
*/
|
|
107
|
+
defaultExpanded?: boolean;
|
|
74
108
|
/**
|
|
75
109
|
* Nesting level for indentation (0, 1, 2)
|
|
76
110
|
* @default 0
|
|
@@ -111,6 +145,10 @@ export interface NavItemTheme {
|
|
|
111
145
|
container: string;
|
|
112
146
|
icon: string;
|
|
113
147
|
};
|
|
148
|
+
activeIndicator?: string;
|
|
149
|
+
notificationDot?: string;
|
|
150
|
+
notificationBadge?: string;
|
|
151
|
+
danger?: string;
|
|
114
152
|
}
|
|
115
153
|
export interface NavItemThemeOverrides {
|
|
116
154
|
baseStyle?: string;
|
|
@@ -137,32 +175,8 @@ export interface NavItemThemeOverrides {
|
|
|
137
175
|
container: string;
|
|
138
176
|
icon: string;
|
|
139
177
|
}>;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
baseStyle: string;
|
|
144
|
-
icon: string;
|
|
145
|
-
text: string;
|
|
146
|
-
states: {
|
|
147
|
-
default: string;
|
|
148
|
-
active: string;
|
|
149
|
-
disabled: string;
|
|
150
|
-
hover: string;
|
|
151
|
-
};
|
|
152
|
-
iconStates?: {
|
|
153
|
-
default?: string;
|
|
154
|
-
active?: string;
|
|
155
|
-
disabled?: string;
|
|
156
|
-
hover?: string;
|
|
157
|
-
};
|
|
158
|
-
levels: {
|
|
159
|
-
0: string;
|
|
160
|
-
1: string;
|
|
161
|
-
2: string;
|
|
162
|
-
};
|
|
163
|
-
collapsed: {
|
|
164
|
-
container: string;
|
|
165
|
-
icon: string;
|
|
166
|
-
};
|
|
178
|
+
activeIndicator?: string;
|
|
179
|
+
notificationDot?: string;
|
|
180
|
+
notificationBadge?: string;
|
|
167
181
|
danger?: string;
|
|
168
182
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { NavItem } from './NavItem';
|
|
2
|
-
export type { NavItemProps, NavItemTheme, NavItemThemeOverrides } from './NavItem.types';
|
|
2
|
+
export type { NavItemProps, NavItemSubmenuItem, NavItemTheme, NavItemThemeOverrides } from './NavItem.types';
|