@mihcm/ui 0.14.1 → 0.15.1

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 +150 -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 +166 -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
package/src/DataTable.tsx CHANGED
@@ -15,7 +15,6 @@ import {
15
15
  Fragment,
16
16
  useState,
17
17
  useMemo,
18
- useCallback,
19
18
  useEffect,
20
19
  useRef,
21
20
  type ReactNode,
@@ -64,268 +63,21 @@ import { Button } from './Button.js';
64
63
  import { Checkbox } from './Checkbox.js';
65
64
  import { Select, SelectTrigger, SelectContent, SelectItem } from './Select.js';
66
65
  import { cn } from './internal/cn.js';
66
+ import {
67
+ ColumnFilterInput,
68
+ ColumnVisibilityDropdown,
69
+ DataTableColumnHeader,
70
+ getCellPinningStyles,
71
+ getPinningStyles,
72
+ } from './DataTable/index.js';
73
+
74
+ export { DataTableColumnHeader };
67
75
 
68
76
  /* ── Re-export for consumer convenience ───────────────────────────── */
69
77
 
70
78
  export { createColumnHelper };
71
79
  export type { ColumnDef, Row, RowSelectionState, TableVariant };
72
80
 
73
- /* ── DataTableColumnHeader ────────────────────────────────────────── */
74
-
75
- interface DataTableColumnHeaderProps<TData, TValue> {
76
- column: Column<TData, TValue>;
77
- title: string;
78
- className?: string;
79
- }
80
-
81
- export function DataTableColumnHeader<TData, TValue>({
82
- column,
83
- title,
84
- className,
85
- }: DataTableColumnHeaderProps<TData, TValue>) {
86
- if (!column.getCanSort()) {
87
- return <span className={className}>{title}</span>;
88
- }
89
-
90
- const sorted = column.getIsSorted();
91
-
92
- return (
93
- <button
94
- type="button"
95
- className={cn(
96
- 'inline-flex items-center gap-1 -ml-1 px-1 py-0.5 rounded-md',
97
- 'hover:bg-muted transition-colors duration-150',
98
- 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
99
- className,
100
- )}
101
- onClick={column.getToggleSortingHandler()}
102
- aria-label={`Sort by ${title}`}
103
- >
104
- {title}
105
- <span className="inline-flex flex-col" aria-hidden>
106
- <svg
107
- viewBox="0 0 8 4"
108
- className={cn(
109
- 'h-1.5 w-2 transition-transform duration-200',
110
- sorted === 'asc' ? 'text-foreground' : 'text-muted-foreground/40',
111
- )}
112
- fill="currentColor"
113
- >
114
- <path d="M4 0L8 4H0z" />
115
- </svg>
116
- <svg
117
- viewBox="0 0 8 4"
118
- className={cn(
119
- 'h-1.5 w-2 transition-transform duration-200',
120
- sorted === 'desc' ? 'text-foreground' : 'text-muted-foreground/40',
121
- )}
122
- fill="currentColor"
123
- >
124
- <path d="M4 4L0 0h8z" />
125
- </svg>
126
- </span>
127
- </button>
128
- );
129
- }
130
-
131
- /* ── Column visibility dropdown ───────────────────────────────────── */
132
-
133
- function ColumnVisibilityDropdown<TData>({
134
- table,
135
- }: {
136
- table: TanStackTable<TData>;
137
- }) {
138
- const [open, setOpen] = useState(false);
139
- const ref = useRef<HTMLDivElement>(null);
140
-
141
- useEffect(() => {
142
- if (!open) return;
143
- function handleClick(e: MouseEvent) {
144
- if (ref.current && !ref.current.contains(e.target as Node)) {
145
- setOpen(false);
146
- }
147
- }
148
- document.addEventListener('mousedown', handleClick);
149
- return () => document.removeEventListener('mousedown', handleClick);
150
- }, [open]);
151
-
152
- return (
153
- <div ref={ref} className="relative">
154
- <Button
155
- variant="outline"
156
- size="sm"
157
- onClick={() => setOpen(!open)}
158
- aria-expanded={open}
159
- aria-haspopup="true"
160
- >
161
- <svg
162
- aria-hidden
163
- viewBox="0 0 24 24"
164
- fill="none"
165
- stroke="currentColor"
166
- strokeWidth={2}
167
- className="h-4 w-4"
168
- >
169
- <path
170
- strokeLinecap="round"
171
- strokeLinejoin="round"
172
- d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75"
173
- />
174
- </svg>
175
- Columns
176
- </Button>
177
- {open && (
178
- <div className="absolute right-0 top-full z-50 mt-1 min-w-[10rem] rounded-lg border border-border bg-card p-1 shadow-md">
179
- {table.getAllLeafColumns().map((column) => {
180
- if (column.id === 'select' || column.id === 'expand') return null;
181
- return (
182
- <label
183
- key={column.id}
184
- className="flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted transition-colors duration-150"
185
- >
186
- <Checkbox
187
- size="sm"
188
- checked={column.getIsVisible()}
189
- onCheckedChange={(v) => column.toggleVisibility(!!v)}
190
- />
191
- <span className="capitalize">
192
- {typeof column.columnDef.header === 'string'
193
- ? column.columnDef.header
194
- : column.id}
195
- </span>
196
- </label>
197
- );
198
- })}
199
- </div>
200
- )}
201
- </div>
202
- );
203
- }
204
-
205
- /* ── Column filter input ──────────────────────────────────────────── */
206
-
207
- function ColumnFilterInput<TData>({
208
- column,
209
- faceted,
210
- }: {
211
- column: Column<TData, unknown>;
212
- faceted: boolean;
213
- }) {
214
- const columnFilterValue = column.getFilterValue();
215
- const [open, setOpen] = useState(false);
216
- const ref = useRef<HTMLDivElement>(null);
217
-
218
- /* Close faceted dropdown on outside click */
219
- useEffect(() => {
220
- if (!open) return;
221
- function handleClick(e: MouseEvent) {
222
- if (ref.current && !ref.current.contains(e.target as Node)) {
223
- setOpen(false);
224
- }
225
- }
226
- document.addEventListener('mousedown', handleClick);
227
- return () => document.removeEventListener('mousedown', handleClick);
228
- }, [open]);
229
-
230
- /* Faceted: number column → min/max range */
231
- if (faceted) {
232
- const facetedMinMax = column.getFacetedMinMaxValues?.();
233
- const isNumberColumn =
234
- facetedMinMax !== undefined &&
235
- facetedMinMax[0] !== undefined &&
236
- typeof facetedMinMax[0] === 'number';
237
-
238
- if (isNumberColumn) {
239
- const [min, max] = facetedMinMax as [number, number];
240
- const currentRange = (columnFilterValue as [number, number] | undefined) ?? [
241
- undefined,
242
- undefined,
243
- ];
244
- return (
245
- <div className="grid min-w-[10rem] grid-cols-2 gap-1">
246
- <Input
247
- type="number"
248
- value={currentRange[0] ?? ''}
249
- onChange={(e) => {
250
- const val = e.target.value ? Number(e.target.value) : undefined;
251
- column.setFilterValue((old: [number, number] | undefined) => [
252
- val,
253
- old?.[1],
254
- ]);
255
- }}
256
- placeholder={`Min (${min})`}
257
- className="h-8 min-w-0 bg-background px-2 text-xs shadow-mi-input"
258
- />
259
- <Input
260
- type="number"
261
- value={currentRange[1] ?? ''}
262
- onChange={(e) => {
263
- const val = e.target.value ? Number(e.target.value) : undefined;
264
- column.setFilterValue((old: [number, number] | undefined) => [
265
- old?.[0],
266
- val,
267
- ]);
268
- }}
269
- placeholder={`Max (${max})`}
270
- className="h-8 min-w-0 bg-background px-2 text-xs shadow-mi-input"
271
- />
272
- </div>
273
- );
274
- }
275
-
276
- /* Faceted: string column → unique values list */
277
- const uniqueValues = column.getFacetedUniqueValues?.();
278
- if (uniqueValues && uniqueValues.size > 0) {
279
- const sortedValues = Array.from(uniqueValues.keys())
280
- .filter((v): v is string => typeof v === 'string')
281
- .sort();
282
- return (
283
- <div ref={ref} className="relative">
284
- <Input
285
- type="text"
286
- value={(columnFilterValue as string) ?? ''}
287
- onChange={(e) => column.setFilterValue(e.target.value || undefined)}
288
- onFocus={() => setOpen(true)}
289
- placeholder="Filter..."
290
- className="h-8 min-w-[8rem] bg-background px-2 text-xs shadow-mi-input"
291
- />
292
- {open && sortedValues.length > 0 && (
293
- <div className="absolute left-0 top-full z-50 mt-1 max-h-40 min-w-full overflow-y-auto rounded-md border border-border bg-card p-1 shadow-mi-modal">
294
- {sortedValues.map((val) => (
295
- <button
296
- key={val}
297
- type="button"
298
- className="block w-full rounded px-2 py-1 text-left text-xs hover:bg-muted transition-colors duration-150"
299
- onClick={() => {
300
- column.setFilterValue(val);
301
- setOpen(false);
302
- }}
303
- >
304
- {val}{' '}
305
- <span className="text-muted-foreground">
306
- ({uniqueValues.get(val)})
307
- </span>
308
- </button>
309
- ))}
310
- </div>
311
- )}
312
- </div>
313
- );
314
- }
315
- }
316
-
317
- /* Default: simple text input */
318
- return (
319
- <Input
320
- type="text"
321
- value={(columnFilterValue as string) ?? ''}
322
- onChange={(e) => column.setFilterValue(e.target.value || undefined)}
323
- placeholder="Filter..."
324
- className="h-8 min-w-[8rem] bg-background px-2 text-xs shadow-mi-input"
325
- />
326
- );
327
- }
328
-
329
81
  /* ── DataTable ────────────────────────────────────────────────────── */
330
82
 
331
83
  export interface DataTableProps<TData, TValue> {
@@ -608,41 +360,6 @@ export function DataTable<TData, TValue>({
608
360
  ? virtualRows.map((virtualRow) => regularRows[virtualRow.index]).filter((row): row is Row<TData> => Boolean(row))
609
361
  : regularRows;
610
362
 
611
- /* ── Pinning style helper ──────────────────────────────────────── */
612
- const getPinningStyles = useCallback(
613
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
614
- (header: Header<any, unknown>): React.CSSProperties => {
615
- if (!columnPinnable) return {};
616
- const isPinned = header.column.getIsPinned();
617
- if (!isPinned) return {};
618
- return {
619
- position: 'sticky',
620
- left: isPinned === 'left' ? `${header.getStart('left')}px` : undefined,
621
- right: isPinned === 'right' ? `${header.getStart('right')}px` : undefined,
622
- zIndex: 1,
623
- backgroundColor: 'var(--color-card, #fff)',
624
- };
625
- },
626
- [columnPinnable],
627
- );
628
-
629
- const getCellPinningStyles = useCallback(
630
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
631
- (cell: { column: Column<any, unknown> }): React.CSSProperties => {
632
- if (!columnPinnable) return {};
633
- const isPinned = cell.column.getIsPinned();
634
- if (!isPinned) return {};
635
- return {
636
- position: 'sticky',
637
- left: isPinned === 'left' ? `${cell.column.getStart('left')}px` : undefined,
638
- right: isPinned === 'right' ? `${cell.column.getStart('right')}px` : undefined,
639
- zIndex: 1,
640
- backgroundColor: 'var(--color-card, #fff)',
641
- };
642
- },
643
- [columnPinnable],
644
- );
645
-
646
363
  /* ── Render ─────────────────────────────────────────────────────── */
647
364
  return (
648
365
  <div className={cn('w-full space-y-4', className)}>
@@ -705,7 +422,7 @@ export function DataTable<TData, TValue>({
705
422
  colSpan={header.colSpan}
706
423
  style={{
707
424
  width: resizable ? header.getSize() : (header.getSize() !== 150 ? header.getSize() : undefined),
708
- ...getPinningStyles(header),
425
+ ...getPinningStyles(header, columnPinnable),
709
426
  }}
710
427
  className={cn(
711
428
  'h-12 whitespace-nowrap px-3 py-2 align-middle',
@@ -786,7 +503,7 @@ export function DataTable<TData, TValue>({
786
503
  colSpan={header.colSpan}
787
504
  style={{
788
505
  width: resizable ? header.getSize() : (header.getSize() !== 150 ? header.getSize() : undefined),
789
- ...getPinningStyles(header),
506
+ ...getPinningStyles(header, columnPinnable),
790
507
  }}
791
508
  className={cn(
792
509
  'h-12 border-t border-border px-3 py-2 align-middle',
@@ -816,7 +533,7 @@ export function DataTable<TData, TValue>({
816
533
  className="bg-muted/30"
817
534
  >
818
535
  {row.getVisibleCells().map((cell) => (
819
- <TableCell key={cell.id} style={getCellPinningStyles(cell)}>
536
+ <TableCell key={cell.id} style={getCellPinningStyles(cell, columnPinnable)}>
820
537
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
821
538
  </TableCell>
822
539
  ))}
@@ -838,7 +555,7 @@ export function DataTable<TData, TValue>({
838
555
  data-state={row.getIsSelected() ? 'selected' : undefined}
839
556
  >
840
557
  {row.getVisibleCells().map((cell) => (
841
- <TableCell key={cell.id} style={getCellPinningStyles(cell)}>
558
+ <TableCell key={cell.id} style={getCellPinningStyles(cell, columnPinnable)}>
842
559
  {/* Grouped cell rendering */}
843
560
  {groupable && cell.getIsGrouped() ? (
844
561
  <button
@@ -913,7 +630,7 @@ export function DataTable<TData, TValue>({
913
630
  className="bg-muted/30"
914
631
  >
915
632
  {row.getVisibleCells().map((cell) => (
916
- <TableCell key={cell.id} style={getCellPinningStyles(cell)}>
633
+ <TableCell key={cell.id} style={getCellPinningStyles(cell, columnPinnable)}>
917
634
  {flexRender(cell.column.columnDef.cell, cell.getContext())}
918
635
  </TableCell>
919
636
  ))}
@@ -38,9 +38,11 @@ export interface DialogProps {
38
38
  open: boolean;
39
39
  onOpenChange: (open: boolean) => void;
40
40
  children: ReactNode;
41
+ /** Tailwind classes applied to the backdrop. Mirrors the web Dialog root. */
42
+ className?: string;
41
43
  }
42
44
 
43
- export function Dialog({ open, onOpenChange, children }: DialogProps) {
45
+ export function Dialog({ open, onOpenChange, children, className }: DialogProps) {
44
46
  return (
45
47
  <Modal
46
48
  visible={open}
@@ -51,7 +53,7 @@ export function Dialog({ open, onOpenChange, children }: DialogProps) {
51
53
  >
52
54
  <Pressable
53
55
  onPress={() => onOpenChange(false)}
54
- className="flex-1 items-center justify-center bg-black/50"
56
+ className={cn('flex-1 items-center justify-center bg-black/50', className)}
55
57
  accessibilityRole="none"
56
58
  >
57
59
  <Pressable onPress={(e) => e.stopPropagation()}>
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Form building blocks — layout and accessibility scaffolding you compose
5
+ * freely around any control: FormItem, FormLabel, FormDescription, FormMessage.
6
+ */
7
+ import { type HTMLAttributes } from 'react';
8
+ import { cn } from '../internal/cn.js';
9
+ import { Label } from '../Label.js';
10
+
11
+ /* ── FormItem ─────────────────────────────────────────────────────── */
12
+
13
+ export interface FormItemProps extends HTMLAttributes<HTMLDivElement> {}
14
+
15
+ /** Vertical stack container for label + control + description + error. */
16
+ export function FormItem({ className, children, ...props }: FormItemProps) {
17
+ return (
18
+ <div className={cn('flex flex-col gap-1.5', className)} {...props}>
19
+ {children}
20
+ </div>
21
+ );
22
+ }
23
+
24
+ /* ── FormLabel ────────────────────────────────────────────────────── */
25
+
26
+ export interface FormLabelProps extends HTMLAttributes<HTMLLabelElement> {
27
+ htmlFor?: string;
28
+ required?: boolean;
29
+ }
30
+
31
+ /** Styled label with optional required asterisk. Delegates to the existing Label primitive. */
32
+ export function FormLabel({ htmlFor, required, className, children, ...props }: FormLabelProps) {
33
+ return (
34
+ <Label
35
+ {...(htmlFor !== undefined && { htmlFor })}
36
+ {...(required !== undefined && { required })}
37
+ {...(className !== undefined && { className })}
38
+ {...props}
39
+ >
40
+ {children}
41
+ </Label>
42
+ );
43
+ }
44
+
45
+ /* ── FormDescription ──────────────────────────────────────────────── */
46
+
47
+ export interface FormDescriptionProps extends HTMLAttributes<HTMLParagraphElement> {}
48
+
49
+ /** Muted helper text rendered below the control. */
50
+ export function FormDescription({ className, children, ...props }: FormDescriptionProps) {
51
+ return (
52
+ <p className={cn('text-xs text-muted-foreground', className)} {...props}>
53
+ {children}
54
+ </p>
55
+ );
56
+ }
57
+
58
+ /* ── FormMessage ──────────────────────────────────────────────────── */
59
+
60
+ export interface FormMessageProps extends HTMLAttributes<HTMLParagraphElement> {
61
+ /** Array of error strings — only the first is displayed. */
62
+ errors?: string[];
63
+ }
64
+
65
+ /** Error message display with warning icon. Transitions in/out smoothly. */
66
+ export function FormMessage({ errors, className, ...props }: FormMessageProps) {
67
+ const firstError = errors?.filter(Boolean)[0];
68
+ if (!firstError) return null;
69
+
70
+ return (
71
+ <p
72
+ role="alert"
73
+ className={cn(
74
+ 'flex items-center gap-1 text-xs text-destructive',
75
+ 'transition-all duration-150',
76
+ className,
77
+ )}
78
+ {...props}
79
+ >
80
+ <svg
81
+ aria-hidden="true"
82
+ viewBox="0 0 16 16"
83
+ fill="none"
84
+ stroke="currentColor"
85
+ strokeWidth={1.5}
86
+ strokeLinecap="round"
87
+ strokeLinejoin="round"
88
+ className="h-3.5 w-3.5 shrink-0"
89
+ >
90
+ <path d="M8 2L1.5 13h13L8 2z" />
91
+ <path d="M8 6v3" />
92
+ <circle cx="8" cy="11" r="0.5" fill="currentColor" stroke="none" />
93
+ </svg>
94
+ {firstError}
95
+ </p>
96
+ );
97
+ }