@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
@@ -1,448 +1,26 @@
1
1
  'use client';
2
2
 
3
3
  /**
4
- * MainSidebar (web variant React DOM).
4
+ * IconSidebarlegacy alias.
5
5
  *
6
- * Figma rail: compact 46 px primary-blue icon strip with an accent selected item.
7
- * Expansion: controlled/uncontrolled drilldown panel that slides beside the rail.
8
- *
9
- * `IconSidebar` remains as a compatibility alias.
10
- *
11
- * Wiki: docs/components/IconSidebar.md
6
+ * The implementation moved to `@mihcm/ui/MainSidebar` and gained five
7
+ * interaction-model variants. This file remains so existing
8
+ * `import { IconSidebar } from '@mihcm/ui/IconSidebar'` consumers keep
9
+ * working without changes. New code should import from `MainSidebar`.
12
10
  */
13
- import { forwardRef, useMemo, useState, type HTMLAttributes, type ReactNode } from 'react';
14
- import { cn } from './internal/cn.js';
15
-
16
- export interface MainSidebarItem {
17
- key: string;
18
- icon: ReactNode;
19
- label: string;
20
- href?: string;
21
- description?: string;
22
- badge?: ReactNode;
23
- disabled?: boolean;
24
- children?: MainSidebarItem[];
25
- panel?: ReactNode;
26
- }
27
-
28
- export interface MainSidebarProps extends Omit<HTMLAttributes<HTMLElement>, 'onSelect'> {
29
- items: MainSidebarItem[];
30
- activeKey?: string;
31
- expanded?: boolean;
32
- defaultExpanded?: boolean;
33
- onExpandedChange?: (expanded: boolean) => void;
34
- onItemSelect?: (key: string, item: MainSidebarItem) => void;
35
- header?: ReactNode;
36
- footer?: ReactNode;
37
- panelHeader?: ReactNode;
38
- panelFooter?: ReactNode;
39
- railClassName?: string;
40
- panelClassName?: string;
41
- itemClassName?: string;
42
- activeItemClassName?: string;
43
- collapsedLabel?: string;
44
- expandedLabel?: string;
45
- backLabel?: string;
46
- showLabelsWhenExpanded?: boolean;
47
- side?: 'left' | 'right';
48
- /** Force the bottom mobile navigation pattern for docs, tests, or mobile-only shells. */
49
- mobile?: boolean;
50
- }
51
-
52
- export type IconSidebarItem = MainSidebarItem;
53
- export type IconSidebarProps = MainSidebarProps;
54
-
55
- function ChevronLeftIcon() {
56
- return (
57
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
58
- <path d="m15 18-6-6 6-6" strokeLinecap="round" strokeLinejoin="round" />
59
- </svg>
60
- );
61
- }
62
-
63
- function ChevronRightIcon() {
64
- return (
65
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
66
- <path d="m9 18 6-6-6-6" strokeLinecap="round" strokeLinejoin="round" />
67
- </svg>
68
- );
69
- }
70
-
71
- function MenuIcon() {
72
- return (
73
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden>
74
- <path d="M4 7h16M4 12h16M4 17h16" strokeLinecap="round" />
75
- </svg>
76
- );
77
- }
78
-
79
- function findItem(items: MainSidebarItem[], key?: string): MainSidebarItem | undefined {
80
- if (!key) return undefined;
81
-
82
- for (const item of items) {
83
- if (item.key === key) return item;
84
- const child = findItem(item.children ?? [], key);
85
- if (child) return child;
86
- }
87
-
88
- return undefined;
89
- }
90
-
91
- function firstPanelItem(items: MainSidebarItem[], activeKey?: string) {
92
- const active = findItem(items, activeKey);
93
- if (active?.children?.length || active?.panel) return active;
94
- return items.find((item) => item.children?.length || item.panel) ?? items[0];
95
- }
96
-
97
- function opensPanel(item: MainSidebarItem) {
98
- return Boolean(item.children?.length || item.panel);
99
- }
100
-
101
- export const MainSidebar = forwardRef<HTMLElement, MainSidebarProps>(function MainSidebar(
102
- {
103
- className,
104
- items,
105
- activeKey,
106
- expanded,
107
- defaultExpanded = false,
108
- onExpandedChange,
109
- onItemSelect,
110
- header,
111
- footer,
112
- panelHeader,
113
- panelFooter,
114
- railClassName,
115
- panelClassName,
116
- itemClassName,
117
- activeItemClassName,
118
- collapsedLabel = 'Expand main sidebar',
119
- expandedLabel = 'Collapse main sidebar',
120
- backLabel = 'Back',
121
- showLabelsWhenExpanded = true,
122
- side = 'left',
123
- mobile = false,
124
- ...props
125
- },
126
- ref,
127
- ) {
128
- const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
129
- const isExpanded = expanded ?? internalExpanded;
130
- const rootItem = useMemo(() => firstPanelItem(items, activeKey), [activeKey, items]);
131
- const [pathKeys, setPathKeys] = useState<string[]>([]);
132
- const path = useMemo(() => {
133
- const resolved = pathKeys
134
- .map((key) => findItem(items, key))
135
- .filter((item): item is MainSidebarItem => Boolean(item));
136
- return resolved.length ? resolved : rootItem ? [rootItem] : [];
137
- }, [items, pathKeys, rootItem]);
138
-
139
- const current = path.at(-1);
140
- const currentItems = current?.children ?? items;
141
- const isRightSide = side === 'right';
142
-
143
- function setExpanded(next: boolean) {
144
- if (expanded === undefined) setInternalExpanded(next);
145
- if (!next) setPathKeys([]);
146
- onExpandedChange?.(next);
147
- }
148
-
149
- function selectRailItem(item: MainSidebarItem) {
150
- if (item.disabled) return;
151
- onItemSelect?.(item.key, item);
152
-
153
- if (opensPanel(item)) {
154
- setPathKeys([item.key]);
155
- setExpanded(true);
156
- } else {
157
- setPathKeys([]);
158
- }
159
- }
160
-
161
- function selectPanelItem(item: MainSidebarItem) {
162
- if (item.disabled) return;
163
- onItemSelect?.(item.key, item);
164
-
165
- if (opensPanel(item)) {
166
- setExpanded(true);
167
- setPathKeys((previous) =>
168
- previous.at(-1) === item.key ? previous : [...previous, item.key],
169
- );
170
- }
171
- }
172
-
173
- const panel = isExpanded ? (
174
- <div
175
- className={cn(
176
- 'min-w-0 bg-card text-card-foreground shadow-mi-card',
177
- isRightSide ? 'border-l border-border' : 'border-r border-border',
178
- 'w-full md:w-72 md:flex-none',
179
- isRightSide
180
- ? 'animate-in fade-in slide-in-from-right-2 duration-300'
181
- : 'animate-in fade-in slide-in-from-left-2 duration-300',
182
- panelClassName,
183
- )}
184
- >
185
- <div className="flex h-full flex-col">
186
- <div className="border-b border-border px-5 py-4">
187
- {panelHeader ?? (
188
- <div className="flex items-center justify-between gap-3">
189
- <div className="min-w-0">
190
- <div className="text-sm font-semibold">{current?.label ?? 'Main Sidebar'}</div>
191
- {current?.description ? (
192
- <p className="mt-1 text-xs text-muted-foreground">{current.description}</p>
193
- ) : null}
194
- </div>
195
- <button
196
- type="button"
197
- aria-label={expandedLabel}
198
- onClick={() => setExpanded(false)}
199
- className="grid size-8 shrink-0 place-items-center rounded-md text-muted-foreground transition hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
200
- >
201
- {isRightSide ? <ChevronRightIcon /> : <ChevronLeftIcon />}
202
- </button>
203
- </div>
204
- )}
205
- </div>
206
-
207
- <div className="min-h-0 flex-1 overflow-y-auto px-3 py-4">
208
- {path.length > 1 ? (
209
- <button
210
- type="button"
211
- onClick={() => setPathKeys((previous) => previous.slice(0, -1))}
212
- className="mb-3 inline-flex items-center gap-2 rounded-md px-2 py-1.5 text-xs font-medium text-muted-foreground transition hover:bg-muted hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
213
- >
214
- <span className="[&_svg]:size-4">
215
- <ChevronLeftIcon />
216
- </span>
217
- {backLabel}
218
- </button>
219
- ) : null}
220
-
221
- {current?.panel ? (
222
- <div className="animate-in fade-in slide-in-from-left-2 duration-200">
223
- {current.panel}
224
- </div>
225
- ) : (
226
- <div className="grid gap-1 animate-in fade-in slide-in-from-right-2 duration-200">
227
- {currentItems.map((item) => {
228
- const isActive = item.key === activeKey;
229
- const hasChildren = opensPanel(item);
230
- return (
231
- <button
232
- key={item.key}
233
- type="button"
234
- disabled={item.disabled}
235
- onClick={() => selectPanelItem(item)}
236
- className={cn(
237
- 'group flex w-full items-center gap-3 rounded-md px-3 py-2.5 text-left transition',
238
- 'focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
239
- item.disabled && 'cursor-not-allowed opacity-40',
240
- isActive ? 'bg-accent text-primary-foreground' : 'hover:bg-muted',
241
- )}
242
- >
243
- <span className="grid size-8 shrink-0 place-items-center rounded-md bg-muted text-foreground group-hover:bg-background [&_svg]:size-4">
244
- {item.icon}
245
- </span>
246
- <span className="min-w-0 flex-1">
247
- <span className="block truncate text-sm font-medium">{item.label}</span>
248
- {item.description ? (
249
- <span
250
- className={cn(
251
- 'mt-0.5 block text-xs',
252
- isActive ? 'text-primary-foreground/80' : 'text-muted-foreground',
253
- )}
254
- >
255
- {item.description}
256
- </span>
257
- ) : null}
258
- </span>
259
- {item.badge ? <span className="shrink-0">{item.badge}</span> : null}
260
- {hasChildren ? (
261
- <span className="shrink-0 text-muted-foreground [&_svg]:size-4">
262
- <ChevronRightIcon />
263
- </span>
264
- ) : null}
265
- </button>
266
- );
267
- })}
268
- </div>
269
- )}
270
- </div>
271
-
272
- {showLabelsWhenExpanded ? (
273
- <div className="border-t border-border px-5 py-4 text-xs text-muted-foreground">
274
- {currentItems.length} navigation {currentItems.length === 1 ? 'item' : 'items'}
275
- </div>
276
- ) : null}
277
- {panelFooter ? <div className="border-t border-border px-5 py-4">{panelFooter}</div> : null}
278
- </div>
279
- </div>
280
- ) : null;
281
-
282
- return (
283
- <>
284
- <nav
285
- aria-label="Main Sidebar"
286
- data-expanded={isExpanded ? 'true' : 'false'}
287
- data-mobile={mobile ? 'true' : undefined}
288
- className={cn('absolute inset-x-0 bottom-0 z-30', !mobile && 'md:hidden', className)}
289
- >
290
- {isExpanded ? (
291
- <div className="absolute inset-x-3 bottom-full mb-3 max-h-96 overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-mi-popover">
292
- {panel}
293
- </div>
294
- ) : null}
295
- <div
296
- className={cn(
297
- 'flex items-stretch border-t border-primary/80 bg-primary text-primary-foreground',
298
- railClassName,
299
- )}
300
- >
301
- <div className="flex min-w-0 flex-1 overflow-x-auto">
302
- {items.map((item) => {
303
- const isActive = item.key === activeKey;
304
- const mobileItemClassName = cn(
305
- 'grid min-w-18 flex-1 place-items-center gap-1 px-3 py-2 text-caption transition',
306
- 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-foreground/70',
307
- item.disabled && 'cursor-not-allowed opacity-40',
308
- isActive ? 'bg-accent text-primary-foreground' : 'text-primary-foreground/75',
309
- itemClassName,
310
- isActive && activeItemClassName,
311
- );
312
- const mobileItemContent = (
313
- <>
314
- <span className="[&_svg]:size-5">{item.icon}</span>
315
- <span className="max-w-16 truncate">{item.label}</span>
316
- </>
317
- );
318
-
319
- if (item.href && !item.disabled && !item.children?.length && !item.panel) {
320
- return (
321
- <a
322
- key={item.key}
323
- href={item.href}
324
- aria-label={item.label}
325
- aria-current={isActive ? 'page' : undefined}
326
- aria-disabled={item.disabled || undefined}
327
- className={mobileItemClassName}
328
- >
329
- {mobileItemContent}
330
- </a>
331
- );
332
- }
333
-
334
- return (
335
- <button
336
- key={item.key}
337
- type="button"
338
- disabled={item.disabled}
339
- aria-label={item.label}
340
- aria-current={isActive ? 'page' : undefined}
341
- aria-disabled={item.disabled || undefined}
342
- onClick={() => selectRailItem(item)}
343
- className={mobileItemClassName}
344
- >
345
- {mobileItemContent}
346
- </button>
347
- );
348
- })}
349
- </div>
350
- <button
351
- type="button"
352
- aria-label={isExpanded ? expandedLabel : collapsedLabel}
353
- aria-expanded={isExpanded}
354
- onClick={() => setExpanded(!isExpanded)}
355
- className="grid min-w-16 place-items-center gap-1 border-l border-primary-foreground/15 px-3 py-2 text-caption text-primary-foreground/85 transition hover:bg-primary-foreground/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-foreground/70"
356
- >
357
- <span className="[&_svg]:size-5">
358
- <MenuIcon />
359
- </span>
360
- Menu
361
- </button>
362
- </div>
363
- </nav>
364
-
365
- <aside
366
- ref={ref}
367
- aria-label="Main Sidebar"
368
- data-expanded={isExpanded ? 'true' : 'false'}
369
- data-side={side}
370
- data-mobile={mobile ? 'false' : undefined}
371
- className={cn(
372
- 'h-full flex-shrink-0 overflow-hidden bg-primary text-primary-foreground',
373
- mobile ? 'hidden' : 'hidden md:flex',
374
- isRightSide && 'flex-row-reverse',
375
- 'transition-[width] duration-300 ease-out',
376
- isExpanded ? 'w-[20.875rem]' : 'w-11.5',
377
- className,
378
- )}
379
- {...props}
380
- >
381
- <div
382
- className={cn(
383
- 'flex w-11.5 shrink-0 flex-col items-center gap-9 overflow-hidden bg-primary pb-20',
384
- railClassName,
385
- )}
386
- >
387
- {header ? <div className="flex w-full justify-center">{header}</div> : null}
388
- <nav className="flex flex-col items-center" aria-label="Primary">
389
- {items.map((item) => {
390
- const isActive = item.key === activeKey;
391
- const shared = {
392
- 'aria-label': item.label,
393
- 'aria-current': isActive ? ('page' as const) : undefined,
394
- 'aria-disabled': item.disabled || undefined,
395
- title: item.label,
396
- className: cn(
397
- 'grid h-11.5 w-11.5 shrink-0 place-items-center transition-colors duration-150',
398
- 'focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-foreground/70',
399
- item.disabled ? 'cursor-not-allowed opacity-40' : 'cursor-pointer',
400
- isActive
401
- ? 'bg-accent text-primary-foreground opacity-100'
402
- : 'text-primary-foreground opacity-70 hover:opacity-100 focus-visible:opacity-100',
403
- itemClassName,
404
- isActive && activeItemClassName,
405
- ),
406
- };
407
- const content = <span className="[&_svg]:size-5">{item.icon}</span>;
408
-
409
- if (item.href && !item.disabled && !item.children?.length && !item.panel) {
410
- return (
411
- <a key={item.key} href={item.href} {...shared}>
412
- {content}
413
- </a>
414
- );
415
- }
416
-
417
- return (
418
- <button
419
- key={item.key}
420
- type="button"
421
- disabled={item.disabled}
422
- onClick={() => selectRailItem(item)}
423
- {...shared}
424
- >
425
- {content}
426
- </button>
427
- );
428
- })}
429
- </nav>
430
- {footer ? <div className="mt-auto flex w-full justify-center">{footer}</div> : null}
431
- <button
432
- type="button"
433
- aria-label={isExpanded ? expandedLabel : collapsedLabel}
434
- aria-expanded={isExpanded}
435
- onClick={() => setExpanded(!isExpanded)}
436
- className="mb-3 grid size-8 place-items-center rounded-md text-primary-foreground/80 transition hover:bg-primary-foreground/10 hover:text-primary-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-foreground/70"
437
- >
438
- <MenuIcon />
439
- </button>
440
- </div>
441
-
442
- {panel}
443
- </aside>
444
- </>
445
- );
446
- });
447
11
 
