@flikk/ui 1.0.0-beta.25 → 1.0.0-beta.27
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 +4 -0
- package/dist/components/ai/PromptInput/VoiceRecorder.js +4 -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 +5 -5
- package/dist/components/core/Calendar/Calendar.theme.js +1 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
- package/dist/components/core/CommandPalette/CommandItem.js +4 -5
- package/dist/components/core/CommandPalette/CommandPalette.js +1 -1
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +11 -13
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +0 -2
- package/dist/components/core/ContextMenu/ContextMenu.theme.js +13 -14
- package/dist/components/core/DotSeparator/DotSeparator.d.ts +3 -0
- package/dist/components/core/DotSeparator/DotSeparator.js +19 -0
- package/dist/components/core/DotSeparator/DotSeparator.theme.d.ts +2 -0
- package/dist/components/core/DotSeparator/DotSeparator.theme.js +10 -0
- package/dist/components/core/DotSeparator/DotSeparator.types.d.ts +25 -0
- package/dist/components/core/DotSeparator/index.d.ts +3 -0
- package/dist/components/core/Drawer/Drawer.theme.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +4 -5
- package/dist/components/core/Dropdown/DropdownItem.js +1 -1
- package/dist/components/core/Dropdown/DropdownMenu.js +1 -1
- 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/MenuItem/MenuItem.js +5 -2
- package/dist/components/core/MenuItem/MenuItem.theme.d.ts +6 -0
- package/dist/components/core/MenuItem/MenuItem.theme.js +19 -16
- package/dist/components/core/MenuItem/MenuItem.types.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 +187 -68
- package/dist/components/core/NavItem/NavItem.theme.js +31 -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/Popover/Popover.types.d.ts +6 -2
- package/dist/components/core/Popover/PopoverBody.js +10 -3
- 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 +3 -1
- package/dist/components/core/Sidebar/Sidebar.js +53 -16
- package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +125 -8
- package/dist/components/core/Sidebar/SidebarCard.d.ts +25 -0
- package/dist/components/core/Sidebar/SidebarCard.js +68 -0
- package/dist/components/core/Sidebar/SidebarCard.theme.d.ts +8 -0
- package/dist/components/core/Sidebar/SidebarCard.theme.js +18 -0
- package/dist/components/core/Sidebar/SidebarContent.js +10 -6
- package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
- package/dist/components/core/Sidebar/SidebarContext.js +43 -20
- package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
- package/dist/components/core/Sidebar/SidebarHeader.js +12 -9
- package/dist/components/core/Sidebar/SidebarLogo.js +39 -8
- package/dist/components/core/Sidebar/SidebarNav.js +4 -3
- package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
- package/dist/components/core/Sidebar/SidebarSearch.d.ts +22 -0
- package/dist/components/core/Sidebar/SidebarSearch.js +135 -0
- 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 +4 -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.js +29 -5
- package/dist/components/core/Tag/Tag.theme.js +14 -9
- package/dist/components/core/Tag/Tag.types.d.ts +7 -0
- package/dist/components/core/Tooltip/Tooltip.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -2
- package/dist/components/core/index.d.ts +2 -0
- package/dist/components/core/index.js +6 -2
- package/dist/components/data-display/GanttChart/GanttChart.d.ts +1 -1
- package/dist/components/data-display/GanttChart/GanttChart.js +31 -4
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +19 -17
- package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +19 -0
- package/dist/components/data-display/GanttChart/GanttChart.utils.js +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.d.ts +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +3 -3
- package/dist/components/data-display/GanttChart/GanttDependencyArrows.js +7 -4
- package/dist/components/data-display/GanttChart/GanttMilestone.js +5 -5
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
- package/dist/components/data-display/GanttChart/GanttTablePanel.js +1 -1
- package/dist/components/data-display/GanttChart/GanttTableTaskRow.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTaskBar.js +37 -16
- package/dist/components/data-display/GanttChart/GanttTimelineGrid.js +2 -1
- package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +35 -11
- package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
- package/dist/components/data-display/GanttChart/hooks/useGanttDrag.js +3 -2
- package/dist/components/data-display/GanttChart/index.d.ts +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 +14 -6
- 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.d.ts +6 -17
- package/dist/components/effects/Aurora/Aurora.js +310 -105
- package/dist/components/effects/Aurora/Aurora.types.d.ts +6 -0
- 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/Combobox/Combobox.js +1 -1
- package/dist/components/forms/Combobox/Combobox.theme.js +2 -4
- package/dist/components/forms/CronInput/CronInput.js +1 -1
- package/dist/components/forms/CronInput/CronInput.theme.js +22 -31
- package/dist/components/forms/DatePicker/DatePicker.theme.js +11 -18
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +7 -14
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
- package/dist/components/forms/FileUpload/FileUpload.animations.d.ts +29 -0
- package/dist/components/forms/FileUpload/FileUpload.animations.js +19 -0
- package/dist/components/forms/FileUpload/FileUpload.js +88 -49
- package/dist/components/forms/FileUpload/FileUpload.theme.d.ts +2 -10
- package/dist/components/forms/FileUpload/FileUpload.theme.js +16 -30
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +26 -10
- 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 +16 -15
- package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
- package/dist/components/forms/Mention/Mention.js +152 -57
- package/dist/components/forms/Mention/Mention.theme.js +16 -23
- package/dist/components/forms/Mention/Mention.types.d.ts +12 -12
- package/dist/components/forms/Mention/Mention.utils.js +32 -4
- 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 +2 -2
- package/dist/components/forms/Select/Select.theme.js +3 -3
- 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 +4 -0
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
- package/dist/components/forms/forms.theme.js +24 -19
- package/dist/components/forms/index.js +4 -0
- package/dist/components/layout/AppShell/AppShell.d.ts +10 -0
- package/dist/components/layout/AppShell/AppShell.js +84 -0
- package/dist/components/layout/AppShell/AppShell.theme.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShell.theme.js +42 -0
- package/dist/components/layout/AppShell/AppShell.types.d.ts +127 -0
- package/dist/components/layout/AppShell/AppShellContext.d.ts +14 -0
- package/dist/components/layout/AppShell/AppShellContext.js +19 -0
- package/dist/components/layout/AppShell/AppShellFooter.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellFooter.js +12 -0
- package/dist/components/layout/AppShell/AppShellHeader.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellHeader.js +12 -0
- package/dist/components/layout/AppShell/AppShellMain.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellMain.js +19 -0
- package/dist/components/layout/AppShell/AppShellRightPanel.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellRightPanel.js +16 -0
- package/dist/components/layout/AppShell/AppShellSidebar.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellSidebar.js +16 -0
- package/dist/components/layout/AppShell/AppShellTopbar.d.ts +3 -0
- package/dist/components/layout/AppShell/AppShellTopbar.js +12 -0
- package/dist/components/layout/AppShell/index.d.ts +3 -0
- package/dist/components/layout/FormLayout/FormLayout.js +9 -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/components/layout/index.d.ts +1 -0
- package/dist/components/layout/index.js +2 -0
- package/dist/hooks/useSelectPortal.d.ts +10 -2
- package/dist/hooks/useSelectPortal.js +45 -24
- package/dist/index.js +8 -2
- package/dist/registry.json +10021 -0
- package/dist/styles.css +1 -1
- package/dist/utils/dateUtils.js +11 -1
- package/package.json +4 -2
- package/src/global.scss +29 -8
- package/src/styles/theme.css +174 -100
- package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
- package/dist/components/core/Sidebar/SidebarSubmenu.js +0 -12
|
@@ -9,12 +9,15 @@ import { menuItemTheme } from './MenuItem.theme.js';
|
|
|
9
9
|
* Handles visual layout, base styling, keyboard activation, and focus tracking.
|
|
10
10
|
* Context-specific behavior (selection, checked state) is handled by consumers.
|
|
11
11
|
*/
|
|
12
|
-
const MenuItem = React__default.forwardRef(({ children, className, startContent, endContent, subtitle, shortcut, disabled = false, isDanger = false, onActivate, theme: themeOverrides, role = "menuitem", ...props }, ref) => {
|
|
12
|
+
const MenuItem = React__default.forwardRef(({ children, className, startContent, endContent, subtitle, shortcut, disabled = false, isDanger = false, selected = false, onActivate, theme: themeOverrides, role = "menuitem", ...props }, ref) => {
|
|
13
13
|
const [isFocused, setIsFocused] = useState(false);
|
|
14
14
|
const theme = {
|
|
15
15
|
...menuItemTheme,
|
|
16
16
|
...themeOverrides,
|
|
17
17
|
};
|
|
18
|
+
// Resolve state to plain utility classes (not data-[state] variants) so a
|
|
19
|
+
// consumer `className` always wins the cascade. See MenuItem.theme.ts.
|
|
20
|
+
const stateKey = disabled ? "disabled" : selected ? "selected" : "default";
|
|
18
21
|
const handleClick = useCallback(() => {
|
|
19
22
|
if (disabled)
|
|
20
23
|
return;
|
|
@@ -26,7 +29,7 @@ const MenuItem = React__default.forwardRef(({ children, className, startContent,
|
|
|
26
29
|
handleClick();
|
|
27
30
|
}
|
|
28
31
|
}, [handleClick]);
|
|
29
|
-
return (jsxs("div", { ref: ref, role: role, tabIndex: disabled ? undefined : -1, "aria-disabled": disabled, "data-focused": isFocused, "data-disabled": disabled, className: cn(theme.baseStyle, theme.
|
|
32
|
+
return (jsxs("div", { ref: ref, role: role, tabIndex: disabled ? undefined : -1, "aria-disabled": disabled, "data-focused": isFocused, "data-disabled": disabled, "data-selected": selected, className: cn(theme.baseStyle, theme.states[stateKey], isFocused && !disabled && !selected && theme.focusedStyle, isDanger && theme.dangerStyle, theme.focusStyle, className), onClick: handleClick, onKeyDown: handleKeyDown, onFocus: () => setIsFocused(true), onBlur: () => setIsFocused(false), ...props, children: [startContent && (jsx("div", { className: "flex items-center justify-center flex-shrink-0", children: startContent })), jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: "truncate", children: children }), shortcut && (jsx("span", { className: "ml-auto flex-shrink-0", children: shortcut }))] }), subtitle && (typeof subtitle === 'string'
|
|
30
33
|
? jsx("div", { className: theme.subtitleStyle, children: subtitle })
|
|
31
34
|
: subtitle)] }), endContent && (jsx("div", { className: "flex items-center justify-center ml-2", children: endContent }))] }));
|
|
32
35
|
});
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
const menuItemTheme = {
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
" "
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
"
|
|
2
|
+
// Structural base only — layout, spacing, radius, transition. No state colors:
|
|
3
|
+
// those are resolved in JS (see `states`) so a consumer `className` always wins.
|
|
4
|
+
baseStyle: "relative flex items-center gap-2 px-3 py-1.5 text-sm font-[500] cursor-pointer outline-none select-none rounded-[var(--radius-base)] transition-colors duration-300 mb-px last-of-type:mb-0",
|
|
5
|
+
// State styling resolved in JS to plain utility classes so `className` overrides
|
|
6
|
+
// win (data-[state] variants carry attribute-selector specificity and can't be
|
|
7
|
+
// overridden by a plain className — see Button.theme.ts / NavItem.theme.ts for
|
|
8
|
+
// the same rationale). data-* attributes are still emitted as hooks for
|
|
9
|
+
// consumer CSS + tests, just not used for styling here.
|
|
10
|
+
states: {
|
|
11
|
+
default: "text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)]",
|
|
12
|
+
selected: "bg-[var(--color-primary)]/5 text-[var(--color-primary)] " +
|
|
13
|
+
"dark:bg-white/10 dark:text-[var(--color-text-primary)]",
|
|
14
|
+
disabled: "opacity-60 cursor-not-allowed pointer-events-none",
|
|
15
|
+
},
|
|
16
|
+
// Keyboard-focus highlight (item received roving focus) — same fill as hover.
|
|
17
|
+
focusedStyle: "bg-[var(--color-surface-hover)]",
|
|
15
18
|
// Danger item styling (e.g., Delete actions)
|
|
16
19
|
dangerStyle: "text-[var(--color-danger)] hover:bg-[var(--color-danger)] hover:text-[var(--color-danger-contrast)] " +
|
|
17
20
|
"dark:text-[var(--color-danger-400)] dark:hover:bg-[var(--color-danger-600)] dark:hover:text-[var(--color-danger-contrast)]",
|
|
18
21
|
// Focus visible styling for keyboard navigation
|
|
19
|
-
focusStyle: "focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 " +
|
|
20
|
-
"dark:focus-visible:ring-[var(--color-primary-400)]
|
|
22
|
+
focusStyle: "focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)] " +
|
|
23
|
+
"dark:focus-visible:ring-[var(--color-primary-400)]",
|
|
21
24
|
// Description text below the label
|
|
22
|
-
subtitleStyle: "text-
|
|
25
|
+
subtitleStyle: "text-sm text-[var(--color-text-secondary)]/80 mt-0.5 truncate",
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
export { menuItemTheme };
|
|
@@ -17,6 +17,8 @@ export interface MenuItemProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
17
17
|
disabled?: boolean;
|
|
18
18
|
/** Whether the item has danger/destructive styling */
|
|
19
19
|
isDanger?: boolean;
|
|
20
|
+
/** Whether the item is in a selected state (drives selected styling) */
|
|
21
|
+
selected?: boolean;
|
|
20
22
|
/** Called on click or Enter/Space keyboard activation */
|
|
21
23
|
onActivate?: () => void;
|
|
22
24
|
/** Theme overrides for styling */
|
|
@@ -54,7 +54,7 @@ const messageTheme = {
|
|
|
54
54
|
linkStyle: "flex flex-col rounded-lg border border-[var(--color-border)] overflow-hidden text-xs max-w-xs",
|
|
55
55
|
// Reactions styles
|
|
56
56
|
reactionsContainerStyle: "flex flex-wrap items-center gap-1.5 mt-1.5",
|
|
57
|
-
reactionPillStyle: "inline-flex items-center gap-1 rounded-full border border-[var(--color-border)] bg-[var(--color-background-secondary)] px-2 py-0.5 text-xs cursor-pointer transition-colors hover:bg-[var(--color-
|
|
57
|
+
reactionPillStyle: "inline-flex items-center gap-1 rounded-full border border-[var(--color-border)] bg-[var(--color-background-secondary)] px-2 py-0.5 text-xs cursor-pointer transition-colors hover:bg-[var(--color-surface-hover)]",
|
|
58
58
|
reactionPillActiveStyle: "border-[var(--color-primary-200)] bg-[var(--color-primary-50)] text-[var(--color-primary)]",
|
|
59
59
|
// Reply/quote styles
|
|
60
60
|
replyStyle: "border-l-2 border-[var(--color-border)] pl-2.5 mb-2 text-xs text-[var(--color-text-muted)]",
|
|
@@ -3,7 +3,7 @@ const modalTheme = {
|
|
|
3
3
|
overlay: "",
|
|
4
4
|
container: "relative w-full max-h-[calc(100vh-2rem)] rounded-[var(--modal-radius)] shadow-2xl shadow-black/30 flex flex-col my-4 min-h-0 overflow-hidden",
|
|
5
5
|
containerVariants: {
|
|
6
|
-
default: "bg-
|
|
6
|
+
default: "bg-[var(--color-surface-raised)] ring-4 !ring-white/10 dark:bg-[var(--color-surface-raised)] border border-[var(--color-border)]",
|
|
7
7
|
translucent: "bg-gradient-to-b from-[var(--color-background)]/90 to-[var(--color-background)]/70 backdrop-blur-xl ring-4 ring-white/20 border-t border-[var(--color-border)]",
|
|
8
8
|
},
|
|
9
9
|
header: "flex items-start justify-between p-6 pb-0 shrink-0 border-b border-transparent gap-6 relative",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { NavItemProps } from "./NavItem.types";
|
|
3
|
-
export declare const NavItem: React.ForwardRefExoticComponent<NavItemProps & React.RefAttributes<
|
|
3
|
+
export declare const NavItem: React.ForwardRefExoticComponent<NavItemProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useId, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import React__default, { useId, useRef, useState, useCallback, useEffect, useMemo } from 'react';
|
|
3
3
|
import { cn } from '../../../utils/cn.js';
|
|
4
|
+
import { Dropdown } from '../Dropdown/Dropdown.js';
|
|
5
|
+
import '../Dropdown/DropdownTrigger.js';
|
|
6
|
+
import '../Dropdown/DropdownMenu.js';
|
|
7
|
+
import '../Dropdown/DropdownItem.js';
|
|
8
|
+
import '../Dropdown/DropdownSection.js';
|
|
9
|
+
import '../Dropdown/DropdownSeparator.js';
|
|
10
|
+
import '../Dropdown/Dropdown.theme.js';
|
|
4
11
|
import { navItemTheme } from './NavItem.theme.js';
|
|
5
|
-
import { useSidebarContext, useAccordionNavContext } from '../Sidebar/SidebarContext.js';
|
|
12
|
+
import { useSidebarContext, useAccordionNavContext, useSidebarDensity } from '../Sidebar/SidebarContext.js';
|
|
6
13
|
import { Tooltip } from '../Tooltip/Tooltip.js';
|
|
7
14
|
import '../Tooltip/Tooltip.animations.js';
|
|
8
15
|
import { Badge } from '../Badge/Badge.js';
|
|
9
16
|
import { ChevronDownIcon } from '@heroicons/react/20/solid';
|
|
10
17
|
import { AnimatePresence, motion } from 'motion/react';
|
|
11
18
|
|
|
12
|
-
const NavItem = React__default.forwardRef(({ children, startContent:
|
|
13
|
-
badge, // Legacy
|
|
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) => {
|
|
19
|
+
const NavItem = React__default.forwardRef(({ children, startContent, endContent, notification, description, shortcut, href, onClick, active = false, isDisabled = false, hasSubmenu: propHasSubmenu = false, expanded: controlledExpanded, onToggleExpanded, items, defaultExpanded = false, level = 0, showTooltipWhenCollapsed = true, className, theme: customTheme = {}, ...props }, ref) => {
|
|
15
20
|
var _a, _b;
|
|
16
21
|
const sidebarContext = useSidebarContext();
|
|
17
22
|
const accordionNav = useAccordionNavContext();
|
|
@@ -19,8 +24,53 @@ notification, description, shortcut, href, onClick, active = false, disabled = f
|
|
|
19
24
|
const collapsed = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.collapsed) || false;
|
|
20
25
|
const isInSidebar = !!sidebarContext;
|
|
21
26
|
const isMobile = (sidebarContext === null || sidebarContext === void 0 ? void 0 : sidebarContext.isMobile) || false;
|
|
27
|
+
const density = useSidebarDensity();
|
|
28
|
+
const defaultExpandedRegisteredRef = useRef(false);
|
|
22
29
|
// Self-managed submenu state
|
|
23
30
|
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
|
|
31
|
+
// Dropdown open state for collapsed sidebar hover submenu
|
|
32
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
33
|
+
const hoverTimeoutRef = useRef(null);
|
|
34
|
+
const handleMouseEnter = useCallback(() => {
|
|
35
|
+
if (hoverTimeoutRef.current) {
|
|
36
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
37
|
+
hoverTimeoutRef.current = null;
|
|
38
|
+
}
|
|
39
|
+
setIsDropdownOpen(true);
|
|
40
|
+
}, []);
|
|
41
|
+
const handleMouseLeave = useCallback(() => {
|
|
42
|
+
if (hoverTimeoutRef.current) {
|
|
43
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
44
|
+
}
|
|
45
|
+
hoverTimeoutRef.current = setTimeout(() => {
|
|
46
|
+
setIsDropdownOpen(false);
|
|
47
|
+
}, 150);
|
|
48
|
+
}, []);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
return () => {
|
|
51
|
+
if (hoverTimeoutRef.current) {
|
|
52
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}, []);
|
|
56
|
+
// Resolve active keys for Dropdown selection mode
|
|
57
|
+
const activeKeys = useMemo(() => {
|
|
58
|
+
if (!items)
|
|
59
|
+
return [];
|
|
60
|
+
return items
|
|
61
|
+
.map((item, idx) => (item.active ? `sub-item-${idx}` : null))
|
|
62
|
+
.filter((key) => key !== null);
|
|
63
|
+
}, [items]);
|
|
64
|
+
const handleItemAction = useCallback((item) => {
|
|
65
|
+
if (item.isDisabled)
|
|
66
|
+
return;
|
|
67
|
+
if (item.onClick) {
|
|
68
|
+
item.onClick();
|
|
69
|
+
}
|
|
70
|
+
else if (item.href) {
|
|
71
|
+
window.location.href = item.href;
|
|
72
|
+
}
|
|
73
|
+
}, []);
|
|
24
74
|
// Determine if this item has a submenu
|
|
25
75
|
const hasSubmenu = propHasSubmenu || (items && items.length > 0);
|
|
26
76
|
// Determine expanded state: controlled vs accordion vs internal
|
|
@@ -52,15 +102,21 @@ notification, description, shortcut, href, onClick, active = false, disabled = f
|
|
|
52
102
|
setInternalExpanded(prev => !prev);
|
|
53
103
|
}
|
|
54
104
|
}, [isControlled, isAccordionManaged, isExpanded, onToggleExpanded, accordionNav, stableId]);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!defaultExpandedRegisteredRef.current &&
|
|
107
|
+
!isControlled &&
|
|
108
|
+
isAccordionManaged &&
|
|
109
|
+
hasSubmenu &&
|
|
110
|
+
defaultExpanded &&
|
|
111
|
+
accordionNav.openId === null) {
|
|
112
|
+
defaultExpandedRegisteredRef.current = true;
|
|
113
|
+
accordionNav.register(stableId);
|
|
114
|
+
}
|
|
115
|
+
}, [accordionNav, defaultExpanded, hasSubmenu, isAccordionManaged, isControlled, stableId]);
|
|
116
|
+
// Whether there is an active notification (dot or non-zero count)
|
|
117
|
+
const hasNotification = notification === true ||
|
|
118
|
+
(typeof notification === "number" && notification > 0);
|
|
119
|
+
// Expanded view: a dot for boolean, a count Badge for numbers
|
|
64
120
|
const notificationElement = useMemo(() => {
|
|
65
121
|
if (notification === undefined || notification === false)
|
|
66
122
|
return null;
|
|
@@ -68,10 +124,10 @@ notification, description, shortcut, href, onClick, active = false, disabled = f
|
|
|
68
124
|
return (jsx("span", { className: cn(navItemTheme.notificationDot, customTheme.notificationDot) }));
|
|
69
125
|
}
|
|
70
126
|
if (typeof notification === "number" && notification > 0) {
|
|
71
|
-
return (jsx(
|
|
127
|
+
return (jsx(Badge, { color: "neutral", variant: "soft", size: "sm", children: notification > 99 ? "99+" : notification }));
|
|
72
128
|
}
|
|
73
129
|
return null;
|
|
74
|
-
}, [notification, customTheme.notificationDot
|
|
130
|
+
}, [notification, customTheme.notificationDot]);
|
|
75
131
|
const theme = useMemo(() => ({
|
|
76
132
|
...navItemTheme,
|
|
77
133
|
...customTheme,
|
|
@@ -79,11 +135,11 @@ notification, description, shortcut, href, onClick, active = false, disabled = f
|
|
|
79
135
|
iconStates: { ...navItemTheme.iconStates, ...customTheme.iconStates },
|
|
80
136
|
levels: { ...navItemTheme.levels, ...customTheme.levels },
|
|
81
137
|
collapsed: { ...navItemTheme.collapsed, ...customTheme.collapsed },
|
|
82
|
-
|
|
138
|
+
density: { ...navItemTheme.density, ...customTheme.density },
|
|
83
139
|
}), [customTheme]);
|
|
84
140
|
// Handle click events
|
|
85
141
|
const handleClick = (e) => {
|
|
86
|
-
if (
|
|
142
|
+
if (isDisabled) {
|
|
87
143
|
e.preventDefault();
|
|
88
144
|
return;
|
|
89
145
|
}
|
|
@@ -108,80 +164,143 @@ notification, description, shortcut, href, onClick, active = false, disabled = f
|
|
|
108
164
|
};
|
|
109
165
|
const isActive = active;
|
|
110
166
|
const isCollapsedView = isInSidebar && collapsed && !isMobile;
|
|
111
|
-
|
|
167
|
+
const isHoveredOrDropdownOpen = isCollapsedView ? isDropdownOpen : (hasSubmenu && isExpanded);
|
|
168
|
+
// State styling resolved in JS to plain utility classes so `className` overrides win
|
|
169
|
+
// (data-[active]/data-[disabled] are still emitted below as styling/test hooks)
|
|
112
170
|
const getStateClasses = () => {
|
|
113
|
-
if (
|
|
171
|
+
if (isDisabled)
|
|
114
172
|
return theme.states.disabled;
|
|
115
173
|
if (isActive)
|
|
116
174
|
return theme.states.active;
|
|
117
175
|
return theme.states.default;
|
|
118
176
|
};
|
|
119
|
-
|
|
120
|
-
const dangerClasses = isDanger ? theme.danger : "";
|
|
121
|
-
// Active indicator
|
|
122
|
-
const activeIndicatorClasses = isActive && level === 0 ? theme.activeIndicator : "";
|
|
123
|
-
const containerClasses = cn(theme.baseStyle, getStateClasses(), theme.levels[level], isCollapsedView && ((_a = theme.collapsed) === null || _a === void 0 ? void 0 : _a.container), dangerClasses, activeIndicatorClasses, className);
|
|
124
|
-
// Get icon state classes
|
|
177
|
+
const containerClasses = cn(theme.baseStyle, theme.focusStyle, getStateClasses(), theme.levels[level], density === "compact" && theme.density.compact, hasSubmenu && isExpanded && !isActive && "text-[var(--color-text-primary)]", isCollapsedView && ((_a = theme.collapsed) === null || _a === void 0 ? void 0 : _a.container), isHoveredOrDropdownOpen && "bg-[var(--color-surface-hover)] text-[var(--color-text-primary)]", className);
|
|
125
178
|
const getIconStateClasses = () => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return (_a = theme.iconStates) === null || _a === void 0 ? void 0 : _a.disabled;
|
|
179
|
+
if (isDisabled)
|
|
180
|
+
return theme.iconStates.disabled;
|
|
129
181
|
if (isActive)
|
|
130
|
-
return
|
|
131
|
-
return
|
|
182
|
+
return theme.iconStates.active;
|
|
183
|
+
return theme.iconStates.default;
|
|
132
184
|
};
|
|
133
|
-
|
|
134
|
-
const iconClasses = cn(isCollapsedView ? (_b = theme.collapsed) === null || _b === void 0 ? void 0 : _b.icon : theme.icon, getIconStateClasses());
|
|
185
|
+
const iconClasses = cn(theme.icon, getIconStateClasses(), hasSubmenu && isExpanded && !isActive && "opacity-100", isCollapsedView && ((_b = theme.collapsed) === null || _b === void 0 ? void 0 : _b.icon), isHoveredOrDropdownOpen && "opacity-100");
|
|
135
186
|
const textClasses = cn(theme.text);
|
|
136
187
|
// Chevron for submenu
|
|
137
|
-
const chevron = hasSubmenu && !isCollapsedView ? (jsx(ChevronDownIcon, { className: cn("size-4 transition-transform duration-200 text-[var(--color-text-muted)]", isExpanded && "rotate-180") })) : null;
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
188
|
+
const chevron = hasSubmenu && !isCollapsedView ? (jsx(ChevronDownIcon, { strokeWidth: 2, className: cn("size-4 transition-transform duration-200 text-[var(--color-text-muted)]", isExpanded && "text-[var(--color-text-primary)]", isExpanded && "rotate-180") })) : null;
|
|
189
|
+
// Strip drag/animation DOM handlers that collide with motion's prop types.
|
|
190
|
+
// `onSubmit`/`onSubmitCapture` are stripped too: NavItem renders as either
|
|
191
|
+
// `motion.a` or `motion.button`, but `NavItemProps extends HTMLAttributes<HTMLElement>`
|
|
192
|
+
// types these as `SubmitEventHandler<HTMLElement>`. Under react@canary types
|
|
193
|
+
// (e.g. Next 16 consumers building from source), `SubmitEvent` gains a
|
|
194
|
+
// T-dependent field, so the generic-element handler is no longer assignable to
|
|
195
|
+
// motion's element-specific `HTMLMotionProps<"a">` — a build-time type error.
|
|
196
|
+
// A NavItem never submits a form, so dropping these is behaviour-neutral.
|
|
197
|
+
const { onAnimationStart, onAnimationEnd, onDrag, onDragStart, onDragEnd, onSubmit, onSubmitCapture, ...motionSafeProps } = props;
|
|
198
|
+
const interactionProps = {
|
|
199
|
+
className: cn("relative overflow-hidden", containerClasses),
|
|
200
|
+
"aria-disabled": isDisabled || undefined,
|
|
201
|
+
"aria-expanded": hasSubmenu ? isExpanded : undefined,
|
|
202
|
+
"data-active": isActive,
|
|
203
|
+
"data-disabled": isDisabled,
|
|
204
|
+
"data-level": level,
|
|
205
|
+
onClick: handleClick,
|
|
206
|
+
whileHover: isDisabled ? undefined : (isCollapsedView ? { scale: 1.05 } : undefined),
|
|
207
|
+
whileTap: isDisabled ? undefined : { scale: 0.98 },
|
|
208
|
+
transition: { type: "spring", stiffness: 400, damping: 25 },
|
|
209
|
+
// Note: tried Framer Motion's canonical `layout` + `LayoutGroup` +
|
|
210
|
+
// `mode="popLayout"` pattern for the row's height shrink — transforms
|
|
211
|
+
// *do* fire (verified via getComputedStyle), but the parent Sidebar's
|
|
212
|
+
// concurrent `motion.aside` width animation re-triggers measurements
|
|
213
|
+
// every frame, which keeps resetting the layout animation before it
|
|
214
|
+
// settles. The row visibly freezes mid-transform. Animating the inner
|
|
215
|
+
// motion.div's height directly (see AnimatePresence below) sidesteps the
|
|
216
|
+
// concurrent-measurement problem and produces a smooth ~200ms shrink.
|
|
217
|
+
...motionSafeProps,
|
|
218
|
+
};
|
|
219
|
+
const innerContent = (jsxs(Fragment, { children: [startContent && (jsxs("div", { className: "flex items-center justify-center flex-shrink-0 relative", children: [React__default.isValidElement(startContent)
|
|
145
220
|
? React__default.cloneElement(startContent, {
|
|
146
221
|
className: cn(iconClasses, startContent.props.className),
|
|
147
222
|
})
|
|
148
|
-
: startContent, isCollapsedView &&
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
flex:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
flexShrink: 0,
|
|
160
|
-
opacity: isCollapsedView ? 0 : 1,
|
|
161
|
-
transition: 'grid-template-columns 0.18s cubic-bezier(0.4,0,0.2,1), opacity 0.12s ease-out',
|
|
162
|
-
}, children: jsxs("div", { className: "flex items-center gap-1.5", style: { overflow: 'hidden', minWidth: 0 }, children: [notificationElement, endContent, chevron] }) }), isCollapsedView && (jsx("span", { className: "sr-only", children: children }))] }));
|
|
223
|
+
: startContent, isCollapsedView && hasNotification && (jsx("span", { className: cn("absolute -top-0.5 -right-0.5 ring-2 ring-[var(--color-surface)]", navItemTheme.notificationDot, customTheme.notificationDot), "aria-hidden": "true" }))] })), jsx(AnimatePresence, { initial: false, children: !isCollapsedView && (jsxs(motion.div, { initial: { opacity: 0, x: -4, height: 0 }, animate: { opacity: 1, x: 0, height: "auto" }, exit: { opacity: 0, x: -4, height: 0 }, transition: {
|
|
224
|
+
opacity: { duration: 0.15, ease: [0.4, 0, 0.2, 1] },
|
|
225
|
+
x: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
|
|
226
|
+
height: { duration: 0.2, ease: [0.4, 0, 0.2, 1] },
|
|
227
|
+
}, className: "flex-1 min-w-0 flex flex-col overflow-hidden whitespace-nowrap", children: [jsxs("span", { className: "flex items-center justify-between gap-2 w-full min-w-0", children: [jsx("span", { className: cn("truncate flex-1 min-w-0", textClasses), children: children }), jsxs("span", { className: "flex items-center gap-1.5 flex-shrink-0 ml-2", children: [shortcut && (jsx("kbd", { className: "hidden group-hover:inline-flex h-[18px] items-center text-[10px] text-[var(--color-text-muted)] font-sans border border-[var(--color-border)] rounded px-1.5 leading-none flex-shrink-0", children: shortcut })), notificationElement, endContent, chevron] })] }), description && (jsx("span", { className: "block text-xs text-[var(--color-text-muted)] mt-0.5 truncate w-full", children: description }))] }, "expanded-content")) }), isCollapsedView && (jsxs("span", { className: "sr-only", children: [children, typeof notification === "number" && notification > 0
|
|
228
|
+
? ` (${notification} unread)`
|
|
229
|
+
: notification === true
|
|
230
|
+
? " (unread)"
|
|
231
|
+
: null] }))] }));
|
|
232
|
+
// Content component
|
|
233
|
+
const content = href && !hasSubmenu ? (jsx(motion.a, { ref: ref, href: isDisabled ? undefined : href, tabIndex: isDisabled ? -1 : undefined, ...interactionProps, children: innerContent })) : (jsx(motion.button, { ref: ref, type: "button", disabled: isDisabled, ...interactionProps, children: innerContent }));
|
|
163
234
|
// Self-contained submenu
|
|
164
235
|
const submenu = hasSubmenu && items && items.length > 0 && !isCollapsedView ? (jsx(AnimatePresence, { initial: false, children: isExpanded && (jsx(motion.div, { initial: "collapsed", animate: "open", exit: "collapsed", variants: {
|
|
165
|
-
open: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
236
|
+
open: {
|
|
237
|
+
opacity: 1,
|
|
238
|
+
height: "auto",
|
|
239
|
+
transition: {
|
|
240
|
+
height: { duration: 0.18, ease: "easeOut" },
|
|
241
|
+
opacity: { duration: 0.12 },
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
collapsed: {
|
|
245
|
+
opacity: 0,
|
|
246
|
+
height: 0,
|
|
247
|
+
transition: {
|
|
248
|
+
height: { duration: 0.16, ease: "easeInOut" },
|
|
249
|
+
opacity: { duration: 0.1 },
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
}, className: "overflow-hidden", children: jsx("div", { className: "relative space-y-0.5 pl-6", children: items.map((item, idx) => (jsxs(motion.div, { className: "relative", children: [idx < items.length - 1 && (jsx(motion.span, { initial: { opacity: 0, scaleY: 0 }, animate: { opacity: 1, scaleY: 1 }, exit: { opacity: 0, scaleY: 0 }, transition: {
|
|
253
|
+
delay: idx * 0.055,
|
|
254
|
+
duration: 0.16,
|
|
255
|
+
ease: "easeOut",
|
|
256
|
+
}, className: "absolute left-[-6px] top-1/2 h-[calc(100%+0.125rem)] w-px origin-top bg-[var(--color-neutral-400)] dark:bg-[var(--color-neutral-600)]", "aria-hidden": "true" })), jsx(motion.span, { initial: { opacity: 0, scale: 0.5 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.5 }, transition: {
|
|
257
|
+
delay: idx * 0.055,
|
|
258
|
+
type: "spring",
|
|
259
|
+
stiffness: 520,
|
|
260
|
+
damping: 30,
|
|
261
|
+
mass: 0.6,
|
|
262
|
+
}, className: cn("absolute left-[-8.5px] top-1/2 z-10 size-1.5 -translate-y-1/2 rounded-full", item.active
|
|
263
|
+
? "bg-[var(--color-primary)]"
|
|
264
|
+
: "bg-[var(--color-neutral-400)] dark:bg-[var(--color-neutral-600)]"), "aria-hidden": "true" }), jsx(motion.div, { initial: { opacity: 0, x: -6 }, animate: { opacity: 1, x: 0 }, exit: { opacity: 0, x: -6 }, transition: {
|
|
265
|
+
delay: idx * 0.055 + 0.035,
|
|
266
|
+
type: "spring",
|
|
267
|
+
stiffness: 460,
|
|
268
|
+
damping: 34,
|
|
269
|
+
mass: 0.7,
|
|
270
|
+
}, className: "ml-2", children: jsx(NavItem, { level: Math.min((level + 1), 2), startContent: item.startContent, active: item.active, isDisabled: item.isDisabled, notification: item.notification, href: item.href, onClick: item.onClick, className: "px-2.5", children: item.children }) })] }, idx))) }) })) })) : null;
|
|
271
|
+
const renderContent = () => {
|
|
272
|
+
if (hasSubmenu && items && items.length > 0) {
|
|
273
|
+
return (jsxs(Dropdown, { isOpen: isCollapsedView ? isDropdownOpen : false, onOpenChange: isCollapsedView ? setIsDropdownOpen : undefined, placement: "right-start", offset: 8, selectionMode: "single", selectedKeys: activeKeys, children: [jsx("div", { onMouseEnter: isCollapsedView ? handleMouseEnter : undefined, onMouseLeave: isCollapsedView ? handleMouseLeave : undefined, className: "w-full", children: jsx(Dropdown.Trigger, { children: content }) }), isCollapsedView && (jsx(Dropdown.Menu, { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: items.map((item, idx) => {
|
|
274
|
+
const itemKey = `sub-item-${idx}`;
|
|
275
|
+
const subNotificationElement = (() => {
|
|
276
|
+
if (item.notification === undefined || item.notification === false)
|
|
277
|
+
return null;
|
|
278
|
+
if (item.notification === true) {
|
|
279
|
+
return (jsx("span", { className: cn(theme.notificationDot, customTheme.notificationDot) }));
|
|
280
|
+
}
|
|
281
|
+
if (typeof item.notification === "number" && item.notification > 0) {
|
|
282
|
+
return (jsx(Badge, { color: "neutral", variant: "soft", size: "sm", children: item.notification > 99 ? "99+" : item.notification }));
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
})();
|
|
286
|
+
return (jsx(Dropdown.Item, { itemKey: itemKey, startContent: item.startContent, endContent: subNotificationElement, isDisabled: item.isDisabled, onAction: () => handleItemAction(item), className: "hover:bg-[var(--color-surface-hover)] dark:hover:bg-[var(--color-surface-hover)]", children: item.children }, idx));
|
|
287
|
+
}) }))] }));
|
|
288
|
+
}
|
|
289
|
+
return content;
|
|
174
290
|
};
|
|
175
291
|
// Always render Tooltip when in a sidebar (keeps tree structure stable across
|
|
176
292
|
// collapse/expand so CSS transitions aren't interrupted by DOM remounts).
|
|
177
|
-
// The tooltip is disabled when
|
|
293
|
+
// The tooltip is *disabled* (not unmounted) when out of collapsed view or while
|
|
294
|
+
// the dropdown flyout is open — unmounting it would swap the stable `block w-full`
|
|
295
|
+
// wrapper for the Dropdown's `display:contents` root, which drops the nav's
|
|
296
|
+
// `space-y-1` top margin and shifts every item below it up on hover.
|
|
178
297
|
const renderWithTooltip = (node) => {
|
|
179
298
|
if (!isInSidebar || !showTooltipWhenCollapsed) {
|
|
180
299
|
return node;
|
|
181
300
|
}
|
|
182
|
-
return (jsx(Tooltip, { content: children, placement: "right", disabled: !isCollapsedView, triggerClassName: "block w-full", children: node }));
|
|
301
|
+
return (jsx(Tooltip, { content: children, placement: "right", disabled: !isCollapsedView || isDropdownOpen, triggerClassName: "block w-full", children: node }));
|
|
183
302
|
};
|
|
184
|
-
return (jsxs(
|
|
303
|
+
return (jsxs("div", { className: "flex flex-col gap-0.5 w-full", children: [renderWithTooltip(renderContent()), submenu] }));
|
|
185
304
|
});
|
|
186
305
|
NavItem.displayName = "NavItem";
|
|
187
306
|
|
|
@@ -1,30 +1,49 @@
|
|
|
1
1
|
const navItemTheme = {
|
|
2
|
-
baseStyle: "relative flex items-center gap-
|
|
3
|
-
icon: "size-
|
|
2
|
+
baseStyle: "group relative flex items-center gap-3 px-2.5 py-2 text-sm font-medium cursor-pointer select-none w-full rounded-[var(--nav-item-radius)] outline-none overflow-hidden transition-[padding,gap,height,background-color,border-color,color,box-shadow] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)] text-left",
|
|
3
|
+
icon: "size-5",
|
|
4
4
|
text: "flex-1 min-w-0 truncate",
|
|
5
|
+
// State styling resolved in JS to plain utility classes so `className` overrides win
|
|
6
|
+
// (data-[state] variants carry attribute-selector specificity and can't be overridden
|
|
7
|
+
// by a plain className — see Button.theme.ts for the same rationale).
|
|
5
8
|
states: {
|
|
6
|
-
default: "text-[var(--color-text-
|
|
7
|
-
active: "bg-[var(--color-primary)]/10 text-[var(--color-primary)]
|
|
9
|
+
default: "text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] hover:text-[var(--color-text-primary)]",
|
|
10
|
+
active: "bg-[var(--color-primary)]/10 text-[var(--color-primary)] dark:text-[var(--color-text-primary)] dark:bg-white/10",
|
|
8
11
|
disabled: "opacity-60 cursor-not-allowed pointer-events-none text-[var(--color-text-placeholder)]",
|
|
9
|
-
hover: "hover:bg-[var(--color-background-secondary)]",
|
|
10
12
|
},
|
|
13
|
+
// Icons inherit the item's text color via `currentColor` — we only adjust
|
|
14
|
+
// opacity so the icon reads slightly lighter than the label by default.
|
|
11
15
|
iconStates: {
|
|
12
|
-
default: "
|
|
13
|
-
active: "
|
|
16
|
+
default: "opacity-70 group-hover:opacity-100",
|
|
17
|
+
active: "opacity-100",
|
|
14
18
|
disabled: "opacity-60",
|
|
15
19
|
},
|
|
20
|
+
// Keyboard focus ring
|
|
21
|
+
focusStyle: "focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-background)] " +
|
|
22
|
+
"dark:focus-visible:ring-[var(--color-primary-400)]",
|
|
16
23
|
levels: {
|
|
17
24
|
0: "",
|
|
18
|
-
1: "pl-
|
|
25
|
+
1: "pl-11",
|
|
19
26
|
2: "pl-10",
|
|
20
27
|
},
|
|
28
|
+
// Collapsed override gives every nav row a uniform 36×36 square hit-area.
|
|
29
|
+
// CRITICAL: this uses PADDING-based centering, not `justify-center`, so the
|
|
30
|
+
// expanded↔collapsed transition is one continuous animation of the padding
|
|
31
|
+
// + gap properties (which are CSS-transitionable). Toggling `justify-*`
|
|
32
|
+
// would cause a frame-0 flex-layout snap — the label appears at its final
|
|
33
|
+
// position instead of sliding in via AnimatePresence.
|
|
34
|
+
//
|
|
35
|
+
// On a 60px rail (Sidebar.Content px-3 → 36px interior, NavItem w-full):
|
|
36
|
+
// !px-2 (8 each) + size-5 icon (20) = 8 + 20 + 8 = 36 → centered icon.
|
|
37
|
+
// baseStyle's py-2 (8 each) + icon 20 = 36 → square height.
|
|
38
|
+
// !py-2 is repeated explicitly so the compact density's py-1.5 doesn't win.
|
|
21
39
|
collapsed: {
|
|
22
|
-
container: "!gap-0
|
|
40
|
+
container: "!gap-0 !px-2 !py-2",
|
|
23
41
|
icon: "size-5",
|
|
24
42
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
43
|
+
density: {
|
|
44
|
+
compact: "px-2 py-1.5 text-[13px]",
|
|
45
|
+
},
|
|
46
|
+
notificationDot: "size-2 rounded-full bg-[var(--color-primary)]",
|
|
28
47
|
};
|
|
29
48
|
|
|
30
49
|
export { navItemTheme };
|