@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,117 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * MainSidebar — `mobile` rendering.
5
+ *
6
+ * On phones and tablets the full sidebar (rail or expanded) consumes too
7
+ * much horizontal space. This component renders a single hamburger button
8
+ * inline; tapping it opens the wide `ExpandedSidebar` inside a `Sheet`
9
+ * overlay slid in from the chosen `side`. The page area stays clear.
10
+ *
11
+ * Triggered via `<MainSidebar mobile items={…} />` — the dispatcher routes
12
+ * here when `mobile === true`. Sheet handles outside-tap dismissal, Esc,
13
+ * focus-trap, and the scrim, so this component just composes existing
14
+ * primitives.
15
+ */
16
+ import { forwardRef, useState, type ReactNode } from 'react';
17
+ import { cn } from '../internal/cn.js';
18
+ import { Sheet, SheetContent } from '../Sheet.js';
19
+ import { ExpandedSidebar } from './expanded.js';
20
+ import type { MainSidebarProps } from './types.js';
21
+
22
+ function HamburgerIcon({ className }: { className?: string }) {
23
+ return (
24
+ <svg
25
+ viewBox="0 0 24 24"
26
+ fill="none"
27
+ stroke="currentColor"
28
+ strokeWidth="2"
29
+ strokeLinecap="round"
30
+ strokeLinejoin="round"
31
+ className={className}
32
+ aria-hidden="true"
33
+ >
34
+ <path d="M3 6h18M3 12h18M3 18h18" />
35
+ </svg>
36
+ );
37
+ }
38
+
39
+ interface MobileSidebarProps extends MainSidebarProps {
40
+ /** Render-prop override for the trigger — receives an `onClick` to open. */
41
+ trigger?: (props: { onClick: () => void; 'aria-label': string }) => ReactNode;
42
+ /** Class hook for the default hamburger button. Ignored when `trigger` is set. */
43
+ triggerClassName?: string;
44
+ /** Accessible label on the hamburger. Default: "Open menu". */
45
+ triggerLabel?: string;
46
+ }
47
+
48
+ export const MobileSidebar = forwardRef<HTMLButtonElement, MobileSidebarProps>(
49
+ function MobileSidebar(
50
+ {
51
+ side = 'left',
52
+ trigger,
53
+ triggerClassName,
54
+ triggerLabel = 'Open menu',
55
+ colorScheme: _colorScheme,
56
+ style: _style,
57
+ className: _className,
58
+ ...sidebarProps
59
+ },
60
+ ref,
61
+ ) {
62
+ void _colorScheme;
63
+ void _style;
64
+ void _className;
65
+ const [open, setOpen] = useState(false);
66
+ const sheetSide = side === 'right' ? 'right' : 'left';
67
+
68
+ return (
69
+ <>
70
+ {trigger ? (
71
+ trigger({ onClick: () => setOpen(true), 'aria-label': triggerLabel })
72
+ ) : (
73
+ <button
74
+ ref={ref}
75
+ type="button"
76
+ aria-label={triggerLabel}
77
+ aria-haspopup="menu"
78
+ aria-expanded={open}
79
+ onClick={() => setOpen(true)}
80
+ className={cn(
81
+ 'inline-flex size-10 items-center justify-center rounded-md text-foreground',
82
+ 'hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
83
+ triggerClassName,
84
+ )}
85
+ >
86
+ <HamburgerIcon className="size-5" />
87
+ </button>
88
+ )}
89
+
90
+ <Sheet open={open} onOpenChange={setOpen}>
91
+ <SheetContent
92
+ side={sheetSide}
93
+ size="sm"
94
+ className="p-0 bg-primary border-0"
95
+ aria-label="Main navigation"
96
+ >
97
+ <ExpandedSidebar
98
+ {...sidebarProps}
99
+ side={sheetSide}
100
+ showCollapseToggle
101
+ onCollapse={() => setOpen(false)}
102
+ collapsedLabel="Close menu"
103
+ /*
104
+ * Make the sidebar fill the sheet — the Sheet's `size` prop
105
+ * controls the visible width, the sidebar should not impose
106
+ * its own width on top of that or a stripe of the sheet's
107
+ * card background shows through.
108
+ */
109
+ expandedWidth="100%"
110
+ className="h-full w-full border-0"
111
+ />
112
+ </SheetContent>
113
+ </Sheet>
114
+ </>
115
+ );
116
+ },
117
+ );
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Motion presets for `MainSidebar` variants.
3
+ *
4
+ * Three intensity levels. Variants pull from this single source so the
5
+ * design-system motion vocabulary stays consistent.
6
+ *
7
+ * Reduced motion: every variant component checks `prefers-reduced-motion`
8
+ * and falls back to instant cuts when the OS-level preference is set.
9
+ */
10
+ import type { Transition, Variants } from 'motion/react';
11
+ import type { MainSidebarMotionPreset } from './types.js';
12
+
13
+ interface MotionConfig {
14
+ /** Slide distance in px on enter. */
15
+ distance: number;
16
+ /** Motion transition. */
17
+ transition: Transition;
18
+ }
19
+
20
+ export const MOTION_PRESETS: Record<MainSidebarMotionPreset, MotionConfig> = {
21
+ subtle: {
22
+ distance: 4,
23
+ transition: { duration: 0.18, ease: [0.16, 1, 0.3, 1] },
24
+ },
25
+ standard: {
26
+ distance: 8,
27
+ transition: { duration: 0.24, ease: [0.32, 0.72, 0, 1] },
28
+ },
29
+ expressive: {
30
+ distance: 16,
31
+ transition: { type: 'spring', stiffness: 320, damping: 28, mass: 0.9 },
32
+ },
33
+ };
34
+
35
+ export function slideVariants(
36
+ preset: MainSidebarMotionPreset,
37
+ fromSide: 'left' | 'right',
38
+ ): Variants {
39
+ const cfg = MOTION_PRESETS[preset];
40
+ const sign = fromSide === 'left' ? -1 : 1;
41
+ return {
42
+ initial: { opacity: 0, x: sign * cfg.distance },
43
+ animate: { opacity: 1, x: 0, transition: cfg.transition },
44
+ exit: { opacity: 0, x: sign * cfg.distance, transition: cfg.transition },
45
+ };
46
+ }
47
+
48
+ export function fadeVariants(preset: MainSidebarMotionPreset): Variants {
49
+ const cfg = MOTION_PRESETS[preset];
50
+ return {
51
+ initial: { opacity: 0 },
52
+ animate: { opacity: 1, transition: cfg.transition },
53
+ exit: { opacity: 0, transition: cfg.transition },
54
+ };
55
+ }
56
+
57
+ export function scaleVariants(preset: MainSidebarMotionPreset): Variants {
58
+ const cfg = MOTION_PRESETS[preset];
59
+ return {
60
+ initial: { opacity: 0, scale: 0.96, y: -4 },
61
+ animate: { opacity: 1, scale: 1, y: 0, transition: cfg.transition },
62
+ exit: { opacity: 0, scale: 0.96, y: -4, transition: cfg.transition },
63
+ };
64
+ }
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Shared rail — the fixed icon strip on the side. Used by all five variants.
5
+ */
6
+ import { forwardRef, type ReactNode } from 'react';
7
+ import { cn } from '../internal/cn.js';
8
+ import { Tooltip } from '../Tooltip.js';
9
+ import { isOnPath, shouldWrapBadge } from './helpers.js';
10
+ import type {
11
+ MainSidebarDensity,
12
+ MainSidebarItem,
13
+ MainSidebarSide,
14
+ } from './types.js';
15
+
16
+ const RAIL_WIDTH = {
17
+ compact: 'w-12',
18
+ comfortable: 'w-[3.5rem]',
19
+ } as const;
20
+
21
+ const RAIL_BUTTON = {
22
+ compact: 'h-12 w-full',
23
+ comfortable: 'h-14 w-full',
24
+ } as const;
25
+
26
+ interface RailProps {
27
+ items: MainSidebarItem[];
28
+ activeKey: string | undefined;
29
+ density?: MainSidebarDensity;
30
+ side?: MainSidebarSide;
31
+ header?: ReactNode;
32
+ footer?: ReactNode;
33
+ itemClassName: string | undefined;
34
+ activeItemClassName: string | undefined;
35
+ className: string | undefined;
36
+ onItemSelect: (item: MainSidebarItem) => void;
37
+ /** Item key whose panel is currently visible (for aria-expanded). */
38
+ openPanelKey: string | undefined;
39
+ /** Used for the toggle button aria-label. */
40
+ ariaLabel?: string;
41
+ }
42
+
43
+ export const Rail = forwardRef<HTMLElement, RailProps>(function Rail(
44
+ {
45
+ items,
46
+ activeKey,
47
+ density = 'comfortable',
48
+ side = 'left',
49
+ header,
50
+ footer,
51
+ itemClassName,
52
+ activeItemClassName,
53
+ className,
54
+ onItemSelect,
55
+ openPanelKey,
56
+ ariaLabel = 'Main navigation',
57
+ },
58
+ ref,
59
+ ) {
60
+ const railWidth = RAIL_WIDTH[density];
61
+ const buttonSize = RAIL_BUTTON[density];
62
+
63
+ return (
64
+ <nav
65
+ ref={ref}
66
+ aria-label={ariaLabel}
67
+ className={cn(
68
+ 'relative z-20 flex h-full shrink-0 flex-col items-stretch bg-primary text-primary-foreground',
69
+ railWidth,
70
+ side === 'right' ? 'border-l border-primary-700/40' : 'border-r border-primary-700/40',
71
+ className,
72
+ )}
73
+ >
74
+ {header ? <div className="flex w-full items-center justify-center">{header}</div> : null}
75
+ <div className="flex w-full flex-col items-stretch">
76
+ {items.map((item) => {
77
+ /* Highlight the rail icon when the active leaf is anywhere in this item's subtree. */
78
+ const isActive = isOnPath(item, activeKey);
79
+ const isOpen = openPanelKey === item.key;
80
+ const tooltipSide = side === 'right' ? 'left' : 'right';
81
+ return (
82
+ <Tooltip
83
+ key={item.key}
84
+ content={item.label}
85
+ side={tooltipSide}
86
+ delayMs={80}
87
+ className="flex w-full"
88
+ >
89
+ <button
90
+ type="button"
91
+ data-item-key={item.key}
92
+ disabled={item.disabled}
93
+ aria-current={isActive ? 'page' : undefined}
94
+ aria-expanded={isOpen}
95
+ aria-haspopup={item.children?.length || item.panel ? 'menu' : undefined}
96
+ aria-label={item.label}
97
+ onClick={() => onItemSelect(item)}
98
+ className={cn(
99
+ 'relative grid place-items-center transition-colors outline-none',
100
+ 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-foreground/60',
101
+ buttonSize,
102
+ 'text-primary-foreground/85 hover:bg-primary-foreground/10 hover:text-primary-foreground',
103
+ 'disabled:cursor-not-allowed disabled:opacity-40',
104
+ isActive && 'bg-accent text-accent-foreground hover:bg-accent',
105
+ isActive && activeItemClassName,
106
+ itemClassName,
107
+ )}
108
+ >
109
+ <span className="grid size-[18px] place-items-center [&_svg]:size-full" aria-hidden="true">
110
+ {item.icon}
111
+ </span>
112
+ <span className="sr-only">{item.label}</span>
113
+ {item.badge != null && item.badge !== false ? (
114
+ shouldWrapBadge(item.badge) ? (
115
+ <span
116
+ aria-hidden="true"
117
+ className="pointer-events-none absolute right-1 top-1 grid h-4 min-w-4 place-items-center rounded-full bg-destructive px-1 text-[10px] font-semibold text-destructive-foreground"
118
+ >
119
+ {item.badge}
120
+ </span>
121
+ ) : (
122
+ <span aria-hidden="true" className="pointer-events-none absolute right-1 top-1">
123
+ {item.badge}
124
+ </span>
125
+ )
126
+ ) : null}
127
+ </button>
128
+ </Tooltip>
129
+ );
130
+ })}
131
+ </div>
132
+ {footer ? (
133
+ <div className="mt-auto flex w-full items-center justify-center">{footer}</div>
134
+ ) : null}
135
+ </nav>
136
+ );
137
+ });
@@ -0,0 +1,99 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Shared MenuSearch input used by all variants. Token-styled, accessible,
5
+ * with a clear affordance.
6
+ */
7
+ import { forwardRef, type ChangeEventHandler } from 'react';
8
+ import { cn } from '../internal/cn.js';
9
+
10
+ interface MenuSearchProps {
11
+ value: string;
12
+ onValueChange: (next: string) => void;
13
+ placeholder?: string;
14
+ className?: string;
15
+ /** Auto-focus on mount — useful for command variant. */
16
+ autoFocus?: boolean;
17
+ /** Aria label for the input. Defaults to the placeholder. */
18
+ ariaLabel?: string;
19
+ /**
20
+ * Visual tone. `light` (default) renders the input on neutral panel
21
+ * backgrounds. `onBrand` renders a semi-transparent input legible on the
22
+ * brand-primary sidebar surface.
23
+ */
24
+ tone?: 'light' | 'onBrand';
25
+ }
26
+
27
+ export const MenuSearch = forwardRef<HTMLInputElement, MenuSearchProps>(function MenuSearch(
28
+ { value, onValueChange, placeholder = 'Search menu…', className, autoFocus, ariaLabel, tone = 'light' },
29
+ ref,
30
+ ) {
31
+ const onChange: ChangeEventHandler<HTMLInputElement> = (e) => onValueChange(e.target.value);
32
+ const onBrand = tone === 'onBrand';
33
+
34
+ return (
35
+ <div className={cn('relative flex w-full items-center', className)}>
36
+ <SearchIcon
37
+ className={cn(
38
+ 'pointer-events-none absolute left-2.5 size-4',
39
+ /*
40
+ * `onBrand` uses card-foreground tokens. Both the rail (bg-primary)
41
+ * and any drill panel (bg-card) have their *-foreground tokens
42
+ * overridden to the same scheme.fg via `colorScheme`, so the
43
+ * search adapts cleanly on every themed surface AND on the
44
+ * default-brand panel (where card-foreground is the page dark
45
+ * foreground over a white card → readable).
46
+ */
47
+ onBrand ? 'text-card-foreground/60' : 'text-muted-foreground',
48
+ )}
49
+ />
50
+ <input
51
+ ref={ref}
52
+ type="search"
53
+ value={value}
54
+ onChange={onChange}
55
+ placeholder={placeholder}
56
+ aria-label={ariaLabel ?? placeholder}
57
+ autoFocus={autoFocus}
58
+ className={cn(
59
+ 'h-9 w-full rounded-md pl-8 pr-8 text-sm',
60
+ onBrand
61
+ ? 'border border-card-foreground/20 bg-card-foreground/10 text-card-foreground placeholder:text-card-foreground/60 focus:outline-none focus:border-card-foreground/40 focus:ring-2 focus:ring-card-foreground/20'
62
+ : 'border border-border bg-background text-foreground shadow-mi-input placeholder:text-muted-foreground focus:outline-none focus:border-primary focus:ring-2 focus:ring-ring/30',
63
+ )}
64
+ />
65
+ {value ? (
66
+ <button
67
+ type="button"
68
+ onClick={() => onValueChange('')}
69
+ aria-label="Clear search"
70
+ className={cn(
71
+ 'absolute right-1 grid size-7 place-items-center rounded focus-visible:outline-none focus-visible:ring-2',
72
+ onBrand
73
+ ? 'text-card-foreground/60 hover:bg-card-foreground/10 hover:text-card-foreground focus-visible:ring-card-foreground/40'
74
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring',
75
+ )}
76
+ >
77
+ <ClearIcon className="size-3.5" />
78
+ </button>
79
+ ) : null}
80
+ </div>
81
+ );
82
+ });
83
+
84
+ function SearchIcon({ className }: { className?: string }) {
85
+ return (
86
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className={className} aria-hidden="true">
87
+ <circle cx="11" cy="11" r="7" />
88
+ <path d="m20 20-3.5-3.5" strokeLinecap="round" />
89
+ </svg>
90
+ );
91
+ }
92
+
93
+ function ClearIcon({ className }: { className?: string }) {
94
+ return (
95
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className={className} aria-hidden="true">
96
+ <path d="M6 6l12 12M18 6 6 18" strokeLinecap="round" />
97
+ </svg>
98
+ );
99
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Shared types for `MainSidebar` and its five variants.
3
+ */
4
+ import type { HTMLAttributes, ReactNode } from 'react';
5
+
6
+ export interface MainSidebarItem {
7
+ key: string;
8
+ icon?: ReactNode;
9
+ label: string;
10
+ href?: string;
11
+ description?: string;
12
+ badge?: ReactNode;
13
+ disabled?: boolean;
14
+ children?: MainSidebarItem[];
15
+ /** Override the auto-rendered child list with a custom panel. */
16
+ panel?: ReactNode;
17
+ }
18
+
19
+ export type MainSidebarVariant =
20
+ | 'drilldown'
21
+ | 'floating'
22
+ | 'columns'
23
+ | 'command'
24
+ | 'hover';
25
+
26
+ export type MainSidebarSide = 'left' | 'right';
27
+
28
+ export type MainSidebarDensity = 'compact' | 'comfortable';
29
+
30
+ export type MainSidebarCollapsible = 'icon' | 'none';
31
+
32
+ export type MainSidebarMotionPreset = 'subtle' | 'standard' | 'expressive';
33
+
34
+ /**
35
+ * Per-instance colour overrides. Each token is optional and applies via a
36
+ * scoped CSS-variable override on the sidebar root, so the rest of the
37
+ * page is unaffected. Omitted tokens fall back to the design-system brand
38
+ * defaults. Structure mirrors shadcn's sidebar token taxonomy
39
+ * (`--sidebar-background`, `--sidebar-accent`, etc.).
40
+ *
41
+ * ### Quick-set shorthands
42
+ *
43
+ * | Token | Default | Sets |
44
+ * |---|---|---|
45
+ * | `bg` | `var(--color-primary)` | Both `railBg` AND `panelBg` |
46
+ * | `fg` | `var(--color-primary-foreground)` | Both `railFg` AND `panelFg` |
47
+ *
48
+ * ### Granular tokens (override individual surfaces)
49
+ *
50
+ * | Token | Default | Applies to |
51
+ * |---|---|---|
52
+ * | `railBg` | `var(--color-primary)` | Icon rail + wide expanded sidebar background |
53
+ * | `railFg` | `var(--color-primary-foreground)` | Icon rail + expanded text and icons |
54
+ * | `panelBg` | `var(--color-card)` | Drill/floating/columns/command/hover panel surface |
55
+ * | `panelFg` | `var(--color-card-foreground)` | Panel titles, item text, icons |
56
+ * | `accentBg` | `var(--color-accent)` | Active item background (rail + panel + expanded) |
57
+ * | `accentFg` | `var(--color-accent-foreground)` | Active item text |
58
+ * | `border` | `var(--color-border)` | All internal dividers + outer rail border |
59
+ * | `mutedFg` | `text-card-foreground/70` | Descriptions, breadcrumb crumbs, icon hints |
60
+ * | `hoverBg` | `card-foreground/10` | Hover overlay on every interactive row |
61
+ * | `ring` | `var(--color-ring)` | Focus ring on every interactive row |
62
+ * | `tooltipBg` | `var(--color-foreground)` | Rail-icon hover tooltip surface |
63
+ * | `tooltipFg` | `var(--color-background)` | Rail-icon hover tooltip text |
64
+ *
65
+ * Example — fully bespoke whitelabel:
66
+ *
67
+ * ```tsx
68
+ * <MainSidebar
69
+ * colorScheme={{
70
+ * railBg: '#0a2540', railFg: '#ffffff',
71
+ * panelBg: '#11304b', panelFg: '#ffffff',
72
+ * accentBg: '#ff5a1f', accentFg: '#ffffff',
73
+ * mutedFg: 'rgb(255 255 255 / 0.6)',
74
+ * hoverBg: 'rgb(255 255 255 / 0.08)',
75
+ * border: 'rgb(255 255 255 / 0.16)',
76
+ * ring: '#ff5a1f',
77
+ * tooltipBg: '#ffffff', tooltipFg: '#0a2540',
78
+ * }}
79
+ * />
80
+ * ```
81
+ */
82
+ export interface MainSidebarColorScheme {
83
+ /* Shorthands (set both rail + panel at once) */
84
+ bg?: string;
85
+ fg?: string;
86
+
87
+ /* Per-surface */
88
+ railBg?: string;
89
+ railFg?: string;
90
+ panelBg?: string;
91
+ panelFg?: string;
92
+
93
+ /* Accent (active state) */
94
+ accentBg?: string;
95
+ accentFg?: string;
96
+
97
+ /* Lines + dividers */
98
+ border?: string;
99
+
100
+ /* Muted text + hover overlay */
101
+ mutedFg?: string;
102
+ hoverBg?: string;
103
+
104
+ /* Focus ring */
105
+ ring?: string;
106
+
107
+ /* Tooltip surface (rail-icon hover) */
108
+ tooltipBg?: string;
109
+ tooltipFg?: string;
110
+ }
111
+
112
+ export interface MainSidebarSearchConfig {
113
+ placeholder?: string;
114
+ /** Custom predicate; defaults to case-insensitive `item.label` match. */
115
+ matcher?: (item: MainSidebarItem, query: string) => boolean;
116
+ /** Shown when nothing matches. Default: "No menu items match {query}". */
117
+ noResultsLabel?: ReactNode;
118
+ }
119
+
120
+ export interface MainSidebarProps
121
+ extends Omit<HTMLAttributes<HTMLElement>, 'onSelect'> {
122
+ items: MainSidebarItem[];
123
+
124
+ /** Interaction model. Defaults to `drilldown` (backward-compatible). */
125
+ variant?: MainSidebarVariant;
126
+
127
+ /* Selection + controlled expansion */
128
+ activeKey?: string;
129
+ expanded?: boolean;
130
+ defaultExpanded?: boolean;
131
+ onExpandedChange?: (expanded: boolean) => void;
132
+ onItemSelect?: (key: string, item: MainSidebarItem) => void;
133
+
134
+ /* Slots */
135
+ header?: ReactNode;
136
+ footer?: ReactNode;
137
+ panelHeader?: ReactNode;
138
+ panelFooter?: ReactNode;
139
+
140
+ /* Search */
141
+ search?: boolean | MainSidebarSearchConfig;
142
+ searchPlaceholder?: string;
143
+ onSearchChange?: (query: string) => void;
144
+
145
+ /* Visual */
146
+ side?: MainSidebarSide;
147
+ density?: MainSidebarDensity;
148
+ panelWidth?: number | string;
149
+ motionPreset?: MainSidebarMotionPreset;
150
+ showLabelsWhenExpanded?: boolean;
151
+
152
+ /* Collapse / expand — shadcn-style icon ↔ full toggle */
153
+ collapsible?: MainSidebarCollapsible;
154
+ collapsed?: boolean;
155
+ defaultCollapsed?: boolean;
156
+ onCollapsedChange?: (collapsed: boolean) => void;
157
+ collapsedWidth?: number | string;
158
+ expandedWidth?: number | string;
159
+ showCollapseToggle?: boolean;
160
+
161
+ /* Variant-specific */
162
+ /** Dismiss the panel when a pointer-down lands outside the rail + panel. Default true (all overlay variants). */
163
+ closeOnOutsideClick?: boolean;
164
+ /** Columns: how many columns can be visible at once before horizontal scroll. */
165
+ columnsMaxVisible?: number;
166
+ /** Hover: open delay in milliseconds. Default 120. */
167
+ hoverDelayMs?: number;
168
+
169
+ /* className overrides */
170
+ railClassName?: string;
171
+ panelClassName?: string;
172
+ itemClassName?: string;
173
+ activeItemClassName?: string;
174
+
175
+ /**
176
+ * Whitelabel colour scheme. Any subset of these tokens overrides the
177
+ * sidebar's surface, accent, border and tooltip colours per-instance —
178
+ * the values are emitted as CSS variables on the sidebar root so they
179
+ * cascade through every nested affordance (rail, expanded surface,
180
+ * tooltips, badges, breadcrumbs).
181
+ *
182
+ * <MainSidebar
183
+ * colorScheme={{
184
+ * bg: '#0a2540',
185
+ * fg: '#ffffff',
186
+ * accentBg: '#ff5a1f',
187
+ * accentFg: '#ffffff',
188
+ * border: 'rgb(255 255 255 / 0.12)',
189
+ * }}
190
+ * />
191
+ *
192
+ * Any token may be omitted; omitted tokens fall back to the design
193
+ * system's brand defaults (primary / accent / border).
194
+ */
195
+ colorScheme?: MainSidebarColorScheme;
196
+
197
+ /* Labels for a11y */
198
+ collapsedLabel?: string;
199
+ expandedLabel?: string;
200
+ backLabel?: string;
201
+
202
+ /** Force the mobile bottom-sheet layout for previews/tests. */
203
+ mobile?: boolean;
204
+ }
205
+
206
+ /** Backward-compat aliases — IconSidebar was the original name. */
207
+ export type IconSidebarItem = MainSidebarItem;
208
+ export type IconSidebarProps = MainSidebarProps;
@@ -1,10 +1,21 @@
1
1
  'use client';
2
2
 
3
+ /**
4
+ * Public barrel for `@mihcm/ui/MainSidebar`. The implementation is split
5
+ * across `./MainSidebar/*` so each variant stays under the 400-line rule
6
+ * (CLAUDE.md §6).
7
+ */
8
+
3
9
  export {
4
- MainSidebar,
5
10
  IconSidebar,
6
- type MainSidebarItem,
7
- type MainSidebarProps,
11
+ MainSidebar,
8
12
  type IconSidebarItem,
9
13
  type IconSidebarProps,
10
- } from './IconSidebar.js';
14
+ type MainSidebarDensity,
15
+ type MainSidebarItem,
16
+ type MainSidebarMotionPreset,
17
+ type MainSidebarProps,
18
+ type MainSidebarSearchConfig,
19
+ type MainSidebarSide,
20
+ type MainSidebarVariant,
21
+ } from './MainSidebar/index.js';
@@ -99,7 +99,7 @@ export const NavigationMenuTrigger = forwardRef<
99
99
  stroke="currentColor"
100
100
  strokeWidth={2}
101
101
  className="relative top-px ml-1 h-3 w-3 transition-transform duration-200 group-data-[state=open]:rotate-180"
102
- aria-hidden
102
+ aria-hidden="true"
103
103
  >
104
104
  <path strokeLinecap="round" strokeLinejoin="round" d="M4 6l4 4 4-4" />
105
105
  </svg>