@mihcm/ui 0.14.1 → 0.15.0

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 (300) hide show
  1. package/dist/CheckboxGrid.native.d.ts.map +1 -1
  2. package/dist/CheckboxGrid.native.js +2 -1
  3. package/dist/CheckboxGrid.native.js.map +1 -1
  4. package/dist/Combobox.native.d.ts.map +1 -1
  5. package/dist/Combobox.native.js +2 -1
  6. package/dist/Combobox.native.js.map +1 -1
  7. package/dist/DataTable/column-filter.d.ts +8 -0
  8. package/dist/DataTable/column-filter.d.ts.map +1 -0
  9. package/dist/DataTable/column-filter.js +67 -0
  10. package/dist/DataTable/column-filter.js.map +1 -0
  11. package/dist/DataTable/column-header.d.ts +16 -0
  12. package/dist/DataTable/column-header.d.ts.map +1 -0
  13. package/dist/DataTable/column-header.js +11 -0
  14. package/dist/DataTable/column-header.js.map +1 -0
  15. package/dist/DataTable/column-visibility.d.ts +7 -0
  16. package/dist/DataTable/column-visibility.d.ts.map +1 -0
  17. package/dist/DataTable/column-visibility.js +35 -0
  18. package/dist/DataTable/column-visibility.js.map +1 -0
  19. package/dist/DataTable/index.d.ts +5 -0
  20. package/dist/DataTable/index.d.ts.map +1 -0
  21. package/dist/DataTable/index.js +5 -0
  22. package/dist/DataTable/index.js.map +1 -0
  23. package/dist/DataTable/pinning.d.ts +13 -0
  24. package/dist/DataTable/pinning.d.ts.map +1 -0
  25. package/dist/DataTable/pinning.js +29 -0
  26. package/dist/DataTable/pinning.js.map +1 -0
  27. package/dist/DataTable.d.ts +3 -7
  28. package/dist/DataTable.d.ts.map +1 -1
  29. package/dist/DataTable.js +7 -126
  30. package/dist/DataTable.js.map +1 -1
  31. package/dist/Dialog.native.d.ts +3 -1
  32. package/dist/Dialog.native.d.ts.map +1 -1
  33. package/dist/Dialog.native.js +2 -2
  34. package/dist/Dialog.native.js.map +1 -1
  35. package/dist/Form/building-blocks.d.ts +26 -0
  36. package/dist/Form/building-blocks.d.ts.map +1 -0
  37. package/dist/Form/building-blocks.js +29 -0
  38. package/dist/Form/building-blocks.js.map +1 -0
  39. package/dist/Form/fields-choice.d.ts +72 -0
  40. package/dist/Form/fields-choice.d.ts.map +1 -0
  41. package/dist/Form/fields-choice.js +69 -0
  42. package/dist/Form/fields-choice.js.map +1 -0
  43. package/dist/Form/fields-complex.d.ts +28 -0
  44. package/dist/Form/fields-complex.d.ts.map +1 -0
  45. package/dist/Form/fields-complex.js +38 -0
  46. package/dist/Form/fields-complex.js.map +1 -0
  47. package/dist/Form/fields-date.d.ts +46 -0
  48. package/dist/Form/fields-date.d.ts.map +1 -0
  49. package/dist/Form/fields-date.js +41 -0
  50. package/dist/Form/fields-date.js.map +1 -0
  51. package/dist/Form/fields-text.d.ts +47 -0
  52. package/dist/Form/fields-text.d.ts.map +1 -0
  53. package/dist/Form/fields-text.js +46 -0
  54. package/dist/Form/fields-text.js.map +1 -0
  55. package/dist/Form/fields-toggle.d.ts +24 -0
  56. package/dist/Form/fields-toggle.d.ts.map +1 -0
  57. package/dist/Form/fields-toggle.js +32 -0
  58. package/dist/Form/fields-toggle.js.map +1 -0
  59. package/dist/Form/helpers.d.ts +66 -0
  60. package/dist/Form/helpers.d.ts.map +1 -0
  61. package/dist/Form/helpers.js +44 -0
  62. package/dist/Form/helpers.js.map +1 -0
  63. package/dist/Form/types.d.ts +25 -0
  64. package/dist/Form/types.d.ts.map +1 -0
  65. package/dist/Form/types.js +8 -0
  66. package/dist/Form/types.js.map +1 -0
  67. package/dist/Form.d.ts +24 -298
  68. package/dist/Form.d.ts.map +1 -1
  69. package/dist/Form.js +30 -246
  70. package/dist/Form.js.map +1 -1
  71. package/dist/IconSidebar.d.ts +6 -46
  72. package/dist/IconSidebar.d.ts.map +1 -1
  73. package/dist/IconSidebar.js +6 -116
  74. package/dist/IconSidebar.js.map +1 -1
  75. package/dist/MainSidebar/back-button.d.ts +14 -0
  76. package/dist/MainSidebar/back-button.d.ts.map +1 -0
  77. package/dist/MainSidebar/back-button.js +14 -0
  78. package/dist/MainSidebar/back-button.js.map +1 -0
  79. package/dist/MainSidebar/breadcrumb.d.ts +10 -0
  80. package/dist/MainSidebar/breadcrumb.d.ts.map +1 -0
  81. package/dist/MainSidebar/breadcrumb.js +24 -0
  82. package/dist/MainSidebar/breadcrumb.js.map +1 -0
  83. package/dist/MainSidebar/columns.d.ts +3 -0
  84. package/dist/MainSidebar/columns.d.ts.map +1 -0
  85. package/dist/MainSidebar/columns.js +198 -0
  86. package/dist/MainSidebar/columns.js.map +1 -0
  87. package/dist/MainSidebar/command.d.ts +3 -0
  88. package/dist/MainSidebar/command.d.ts.map +1 -0
  89. package/dist/MainSidebar/command.js +193 -0
  90. package/dist/MainSidebar/command.js.map +1 -0
  91. package/dist/MainSidebar/drilldown.d.ts +3 -0
  92. package/dist/MainSidebar/drilldown.d.ts.map +1 -0
  93. package/dist/MainSidebar/drilldown.js +154 -0
  94. package/dist/MainSidebar/drilldown.js.map +1 -0
  95. package/dist/MainSidebar/expanded.d.ts +7 -0
  96. package/dist/MainSidebar/expanded.d.ts.map +1 -0
  97. package/dist/MainSidebar/expanded.js +102 -0
  98. package/dist/MainSidebar/expanded.js.map +1 -0
  99. package/dist/MainSidebar/floating.d.ts +3 -0
  100. package/dist/MainSidebar/floating.d.ts.map +1 -0
  101. package/dist/MainSidebar/floating.js +116 -0
  102. package/dist/MainSidebar/floating.js.map +1 -0
  103. package/dist/MainSidebar/helpers.d.ts +50 -0
  104. package/dist/MainSidebar/helpers.d.ts.map +1 -0
  105. package/dist/MainSidebar/helpers.js +148 -0
  106. package/dist/MainSidebar/helpers.js.map +1 -0
  107. package/dist/MainSidebar/hover.d.ts +3 -0
  108. package/dist/MainSidebar/hover.d.ts.map +1 -0
  109. package/dist/MainSidebar/hover.js +177 -0
  110. package/dist/MainSidebar/hover.js.map +1 -0
  111. package/dist/MainSidebar/index.d.ts +6 -0
  112. package/dist/MainSidebar/index.d.ts.map +1 -0
  113. package/dist/MainSidebar/index.js +108 -0
  114. package/dist/MainSidebar/index.js.map +1 -0
  115. package/dist/MainSidebar/mobile.d.ts +29 -0
  116. package/dist/MainSidebar/mobile.d.ts.map +1 -0
  117. package/dist/MainSidebar/mobile.js +38 -0
  118. package/dist/MainSidebar/mobile.js.map +1 -0
  119. package/dist/MainSidebar/motion.d.ts +23 -0
  120. package/dist/MainSidebar/motion.d.ts.map +1 -0
  121. package/dist/MainSidebar/motion.js +40 -0
  122. package/dist/MainSidebar/motion.js.map +1 -0
  123. package/dist/MainSidebar/rail.d.ts +24 -0
  124. package/dist/MainSidebar/rail.d.ts.map +1 -0
  125. package/dist/MainSidebar/rail.js +29 -0
  126. package/dist/MainSidebar/rail.js.map +1 -0
  127. package/dist/MainSidebar/search.d.ts +19 -0
  128. package/dist/MainSidebar/search.d.ts.map +1 -0
  129. package/dist/MainSidebar/search.js +33 -0
  130. package/dist/MainSidebar/search.js.map +1 -0
  131. package/dist/MainSidebar/types.d.ts +161 -0
  132. package/dist/MainSidebar/types.d.ts.map +1 -0
  133. package/dist/MainSidebar/types.js +2 -0
  134. package/dist/MainSidebar/types.js.map +1 -0
  135. package/dist/MainSidebar.d.ts +6 -1
  136. package/dist/MainSidebar.d.ts.map +1 -1
  137. package/dist/MainSidebar.js +6 -1
  138. package/dist/MainSidebar.js.map +1 -1
  139. package/dist/NavigationMenu.js +1 -1
  140. package/dist/NavigationMenu.js.map +1 -1
  141. package/dist/RichTextEditor/theme.d.ts +44 -0
  142. package/dist/RichTextEditor/theme.d.ts.map +1 -0
  143. package/dist/RichTextEditor/theme.js +41 -0
  144. package/dist/RichTextEditor/theme.js.map +1 -0
  145. package/dist/RichTextEditor/toolbar-icons.d.ts +21 -0
  146. package/dist/RichTextEditor/toolbar-icons.d.ts.map +1 -0
  147. package/dist/RichTextEditor/toolbar-icons.js +21 -0
  148. package/dist/RichTextEditor/toolbar-icons.js.map +1 -0
  149. package/dist/RichTextEditor/toolbar.d.ts +5 -0
  150. package/dist/RichTextEditor/toolbar.d.ts.map +1 -0
  151. package/dist/RichTextEditor/toolbar.js +116 -0
  152. package/dist/RichTextEditor/toolbar.js.map +1 -0
  153. package/dist/RichTextEditor.d.ts +16 -9
  154. package/dist/RichTextEditor.d.ts.map +1 -1
  155. package/dist/RichTextEditor.js +18 -164
  156. package/dist/RichTextEditor.js.map +1 -1
  157. package/dist/Select/content.d.ts +9 -0
  158. package/dist/Select/content.d.ts.map +1 -0
  159. package/dist/Select/content.js +80 -0
  160. package/dist/Select/content.js.map +1 -0
  161. package/dist/Select/context.d.ts +27 -0
  162. package/dist/Select/context.d.ts.map +1 -0
  163. package/dist/Select/context.js +35 -0
  164. package/dist/Select/context.js.map +1 -0
  165. package/dist/Select/item.d.ts +13 -0
  166. package/dist/Select/item.d.ts.map +1 -0
  167. package/dist/Select/item.js +39 -0
  168. package/dist/Select/item.js.map +1 -0
  169. package/dist/Select/parts.d.ts +14 -0
  170. package/dist/Select/parts.d.ts.map +1 -0
  171. package/dist/Select/parts.js +17 -0
  172. package/dist/Select/parts.js.map +1 -0
  173. package/dist/Select/react-select.d.ts +25 -0
  174. package/dist/Select/react-select.d.ts.map +1 -0
  175. package/dist/Select/react-select.js +66 -0
  176. package/dist/Select/react-select.js.map +1 -0
  177. package/dist/Select/root.d.ts +15 -0
  178. package/dist/Select/root.d.ts.map +1 -0
  179. package/dist/Select/root.js +41 -0
  180. package/dist/Select/root.js.map +1 -0
  181. package/dist/Select/trigger.d.ts +15 -0
  182. package/dist/Select/trigger.d.ts.map +1 -0
  183. package/dist/Select/trigger.js +61 -0
  184. package/dist/Select/trigger.js.map +1 -0
  185. package/dist/Select.d.ts +14 -62
  186. package/dist/Select.d.ts.map +1 -1
  187. package/dist/Select.js +14 -293
  188. package/dist/Select.js.map +1 -1
  189. package/dist/Sidebar/context.d.ts +28 -0
  190. package/dist/Sidebar/context.d.ts.map +1 -0
  191. package/dist/Sidebar/context.js +37 -0
  192. package/dist/Sidebar/context.js.map +1 -0
  193. package/dist/Sidebar/group.d.ts +13 -0
  194. package/dist/Sidebar/group.d.ts.map +1 -0
  195. package/dist/Sidebar/group.js +20 -0
  196. package/dist/Sidebar/group.js.map +1 -0
  197. package/dist/Sidebar/icons.d.ts +7 -0
  198. package/dist/Sidebar/icons.d.ts.map +1 -0
  199. package/dist/Sidebar/icons.js +12 -0
  200. package/dist/Sidebar/icons.js.map +1 -0
  201. package/dist/Sidebar/layout.d.ts +9 -0
  202. package/dist/Sidebar/layout.d.ts.map +1 -0
  203. package/dist/Sidebar/layout.js +21 -0
  204. package/dist/Sidebar/layout.js.map +1 -0
  205. package/dist/Sidebar/menu.d.ts +29 -0
  206. package/dist/Sidebar/menu.d.ts.map +1 -0
  207. package/dist/Sidebar/menu.js +55 -0
  208. package/dist/Sidebar/menu.js.map +1 -0
  209. package/dist/Sidebar/provider.d.ts +33 -0
  210. package/dist/Sidebar/provider.d.ts.map +1 -0
  211. package/dist/Sidebar/provider.js +110 -0
  212. package/dist/Sidebar/provider.js.map +1 -0
  213. package/dist/Sidebar/sidebar.d.ts +17 -0
  214. package/dist/Sidebar/sidebar.d.ts.map +1 -0
  215. package/dist/Sidebar/sidebar.js +51 -0
  216. package/dist/Sidebar/sidebar.js.map +1 -0
  217. package/dist/Sidebar/submenu.d.ts +13 -0
  218. package/dist/Sidebar/submenu.d.ts.map +1 -0
  219. package/dist/Sidebar/submenu.js +17 -0
  220. package/dist/Sidebar/submenu.js.map +1 -0
  221. package/dist/Sidebar/trigger.d.ts +9 -0
  222. package/dist/Sidebar/trigger.d.ts.map +1 -0
  223. package/dist/Sidebar/trigger.js +33 -0
  224. package/dist/Sidebar/trigger.js.map +1 -0
  225. package/dist/Sidebar.d.ts +14 -104
  226. package/dist/Sidebar.d.ts.map +1 -1
  227. package/dist/Sidebar.js +14 -300
  228. package/dist/Sidebar.js.map +1 -1
  229. package/dist/StatCard.d.ts +67 -9
  230. package/dist/StatCard.d.ts.map +1 -1
  231. package/dist/StatCard.js +111 -9
  232. package/dist/StatCard.js.map +1 -1
  233. package/dist/TransferList.native.d.ts.map +1 -1
  234. package/dist/TransferList.native.js +2 -1
  235. package/dist/TransferList.native.js.map +1 -1
  236. package/package.json +2 -2
  237. package/src/CheckboxGrid.native.tsx +2 -1
  238. package/src/Combobox.native.tsx +2 -1
  239. package/src/DataTable/column-filter.tsx +134 -0
  240. package/src/DataTable/column-header.tsx +67 -0
  241. package/src/DataTable/column-visibility.tsx +87 -0
  242. package/src/DataTable/index.ts +4 -0
  243. package/src/DataTable/pinning.ts +40 -0
  244. package/src/DataTable.tsx +14 -297
  245. package/src/Dialog.native.tsx +4 -2
  246. package/src/Form/building-blocks.tsx +97 -0
  247. package/src/Form/fields-choice.tsx +312 -0
  248. package/src/Form/fields-complex.tsx +195 -0
  249. package/src/Form/fields-date.tsx +195 -0
  250. package/src/Form/fields-text.tsx +218 -0
  251. package/src/Form/fields-toggle.tsx +123 -0
  252. package/src/Form/helpers.tsx +189 -0
  253. package/src/Form/types.ts +26 -0
  254. package/src/Form.tsx +91 -1308
  255. package/src/IconSidebar.tsx +20 -442
  256. package/src/MainSidebar/back-button.tsx +58 -0
  257. package/src/MainSidebar/breadcrumb.tsx +53 -0
  258. package/src/MainSidebar/columns.tsx +350 -0
  259. package/src/MainSidebar/command.tsx +404 -0
  260. package/src/MainSidebar/drilldown.tsx +373 -0
  261. package/src/MainSidebar/expanded.tsx +414 -0
  262. package/src/MainSidebar/floating.tsx +268 -0
  263. package/src/MainSidebar/helpers.ts +164 -0
  264. package/src/MainSidebar/hover.tsx +334 -0
  265. package/src/MainSidebar/index.tsx +191 -0
  266. package/src/MainSidebar/mobile.tsx +117 -0
  267. package/src/MainSidebar/motion.ts +64 -0
  268. package/src/MainSidebar/rail.tsx +137 -0
  269. package/src/MainSidebar/search.tsx +99 -0
  270. package/src/MainSidebar/types.ts +208 -0
  271. package/src/MainSidebar.tsx +15 -4
  272. package/src/NavigationMenu.tsx +1 -1
  273. package/src/RichTextEditor/theme.ts +43 -0
  274. package/src/RichTextEditor/toolbar-icons.tsx +40 -0
  275. package/src/RichTextEditor/toolbar.tsx +271 -0
  276. package/src/RichTextEditor.tsx +23 -371
  277. package/src/Select/content.tsx +111 -0
  278. package/src/Select/context.tsx +66 -0
  279. package/src/Select/item.tsx +97 -0
  280. package/src/Select/parts.tsx +43 -0
  281. package/src/Select/react-select.tsx +216 -0
  282. package/src/Select/root.tsx +75 -0
  283. package/src/Select/trigger.tsx +122 -0
  284. package/src/Select.tsx +34 -692
  285. package/src/Sidebar/context.tsx +72 -0
  286. package/src/Sidebar/group.tsx +69 -0
  287. package/src/Sidebar/icons.tsx +42 -0
  288. package/src/Sidebar/layout.tsx +64 -0
  289. package/src/Sidebar/menu.tsx +171 -0
  290. package/src/Sidebar/provider.tsx +224 -0
  291. package/src/Sidebar/sidebar.tsx +178 -0
  292. package/src/Sidebar/submenu.tsx +58 -0
  293. package/src/Sidebar/trigger.tsx +104 -0
  294. package/src/Sidebar.tsx +44 -927
  295. package/src/StatCard.tsx +365 -20
  296. package/src/TransferList.native.tsx +2 -1
  297. package/dist/TiptapEditor.d.ts +0 -24
  298. package/dist/TiptapEditor.d.ts.map +0 -1
  299. package/dist/TiptapEditor.js +0 -84
  300. package/dist/TiptapEditor.js.map +0 -1
