@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.
Files changed (211) hide show
  1. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +2 -2
  2. package/dist/components/ai/PromptInput/PromptInput.js +4 -0
  3. package/dist/components/ai/PromptInput/VoiceRecorder.js +4 -0
  4. package/dist/components/ai/StreamingResponse/MarkdownRenderer.js +1 -1
  5. package/dist/components/ai/TokenCounter/TokenCounter.theme.js +1 -1
  6. package/dist/components/charts/BarChart/BarChart.js +2 -2
  7. package/dist/components/charts/StackedBarChart/StackedBarChart.js +1 -1
  8. package/dist/components/charts/shared/BarRenderer/BarRenderer.js +2 -2
  9. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +1 -1
  10. package/dist/components/core/Button/Button.js +1 -1
  11. package/dist/components/core/Button/Button.theme.js +5 -5
  12. package/dist/components/core/Calendar/Calendar.theme.js +1 -1
  13. package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
  14. package/dist/components/core/CommandPalette/CommandItem.js +4 -5
  15. package/dist/components/core/CommandPalette/CommandPalette.js +1 -1
  16. package/dist/components/core/CommandPalette/CommandPalette.theme.js +11 -13
  17. package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +0 -2
  18. package/dist/components/core/ContextMenu/ContextMenu.theme.js +13 -14
  19. package/dist/components/core/DotSeparator/DotSeparator.d.ts +3 -0
  20. package/dist/components/core/DotSeparator/DotSeparator.js +19 -0
  21. package/dist/components/core/DotSeparator/DotSeparator.theme.d.ts +2 -0
  22. package/dist/components/core/DotSeparator/DotSeparator.theme.js +10 -0
  23. package/dist/components/core/DotSeparator/DotSeparator.types.d.ts +25 -0
  24. package/dist/components/core/DotSeparator/index.d.ts +3 -0
  25. package/dist/components/core/Drawer/Drawer.theme.js +2 -2
  26. package/dist/components/core/Dropdown/Dropdown.theme.js +4 -5
  27. package/dist/components/core/Dropdown/DropdownItem.js +1 -1
  28. package/dist/components/core/Dropdown/DropdownMenu.js +1 -1
  29. package/dist/components/core/Empty/Empty.js +1 -1
  30. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.d.ts +14 -0
  31. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.js +28 -0
  32. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.d.ts +6 -0
  33. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.theme.js +8 -0
  34. package/dist/components/core/ImagePlaceholder/ImagePlaceholder.types.d.ts +32 -0
  35. package/dist/components/core/ImagePlaceholder/index.d.ts +2 -0
  36. package/dist/components/core/MenuItem/MenuItem.js +5 -2
  37. package/dist/components/core/MenuItem/MenuItem.theme.d.ts +6 -0
  38. package/dist/components/core/MenuItem/MenuItem.theme.js +19 -16
  39. package/dist/components/core/MenuItem/MenuItem.types.d.ts +2 -0
  40. package/dist/components/core/Message/Message.theme.js +1 -1
  41. package/dist/components/core/Modal/Modal.theme.js +1 -1
  42. package/dist/components/core/NavItem/NavItem.d.ts +1 -1
  43. package/dist/components/core/NavItem/NavItem.js +187 -68
  44. package/dist/components/core/NavItem/NavItem.theme.js +31 -12
  45. package/dist/components/core/NavItem/NavItem.types.d.ts +17 -39
  46. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  47. package/dist/components/core/Popover/Popover.theme.js +1 -1
  48. package/dist/components/core/Popover/Popover.types.d.ts +6 -2
  49. package/dist/components/core/Popover/PopoverBody.js +10 -3
  50. package/dist/components/core/Progress/Progress.theme.js +1 -1
  51. package/dist/components/core/ScrollArea/ScrollArea.js +2 -2
  52. package/dist/components/core/ScrollArea/ScrollArea.theme.js +1 -1
  53. package/dist/components/core/Segmented/Segmented.js +8 -6
  54. package/dist/components/core/Segmented/Segmented.theme.js +3 -3
  55. package/dist/components/core/Sidebar/Sidebar.d.ts +3 -1
  56. package/dist/components/core/Sidebar/Sidebar.js +53 -16
  57. package/dist/components/core/Sidebar/Sidebar.theme.js +5 -7
  58. package/dist/components/core/Sidebar/Sidebar.types.d.ts +125 -8
  59. package/dist/components/core/Sidebar/SidebarCard.d.ts +25 -0
  60. package/dist/components/core/Sidebar/SidebarCard.js +68 -0
  61. package/dist/components/core/Sidebar/SidebarCard.theme.d.ts +8 -0
  62. package/dist/components/core/Sidebar/SidebarCard.theme.js +18 -0
  63. package/dist/components/core/Sidebar/SidebarContent.js +10 -6
  64. package/dist/components/core/Sidebar/SidebarContext.d.ts +17 -16
  65. package/dist/components/core/Sidebar/SidebarContext.js +43 -20
  66. package/dist/components/core/Sidebar/SidebarFooter.js +4 -6
  67. package/dist/components/core/Sidebar/SidebarHeader.js +12 -9
  68. package/dist/components/core/Sidebar/SidebarLogo.js +39 -8
  69. package/dist/components/core/Sidebar/SidebarNav.js +4 -3
  70. package/dist/components/core/Sidebar/SidebarNavGroup.js +4 -7
  71. package/dist/components/core/Sidebar/SidebarSearch.d.ts +22 -0
  72. package/dist/components/core/Sidebar/SidebarSearch.js +135 -0
  73. package/dist/components/core/Sidebar/SidebarToggle.js +6 -1
  74. package/dist/components/core/Sidebar/SidebarUserProfile.js +33 -17
  75. package/dist/components/core/Sidebar/index.d.ts +4 -3
  76. package/dist/components/core/Skeleton/Skeleton.theme.js +1 -1
  77. package/dist/components/core/Spinner/Spinner.theme.js +1 -1
  78. package/dist/components/core/Tabs/Tabs.js +6 -1
  79. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  80. package/dist/components/core/Tabs/TabsBody.js +5 -3
  81. package/dist/components/core/Tag/Tag.js +29 -5
  82. package/dist/components/core/Tag/Tag.theme.js +14 -9
  83. package/dist/components/core/Tag/Tag.types.d.ts +7 -0
  84. package/dist/components/core/Tooltip/Tooltip.js +1 -1
  85. package/dist/components/core/Tooltip/Tooltip.theme.js +1 -2
  86. package/dist/components/core/index.d.ts +2 -0
  87. package/dist/components/core/index.js +6 -2
  88. package/dist/components/data-display/GanttChart/GanttChart.d.ts +1 -1
  89. package/dist/components/data-display/GanttChart/GanttChart.js +31 -4
  90. package/dist/components/data-display/GanttChart/GanttChart.theme.js +19 -17
  91. package/dist/components/data-display/GanttChart/GanttChart.types.d.ts +19 -0
  92. package/dist/components/data-display/GanttChart/GanttChart.utils.js +1 -1
  93. package/dist/components/data-display/GanttChart/GanttChartPanel.d.ts +1 -1
  94. package/dist/components/data-display/GanttChart/GanttChartPanel.js +3 -3
  95. package/dist/components/data-display/GanttChart/GanttDependencyArrows.js +7 -4
  96. package/dist/components/data-display/GanttChart/GanttMilestone.js +5 -5
  97. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +1 -1
  98. package/dist/components/data-display/GanttChart/GanttTablePanel.js +1 -1
  99. package/dist/components/data-display/GanttChart/GanttTableTaskRow.js +2 -2
  100. package/dist/components/data-display/GanttChart/GanttTaskBar.js +37 -16
  101. package/dist/components/data-display/GanttChart/GanttTimelineGrid.js +2 -1
  102. package/dist/components/data-display/GanttChart/GanttTimelineHeader.js +35 -11
  103. package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
  104. package/dist/components/data-display/GanttChart/hooks/useGanttDrag.js +3 -2
  105. package/dist/components/data-display/GanttChart/index.d.ts +1 -1
  106. package/dist/components/data-display/KPI/KPI.js +13 -3
  107. package/dist/components/data-display/KPI/KPI.theme.js +3 -3
  108. package/dist/components/data-display/Metric/Metric.theme.js +3 -3
  109. package/dist/components/data-display/Table/Table.js +4 -3
  110. package/dist/components/data-display/Table/Table.theme.js +14 -6
  111. package/dist/components/data-display/Table/Table.types.d.ts +15 -0
  112. package/dist/components/data-display/Table/TableColumnManager.js +1 -1
  113. package/dist/components/data-display/Table/index.d.ts +2 -2
  114. package/dist/components/effects/Aurora/Aurora.d.ts +6 -17
  115. package/dist/components/effects/Aurora/Aurora.js +310 -105
  116. package/dist/components/effects/Aurora/Aurora.types.d.ts +6 -0
  117. package/dist/components/effects/InteractiveCharacters/InteractiveCharacters.js +3 -3
  118. package/dist/components/effects/InteractiveCharacters/characters/BlobCharacter.js +13 -4
  119. package/dist/components/effects/InteractiveCharacters/characters/SmallCharacter.js +13 -4
  120. package/dist/components/effects/InteractiveCharacters/characters/TallCharacter.js +14 -4
  121. package/dist/components/effects/MorphingText/MorphingText.js +40 -7
  122. package/dist/components/effects/StripePattern/StripePattern.d.ts +18 -0
  123. package/dist/components/effects/StripePattern/StripePattern.js +54 -0
  124. package/dist/components/effects/StripePattern/StripePattern.types.d.ts +53 -0
  125. package/dist/components/effects/StripePattern/index.d.ts +2 -0
  126. package/dist/components/effects/index.d.ts +2 -0
  127. package/dist/components/effects/index.js +1 -0
  128. package/dist/components/forms/Checkbox/Checkbox.theme.js +1 -1
  129. package/dist/components/forms/Combobox/Combobox.js +1 -1
  130. package/dist/components/forms/Combobox/Combobox.theme.js +2 -4
  131. package/dist/components/forms/CronInput/CronInput.js +1 -1
  132. package/dist/components/forms/CronInput/CronInput.theme.js +22 -31
  133. package/dist/components/forms/DatePicker/DatePicker.theme.js +11 -18
  134. package/dist/components/forms/DatePicker/DatePickerTrigger.js +1 -1
  135. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +7 -14
  136. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +1 -1
  137. package/dist/components/forms/FileUpload/FileUpload.animations.d.ts +29 -0
  138. package/dist/components/forms/FileUpload/FileUpload.animations.js +19 -0
  139. package/dist/components/forms/FileUpload/FileUpload.js +88 -49
  140. package/dist/components/forms/FileUpload/FileUpload.theme.d.ts +2 -10
  141. package/dist/components/forms/FileUpload/FileUpload.theme.js +16 -30
  142. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +26 -10
  143. package/dist/components/forms/FileUpload/FileUploadProgress.js +27 -20
  144. package/dist/components/forms/FileUpload/FileUploadProgress.theme.js +2 -2
  145. package/dist/components/forms/Input/Input.theme.js +16 -15
  146. package/dist/components/forms/InputCounter/InputCounter.theme.js +1 -1
  147. package/dist/components/forms/Mention/Mention.js +152 -57
  148. package/dist/components/forms/Mention/Mention.theme.js +16 -23
  149. package/dist/components/forms/Mention/Mention.types.d.ts +12 -12
  150. package/dist/components/forms/Mention/Mention.utils.js +32 -4
  151. package/dist/components/forms/Radio/Radio.theme.js +1 -1
  152. package/dist/components/forms/RichTextEditor/RichTextEditor.d.ts +18 -0
  153. package/dist/components/forms/RichTextEditor/RichTextEditor.js +1231 -236
  154. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.d.ts +53 -1
  155. package/dist/components/forms/RichTextEditor/RichTextEditor.theme.js +99 -25
  156. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +42 -3
  157. package/dist/components/forms/RichTextEditor/index.d.ts +2 -2
  158. package/dist/components/forms/Select/Select.js +2 -2
  159. package/dist/components/forms/Select/Select.theme.js +3 -3
  160. package/dist/components/forms/Slider/Slider.theme.js +1 -1
  161. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  162. package/dist/components/forms/TimePicker/TimePicker.theme.js +2 -2
  163. package/dist/components/forms/TimePicker/TimePickerContent.js +4 -0
  164. package/dist/components/forms/TimePicker/TimePickerTrigger.js +1 -1
  165. package/dist/components/forms/forms.theme.js +24 -19
  166. package/dist/components/forms/index.js +4 -0
  167. package/dist/components/layout/AppShell/AppShell.d.ts +10 -0
  168. package/dist/components/layout/AppShell/AppShell.js +84 -0
  169. package/dist/components/layout/AppShell/AppShell.theme.d.ts +3 -0
  170. package/dist/components/layout/AppShell/AppShell.theme.js +42 -0
  171. package/dist/components/layout/AppShell/AppShell.types.d.ts +127 -0
  172. package/dist/components/layout/AppShell/AppShellContext.d.ts +14 -0
  173. package/dist/components/layout/AppShell/AppShellContext.js +19 -0
  174. package/dist/components/layout/AppShell/AppShellFooter.d.ts +3 -0
  175. package/dist/components/layout/AppShell/AppShellFooter.js +12 -0
  176. package/dist/components/layout/AppShell/AppShellHeader.d.ts +3 -0
  177. package/dist/components/layout/AppShell/AppShellHeader.js +12 -0
  178. package/dist/components/layout/AppShell/AppShellMain.d.ts +3 -0
  179. package/dist/components/layout/AppShell/AppShellMain.js +19 -0
  180. package/dist/components/layout/AppShell/AppShellRightPanel.d.ts +3 -0
  181. package/dist/components/layout/AppShell/AppShellRightPanel.js +16 -0
  182. package/dist/components/layout/AppShell/AppShellSidebar.d.ts +3 -0
  183. package/dist/components/layout/AppShell/AppShellSidebar.js +16 -0
  184. package/dist/components/layout/AppShell/AppShellTopbar.d.ts +3 -0
  185. package/dist/components/layout/AppShell/AppShellTopbar.js +12 -0
  186. package/dist/components/layout/AppShell/index.d.ts +3 -0
  187. package/dist/components/layout/FormLayout/FormLayout.js +9 -4
  188. package/dist/components/layout/FormLayout/FormLayout.types.d.ts +3 -3
  189. package/dist/components/layout/FormLayout/FormLayoutSection.js +6 -1
  190. package/dist/components/layout/PageLayout/PageLayout.d.ts +2 -2
  191. package/dist/components/layout/PageLayout/PageLayout.js +23 -5
  192. package/dist/components/layout/PageLayout/PageLayout.theme.d.ts +2 -1
  193. package/dist/components/layout/PageLayout/PageLayout.theme.js +35 -6
  194. package/dist/components/layout/PageLayout/PageLayout.types.d.ts +45 -0
  195. package/dist/components/layout/PageLayout/PageLayoutContent.js +6 -4
  196. package/dist/components/layout/PageLayout/PageLayoutHeader.js +3 -3
  197. package/dist/components/layout/PageLayout/PageLayoutSidebar.js +5 -3
  198. package/dist/components/layout/PageLayout/index.d.ts +1 -1
  199. package/dist/components/layout/index.d.ts +1 -0
  200. package/dist/components/layout/index.js +2 -0
  201. package/dist/hooks/useSelectPortal.d.ts +10 -2
  202. package/dist/hooks/useSelectPortal.js +45 -24
  203. package/dist/index.js +8 -2
  204. package/dist/registry.json +10021 -0
  205. package/dist/styles.css +1 -1
  206. package/dist/utils/dateUtils.js +11 -1
  207. package/package.json +4 -2
  208. package/src/global.scss +29 -8
  209. package/src/styles/theme.css +174 -100
  210. package/dist/components/core/Sidebar/SidebarSubmenu.d.ts +0 -7
  211. 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.focusStyle, isDanger && theme.dangerStyle, 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'
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,5 +1,11 @@
1
1
  export interface MenuItemTheme {
2
2
  baseStyle: string;
3
+ states: {
4
+ default: string;
5
+ selected: string;
6
+ disabled: string;
7
+ };
8
+ focusedStyle: string;
3
9
  dangerStyle: string;
4
10
  focusStyle: string;
5
11
  subtitleStyle: string;
@@ -1,25 +1,28 @@
1
1
  const menuItemTheme = {
2
- // Base item styling with hover, focus, and disabled states
3
- baseStyle: "relative flex items-center gap-2 px-3 py-1.5 text-sm font-[500] text-[var(--color-text-primary)] cursor-pointer outline-none select-none rounded-[var(--radius-base)] transition-colors duration-300 " +
4
- "hover:bg-[var(--color-background-secondary)] focus:bg-[var(--color-background-secondary)] " +
5
- "data-[focused=true]:bg-[var(--color-background-secondary)] " +
6
- "data-[selected=true]:bg-[var(--color-primary)]/5 data-[selected=true]:text-[var(--color-primary)] " +
7
- "mb-px last-of-type:mb-0 " +
8
- "data-[disabled=true]:opacity-60 data-[disabled=true]:cursor-not-allowed data-[disabled=true]:pointer-events-none " +
9
- // Dark mode
10
- " " +
11
- " " +
12
- "dark:data-[focused=true]:bg-[var(--color-background-secondary)] " +
13
- "dark:hover:bg-[var(--color-neutral-700)] " +
14
- "dark:data-[selected=true]:bg-[var(--color-neutral-600)]/50 dark:data-[selected=true]:text-[var(--color-text-primary)]",
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)] dark:focus-visible:ring-offset-[var(--color-neutral-900)]",
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-xs text-[var(--color-text-muted)] mt-0.5 truncate",
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-background-tertiary)]",
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-white ring-4 !ring-white/10 dark:bg-[var(--color-background-secondary)] border border-[var(--color-border)]",
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<HTMLDivElement>>;
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: propStartContent, endContent: propEndContent, icon, // Legacy
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
- // Map legacy props
56
- const startContent = propStartContent || icon;
57
- const actualIsDisabled = propIsDisabled || disabled;
58
- // Build end content
59
- let endContent = propEndContent;
60
- if (!endContent && badge) {
61
- endContent = (jsx(Badge, { color: "neutral", variant: "soft", size: "sm", children: badge }));
62
- }
63
- // Notification element
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("span", { className: cn(navItemTheme.notificationBadge, customTheme.notificationBadge), children: notification > 99 ? "99+" : notification }));
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, customTheme.notificationBadge]);
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
- danger: customTheme.danger || navItemTheme.danger || "text-[var(--color-danger)] hover:bg-[var(--color-danger-50)]",
138
+ density: { ...navItemTheme.density, ...customTheme.density },
83
139
  }), [customTheme]);