448
- export const IconSidebar = MainSidebar;
12
+ export {
13
+ IconSidebar,
14
+ MainSidebar,
15
+ type IconSidebarItem,
16
+ type IconSidebarProps,
17
+ type MainSidebarCollapsible,
18
+ type MainSidebarColorScheme,
19
+ type MainSidebarDensity,
20
+ type MainSidebarItem,
21
+ type MainSidebarMotionPreset,
22
+ type MainSidebarProps,
23
+ type MainSidebarSearchConfig,
24
+ type MainSidebarSide,
25
+ type MainSidebarVariant,
26
+ } from './MainSidebar/index.js';
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Shared back / chevron button. Used by drilldown + columns + floating
5
+ * panel headers.
6
+ */
7
+ import { cn } from '../internal/cn.js';
8
+
9
+ interface BackButtonProps {
10
+ onClick: () => void;
11
+ label: string;
12
+ className?: string;
13
+ }
14
+
15
+ export function BackButton({ onClick, label, className }: BackButtonProps) {
16
+ return (
17
+ <button
18
+ type="button"
19
+ onClick={onClick}
20
+ className={cn(
21
+ 'inline-flex items-center gap-1 rounded-md px-1.5 py-1 text-xs font-medium text-card-foreground/70 transition-colors',
22
+ 'hover:bg-card-foreground/10 hover:text-card-foreground',
23
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-card-foreground/40',
24
+ className,
25
+ )}
26
+ >
27
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="size-3.5" aria-hidden="true">
28
+ <path d="m15 18-6-6 6-6" strokeLinecap="round" strokeLinejoin="round" />
29
+ </svg>
30
+ {label}
31
+ </button>
32
+ );
33
+ }
34
+
35
+ interface CloseButtonProps {
36
+ onClick: () => void;
37
+ label: string;
38
+ className?: string;
39
+ }
40
+
41
+ export function CloseButton({ onClick, label, className }: CloseButtonProps) {
42
+ return (
43
+ <button
44
+ type="button"
45
+ onClick={onClick}
46
+ aria-label={label}
47
+ className={cn(
48
+ 'grid size-8 shrink-0 place-items-center rounded-md text-card-foreground/70 transition hover:bg-card-foreground/10 hover:text-card-foreground',
49
+ 'focus:outline-none focus-visible:ring-2 focus-visible:ring-card-foreground/40',
50
+ className,
51
+ )}
52
+ >
53
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="size-4" aria-hidden="true">
54
+ <path d="M6 6l12 12M18 6 6 18" strokeLinecap="round" />
55
+ </svg>
56
+ </button>
57
+ );
58
+ }
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Path breadcrumb shared by drilldown / floating / columns / command / hover.
5
+ *
6
+ * Renders the drilled-into ancestors as clickable text crumbs so the user can
7
+ * jump back to any level in one click. Used in the panel header when the
8
+ * `path` is deeper than the root.
9
+ *
10
+ * <PathBreadcrumb path={[people, onboarding]} onJump={(idx) => ...} />
11
+ * ⇒ People › Onboarding
12
+ *
13
+ * `path` is the full ancestor list (including the current leaf). The leaf
14
+ * itself renders as the panel title, so the breadcrumb stops at
15
+ * `path.length - 1`.
16
+ */
17
+ import { cn } from '../internal/cn.js';
18
+ import type { MainSidebarItem } from './types.js';
19
+
20
+ interface PathBreadcrumbProps {
21
+ path: MainSidebarItem[];
22
+ /** Click handler — receives the depth index to jump to. */
23
+ onJump: (depth: number) => void;
24
+ className?: string;
25
+ }
26
+
27
+ export function PathBreadcrumb({ path, onJump, className }: PathBreadcrumbProps) {
28
+ if (path.length <= 1) return null;
29
+ const ancestors = path.slice(0, -1);
30
+ return (
31
+ <nav
32
+ aria-label="Breadcrumb"
33
+ className={cn('flex flex-wrap items-center gap-1 text-xs text-card-foreground/70', className)}
34
+ >
35
+ <ol className="flex flex-wrap items-center gap-1">
36
+ {ancestors.map((node, idx) => (
37
+ <li key={node.key} className="flex items-center gap-1">
38
+ <button
39
+ type="button"
40
+ onClick={() => onJump(idx)}
41
+ className="rounded px-1 py-0.5 text-xs font-medium text-card-foreground/70 transition-colors hover:bg-card-foreground/10 hover:text-card-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-card-foreground/40"
42
+ >
43
+ {node.label}
44
+ </button>
45
+ <span aria-hidden="true" className="text-card-foreground/50">
46
+
47
+ </span>
48
+ </li>
49
+ ))}
50
+ </ol>
51
+ </nav>
52
+ );
53
+ }