@@ -0,0 +1,373 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * MainSidebar — `drilldown` variant.
5
+ *
6
+ * Default. Rail click opens a panel beside the rail; deeper levels replace
7
+ * the current panel; back/breadcrumb to step out. Layout reflows.
8
+ */
9
+ import { forwardRef, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
10
+ import { AnimatePresence, motion, useReducedMotion } from 'motion/react';
11
+ import { cn } from '../internal/cn.js';
12
+ import { Rail } from './rail.js';
13
+ import { MenuSearch } from './search.js';
14
+ import { BackButton, CloseButton } from './back-button.js';
15
+ import { PathBreadcrumb } from './breadcrumb.js';
16
+ import { defaultMatcher, filterLevel, findItem, firstPanelItem, isOnPath, opensPanel, resolvePath, shouldWrapBadge } from './helpers.js';
17
+ import { slideVariants } from './motion.js';
18
+ import type { MainSidebarItem, MainSidebarProps } from './types.js';
19
+
20
+ interface PanelRowProps {
21
+ item: MainSidebarItem;
22
+ active?: boolean;
23
+ onClick: () => void;
24
+ showChevron?: boolean;
25
+ className: string | undefined;
26
+ activeClassName: string | undefined;
27
+ }
28
+
29
+ function PanelRow({ item, active, onClick, showChevron, className, activeClassName }: PanelRowProps) {
30
+ return (
31
+ <button
32
+ type="button"
33
+ onClick={onClick}
34
+ disabled={item.disabled}
35
+ aria-current={active ? 'page' : undefined}
36
+ className={cn(
37
+ 'group flex w-full items-center gap-2.5 rounded-md px-2.5 py-2 text-left text-sm transition-colors',
38
+ 'hover:bg-card-foreground/10',
39
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
40
+ 'disabled:cursor-not-allowed disabled:opacity-50',
41
+ active && 'bg-accent text-accent-foreground font-medium hover:bg-accent hover:text-accent-foreground',
42
+ active && activeClassName,
43
+ className,
44
+ )}
45
+ >
46
+ {item.icon ? (
47
+ <span className="grid size-5 shrink-0 place-items-center text-card-foreground/70 group-hover:text-card-foreground [&_svg]:size-full" aria-hidden="true">
48
+ {item.icon}
49
+ </span>
50
+ ) : null}
51
+ <span className="min-w-0 flex-1 truncate">{item.label}</span>
52
+ {item.badge != null && item.badge !== false ? (
53
+ shouldWrapBadge(item.badge) ? (
54
+ <span className="ml-auto inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-card-foreground/10 px-1.5 text-xs font-medium text-card-foreground/70">
55
+ {item.badge}
56
+ </span>
57
+ ) : (
58
+ <span className="ml-auto inline-flex shrink-0 items-center">{item.badge}</span>
59
+ )
60
+ ) : null}
61
+ {showChevron ? (
62
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="size-3.5 shrink-0 text-card-foreground/70" aria-hidden="true">
63
+ <path d="m9 18 6-6-6-6" strokeLinecap="round" strokeLinejoin="round" />
64
+ </svg>
65
+ ) : null}
66
+ </button>
67
+ );
68
+ }
69
+
70
+ export const DrilldownSidebar = forwardRef<HTMLElement, MainSidebarProps>(function DrilldownSidebar(
71
+ {
72
+ items,
73
+ activeKey,
74
+ expanded,
75
+ defaultExpanded = false,
76
+ onExpandedChange,
77
+ onItemSelect,
78
+ header,
79
+ footer,
80
+ panelHeader,
81
+ panelFooter,
82
+ search = false,
83
+ searchPlaceholder,
84
+ onSearchChange,
85
+ side = 'left',
86
+ density = 'comfortable',
87
+ panelWidth = 288,
88
+ motionPreset = 'expressive',
89
+ closeOnOutsideClick = true,
90
+ railClassName,
91
+ panelClassName,
92
+ itemClassName,
93
+ activeItemClassName,
94
+ backLabel = 'Back',
95
+ expandedLabel = 'Collapse menu',
96
+ className,
97
+ ...rest
98
+ },
99
+ ref,
100
+ ) {
101
+ const reduceMotion = useReducedMotion();
102
+ const effectivePreset = reduceMotion ? 'subtle' : motionPreset;
103
+
104
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
105
+ const isExpanded = expanded ?? internalExpanded;
106
+
107
+ const rootItem = useMemo(() => firstPanelItem(items, activeKey), [activeKey, items]);
108
+ const [pathKeys, setPathKeys] = useState<string[]>([]);
109
+ const path = useMemo(() => {
110
+ const resolved = resolvePath(items, pathKeys);
111
+ return resolved.length ? resolved : rootItem ? [rootItem] : [];
112
+ }, [items, pathKeys, rootItem]);
113
+
114
+ const current = path.at(-1);
115
+ const [query, setQuery] = useState('');
116
+ const searchEnabled = search !== false;
117
+ const searchCfg = typeof search === 'object' ? search : undefined;
118
+ const matcher = searchCfg?.matcher ?? defaultMatcher;
119
+ const currentItems = current?.children ?? items;
120
+ const filtered = filterLevel(currentItems, query, matcher);
121
+
122
+ function resetDeepState() {
123
+ setPathKeys([]);
124
+ setQuery('');
125
+ }
126
+
127
+ function setExpanded(next: boolean) {
128
+ if (expanded === undefined) setInternalExpanded(next);
129
+ if (!next) resetDeepState();
130
+ onExpandedChange?.(next);
131
+ }
132
+
133
+ function closePanel() {
134
+ resetDeepState();
135
+ setExpanded(false);
136
+ }
137
+
138
+ const panelRef = useRef<HTMLElement>(null);
139
+ const railRef = useRef<HTMLElement>(null);
140
+
141
+ /* Esc + outside-click close the panel. */
142
+ useEffect(() => {
143
+ if (!isExpanded) return;
144
+ function onKey(e: KeyboardEvent) {
145
+ if (e.key === 'Escape') closePanel();
146
+ }
147
+ function onPointerDown(e: PointerEvent) {
148
+ if (!closeOnOutsideClick) return;
149
+ const target = e.target as Node;
150
+ if (panelRef.current?.contains(target) || railRef.current?.contains(target)) return;
151
+ closePanel();
152
+ }
153
+ document.addEventListener('keydown', onKey);
154
+ document.addEventListener('pointerdown', onPointerDown);
155
+ return () => {
156
+ document.removeEventListener('keydown', onKey);
157
+ document.removeEventListener('pointerdown', onPointerDown);
158
+ };
159
+ // eslint-disable-next-line react-hooks/exhaustive-deps
160
+ }, [isExpanded, closeOnOutsideClick]);
161
+
162
+ /*
163
+ * Move focus into the panel when it opens — but skip the initial
164
+ * mount (covers `defaultExpanded` and SSR hydration). Otherwise the
165
+ * browser scrolls the page to bring the just-focused input into view,
166
+ * landing the user halfway down the docs page. We also pass
167
+ * `preventScroll: true` as a belt-and-braces guard.
168
+ */
169
+ const focusInitialisedRef = useRef(false);
170
+ useEffect(() => {
171
+ if (!isExpanded) {
172
+ focusInitialisedRef.current = true;
173
+ return;
174
+ }
175
+ if (!focusInitialisedRef.current) {
176
+ focusInitialisedRef.current = true;
177
+ return;
178
+ }
179
+ const id = window.setTimeout(() => {
180
+ const focusable = panelRef.current?.querySelector<HTMLElement>(
181
+ 'input, [role="searchbox"], button:not([disabled])',
182
+ );
183
+ focusable?.focus({ preventScroll: true });
184
+ }, 0);
185
+ return () => window.clearTimeout(id);
186
+ }, [isExpanded]);
187
+
188
+ function selectRailItem(item: MainSidebarItem) {
189
+ if (item.disabled) return;
190
+ onItemSelect?.(item.key, item);
191
+ if (opensPanel(item)) {
192
+ /* Toggle close when clicking the rail icon for the panel that's already open. */
193
+ if (isExpanded && pathKeys[0] === item.key) {
194
+ setExpanded(false);
195
+ return;
196
+ }
197
+ setPathKeys([item.key]);
198
+ setExpanded(true);
199
+ setQuery('');
200
+ } else {
201
+ setExpanded(false);
202
+ }
203
+ }
204
+
205
+ function selectPanelItem(item: MainSidebarItem) {
206
+ if (item.disabled) return;
207
+ onItemSelect?.(item.key, item);
208
+ if (opensPanel(item)) {
209
+ setPathKeys((prev) => (prev.at(-1) === item.key ? prev : [...prev, item.key]));
210
+ setQuery('');
211
+ }
212
+ }
213
+
214
+ function goBack() {
215
+ setPathKeys((prev) => prev.slice(0, -1));
216
+ setQuery('');
217
+ }
218
+
219
+ function updateQuery(next: string) {
220
+ setQuery(next);
221
+ onSearchChange?.(next);
222
+ }
223
+
224
+ const railOffset = density === 'compact' ? '3rem' : '3.5rem';
225
+ return (
226
+ <div
227
+ ref={ref as never}
228
+ className={cn('relative h-full', className)}
229
+ {...rest}
230
+ >
231
+ <Rail
232
+ ref={railRef}
233
+ items={items}
234
+ activeKey={activeKey}
235
+ density={density}
236
+ side={side}
237
+ header={header}
238
+ footer={footer}
239
+ itemClassName={itemClassName}
240
+ activeItemClassName={activeItemClassName}
241
+ className={railClassName}
242
+ onItemSelect={selectRailItem}
243
+ openPanelKey={isExpanded ? current?.key : undefined}
244
+ />
245
+
246
+ <AnimatePresence initial={false} mode="wait">
247
+ {isExpanded && current ? (
248
+ <motion.aside
249
+ key={current.key + ':' + path.length}
250
+ ref={panelRef}
251
+ variants={slideVariants(effectivePreset, side === 'right' ? 'right' : 'left')}
252
+ initial="initial"
253
+ animate="animate"
254
+ exit="exit"
255
+ role="menu"
256
+ aria-label={current.label}
257
+ style={{
258
+ width: typeof panelWidth === 'number' ? `${panelWidth}px` : panelWidth,
259
+ [side === 'right' ? 'right' : 'left']: railOffset,
260
+ }}
261
+ className={cn(
262
+ 'absolute top-0 z-10 flex h-full min-w-0 flex-col bg-card text-card-foreground shadow-xl',
263
+ side === 'right' ? 'border-l border-border' : 'border-r border-border',
264
+ panelClassName,
265
+ )}
266
+ >
267
+ <div className="flex h-full flex-col">
268
+ <div className="border-b border-border px-4 py-3">
269
+ {panelHeader ?? (
270
+ <div className="flex items-start justify-between gap-3">
271
+ <div className="min-w-0">
272
+ {path.length > 1 ? (
273
+ <>
274
+ <PathBreadcrumb
275
+ path={path}
276
+ onJump={(depth) => setPathKeys((prev) => prev.slice(0, depth + 1))}
277
+ className="mb-1"
278
+ />
279
+ <BackButton onClick={goBack} label={backLabel} className="mb-1 -ml-1.5" />
280
+ </>
281
+ ) : null}
282
+ <div className="truncate text-base font-semibold text-card-foreground">{current.label}</div>
283
+ {current.description ? (
284
+ <p className="mt-0.5 text-xs text-card-foreground/70">{current.description}</p>
285
+ ) : null}
286
+ </div>
287
+ <CloseButton onClick={closePanel} label={expandedLabel} />
288
+ </div>
289
+ )}
290
+ {searchEnabled ? (
291
+ <div className="mt-3">
292
+ <MenuSearch
293
+ value={query}
294
+ onValueChange={updateQuery}
295
+ placeholder={searchCfg?.placeholder ?? searchPlaceholder ?? `Search ${current.label}…`}
296
+
297
+ tone="onBrand"
298
+ />
299
+ </div>
300
+ ) : null}
301
+ </div>
302
+
303
+ <PanelList
304
+ items={filtered}
305
+ allItems={currentItems}
306
+ query={query}
307
+ noResultsLabel={searchCfg?.noResultsLabel}
308
+ activeKey={activeKey}
309
+ onItemSelect={selectPanelItem}
310
+ itemClassName={itemClassName}
311
+ activeItemClassName={activeItemClassName}
312
+ pathKeys={pathKeys}
313
+ />
314
+
315
+ {panelFooter ? (
316
+ <div className="mt-auto border-t border-border px-4 py-3">{panelFooter}</div>
317
+ ) : null}
318
+ </div>
319
+ </motion.aside>
320
+ ) : null}
321
+ </AnimatePresence>
322
+ </div>
323
+ );
324
+ });
325
+
326
+ function PanelList({
327
+ items,
328
+ allItems,
329
+ query,
330
+ noResultsLabel,
331
+ activeKey,
332
+ onItemSelect,
333
+ itemClassName,
334
+ activeItemClassName,
335
+ pathKeys,
336
+ }: {
337
+ items: MainSidebarItem[];
338
+ allItems: MainSidebarItem[];
339
+ query: string;
340
+ noResultsLabel?: ReactNode;
341
+ activeKey: string | undefined;
342
+ onItemSelect: (item: MainSidebarItem) => void;
343
+ itemClassName: string | undefined;
344
+ activeItemClassName: string | undefined;
345
+ pathKeys: string[];
346
+ }) {
347
+ if (items.length === 0 && query) {
348
+ return (
349
+ <div className="px-4 py-8 text-center text-sm text-card-foreground/70">
350
+ {noResultsLabel ?? `No items match "${query}".`}
351
+ </div>
352
+ );
353
+ }
354
+ void allItems;
355
+ return (
356
+ <div className="flex-1 overflow-y-auto p-2">
357
+ {items.map((item) => {
358
+ const onActivePath = isOnPath(item, activeKey) || pathKeys.includes(item.key);
359
+ return (
360
+ <PanelRow
361
+ key={item.key}
362
+ item={item}
363
+ active={onActivePath}
364
+ onClick={() => onItemSelect(item)}
365
+ showChevron={opensPanel(item)}
366
+ className={itemClassName}
367
+ activeClassName={activeItemClassName}
368
+ />
369
+ );
370
+ })}
371
+ </div>
372
+ );
373
+ }