84
140
  // Handle click events
85
141
  const handleClick = (e) => {
86
- if (actualIsDisabled) {
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
- // Get state classes
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 (actualIsDisabled)
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
- // Danger overrides
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
- var _a, _b, _c;
127
- if (actualIsDisabled)
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 (_b = theme.iconStates) === null || _b === void 0 ? void 0 : _b.active;
131
- return (_c = theme.iconStates) === null || _c === void 0 ? void 0 : _c.default;
182
+ return theme.iconStates.active;
183
+ return theme.iconStates.default;
132
184
  };
133
- // Build icon classes
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
- // Content component
139
- 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) => {
140
- if (e.key === "Enter" || e.key === " ") {
141
- e.preventDefault();
142
- handleClick(e);
143
- }
144
- }, ...props, children: [startContent && (jsxs("div", { className: "flex items-center justify-center flex-shrink-0 relative", children: [React__default.isValidElement(startContent)
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 && notificationElement && (jsx("span", { className: "absolute -top-1 -right-1", children: notificationElement }))] })), jsx("div", { "aria-hidden": isCollapsedView || undefined, style: {
149
- display: 'grid',
150
- gridTemplateColumns: isCollapsedView ? '0fr' : '1fr',
151
- overflow: 'hidden',
152
- flex: '1 1 auto',
153
- opacity: isCollapsedView ? 0 : 1,
154
- transition: 'grid-template-columns 0.18s cubic-bezier(0.4,0,0.2,1), opacity 0.12s ease-out',
155
- }, children: jsxs("div", { style: { overflow: 'hidden', minWidth: 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 }))] }) }), jsx("div", { style: {
156
- display: 'grid',
157
- gridTemplateColumns: isCollapsedView ? '0fr' : '1fr',
158
- overflow: 'hidden',
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: { opacity: 1, height: "auto" },
166
- collapsed: { opacity: 0, height: 0 },
167
- }, 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;
168
- // Wrap with link if href is provided
169
- const renderWrapped = (node) => {
170
- if (href && !actualIsDisabled) {
171
- return (jsx("a", { href: href, className: "block group", children: node }));
172
- }
173
- return node;
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 not in collapsed view.
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(Fragment, { children: [renderWithTooltip(renderWrapped(content)), submenu] }));
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-2 px-2 py-1.5 text-sm font-medium cursor-pointer select-none transition-colors duration-150 w-full rounded-[var(--nav-item-radius)] outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-primary)] group overflow-hidden",
3
- icon: "size-4 text-[var(--color-text-secondary)]",
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-primary)] hover:bg-[var(--color-background-secondary)]",
7
- active: "bg-[var(--color-primary)]/10 text-[var(--color-primary)] font-semibold",
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: "text-[var(--color-text-secondary)] group-hover:text-[var(--color-text-primary)]",
13
- active: "text-[var(--color-primary)]",
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-6",
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 justify-center !p-2",
40
+ container: "!gap-0 !px-2 !py-2",
23
41
  icon: "size-5",
24
42
  },
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",
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 };