@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
@@ -0,0 +1,218 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Text-style fields integrated with TanStack Form: FormInput, FormSearchField,
5
+ * FormTextarea. Each wires `form.Field` to the matching UI primitive with
6
+ * label, description, and error display included.
7
+ */
8
+ import { useId } from 'react';
9
+ import { Input } from '../Input.js';
10
+ import { SearchField, type SearchFieldProps } from '../SearchField.js';
11
+ import { Textarea } from '../Textarea.js';
12
+ import {
13
+ FormDescription,
14
+ FormItem,
15
+ FormLabel,
16
+ FormMessage,
17
+ } from './building-blocks.js';
18
+ import type { AnyFormApi, FieldRenderProps, FormFieldValidators } from './types.js';
19
+
20
+ /* ── FormInput ─────────────────────────────────────────────────────── */
21
+
22
+ export interface FormInputProps {
23
+ form: AnyFormApi;
24
+ name: string;
25
+ label: string;
26
+ description?: string;
27
+ required?: boolean;
28
+ placeholder?: string;
29
+ type?: string;
30
+ disabled?: boolean;
31
+ className?: string;
32
+ validators?: FormFieldValidators;
33
+ }
34
+
35
+ /** Input field integrated with TanStack Form. */
36
+ export function FormInput({
37
+ form,
38
+ name,
39
+ label,
40
+ description,
41
+ required,
42
+ placeholder,
43
+ type,
44
+ disabled,
45
+ className,
46
+ validators,
47
+ }: FormInputProps) {
48
+ const id = useId();
49
+ const descId = `${id}-desc`;
50
+ const msgId = `${id}-msg`;
51
+
52
+ return (
53
+ <form.Field name={name} {...(validators !== undefined && { validators })}>
54
+ {(field: FieldRenderProps<string>) => {
55
+ const errors = field.state.meta.isTouched ? field.state.meta.errors : [];
56
+ const hasError = errors.length > 0;
57
+ return (
58
+ <FormItem {...(className !== undefined && { className })}>
59
+ <FormLabel htmlFor={id} {...(required !== undefined && { required })}>
60
+ {label}
61
+ </FormLabel>
62
+ <Input
63
+ id={id}
64
+ {...(type !== undefined && { type })}
65
+ {...(placeholder !== undefined && { placeholder })}
66
+ {...(disabled !== undefined && { disabled })}
67
+ {...(required !== undefined && { required })}
68
+ invalid={hasError}
69
+ value={field.state.value ?? ''}
70
+ onChange={(e) => field.handleChange(e.target.value)}
71
+ onBlur={field.handleBlur}
72
+ aria-describedby={hasError ? msgId : description !== undefined ? descId : undefined}
73
+ />
74
+ {description !== undefined ? (
75
+ <FormDescription id={descId}>{description}</FormDescription>
76
+ ) : null}
77
+ {hasError ? <FormMessage id={msgId} errors={errors} /> : null}
78
+ </FormItem>
79
+ );
80
+ }}
81
+ </form.Field>
82
+ );
83
+ }
84
+
85
+ /* ── FormSearchField ─────────────────────────────────────────────── */
86
+
87
+ export interface FormSearchFieldProps {
88
+ form: AnyFormApi;
89
+ name: string;
90
+ label: string;
91
+ description?: string;
92
+ required?: boolean;
93
+ placeholder?: string;
94
+ disabled?: boolean;
95
+ size?: SearchFieldProps['size'];
96
+ noClear?: boolean;
97
+ className?: string;
98
+ inputClassName?: string;
99
+ validators?: FormFieldValidators;
100
+ }
101
+
102
+ /** Search field integrated with TanStack Form. */
103
+ export function FormSearchField({
104
+ form,
105
+ name,
106
+ label,
107
+ description,
108
+ required,
109
+ placeholder,
110
+ disabled,
111
+ size,
112
+ noClear,
113
+ className,
114
+ inputClassName,
115
+ validators,
116
+ }: FormSearchFieldProps) {
117
+ const id = useId();
118
+ const descId = `${id}-desc`;
119
+ const msgId = `${id}-msg`;
120
+
121
+ return (
122
+ <form.Field name={name} {...(validators !== undefined && { validators })}>
123
+ {(field: FieldRenderProps<string>) => {
124
+ const errors = field.state.meta.isTouched ? field.state.meta.errors : [];
125
+ const hasError = errors.length > 0;
126
+ return (
127
+ <FormItem {...(className !== undefined && { className })}>
128
+ <FormLabel htmlFor={id} {...(required !== undefined && { required })}>
129
+ {label}
130
+ </FormLabel>
131
+ <SearchField
132
+ id={id}
133
+ value={field.state.value ?? ''}
134
+ onValueChange={field.handleChange}
135
+ onBlur={field.handleBlur}
136
+ {...(placeholder !== undefined && { placeholder })}
137
+ {...(disabled !== undefined && { disabled })}
138
+ {...(size !== undefined && { size })}
139
+ {...(noClear !== undefined && { noClear })}
140
+ {...(inputClassName !== undefined && { inputClassName })}
141
+ aria-describedby={hasError ? msgId : description !== undefined ? descId : undefined}
142
+ aria-invalid={hasError}
143
+ />
144
+ {description !== undefined ? (
145
+ <FormDescription id={descId}>{description}</FormDescription>
146
+ ) : null}
147
+ {hasError ? <FormMessage id={msgId} errors={errors} /> : null}
148
+ </FormItem>
149
+ );
150
+ }}
151
+ </form.Field>
152
+ );
153
+ }
154
+
155
+ /* ── FormTextarea ────────────────────────────────────────────────── */
156
+
157
+ export interface FormTextareaProps {
158
+ form: AnyFormApi;
159
+ name: string;
160
+ label: string;
161
+ description?: string;
162
+ required?: boolean;
163
+ placeholder?: string;
164
+ rows?: number;
165
+ disabled?: boolean;
166
+ className?: string;
167
+ validators?: FormFieldValidators;
168
+ }
169
+
170
+ /** Textarea field integrated with TanStack Form. */
171
+ export function FormTextarea({
172
+ form,
173
+ name,
174
+ label,
175
+ description,
176
+ required,
177
+ placeholder,
178
+ rows,
179
+ disabled,
180
+ className,
181
+ validators,
182
+ }: FormTextareaProps) {
183
+ const id = useId();
184
+ const descId = `${id}-desc`;
185
+ const msgId = `${id}-msg`;
186
+
187
+ return (
188
+ <form.Field name={name} {...(validators !== undefined && { validators })}>
189
+ {(field: FieldRenderProps<string>) => {
190
+ const errors = field.state.meta.isTouched ? field.state.meta.errors : [];
191
+ const hasError = errors.length > 0;
192
+ return (
193
+ <FormItem {...(className !== undefined && { className })}>
194
+ <FormLabel htmlFor={id} {...(required !== undefined && { required })}>
195
+ {label}
196
+ </FormLabel>
197
+ <Textarea
198
+ id={id}
199
+ {...(placeholder !== undefined && { placeholder })}
200
+ {...(disabled !== undefined && { disabled })}
201
+ {...(required !== undefined && { required })}
202
+ {...(rows !== undefined && { rows })}
203
+ invalid={hasError}
204
+ value={field.state.value ?? ''}
205
+ onChange={(e) => field.handleChange(e.target.value)}
206
+ onBlur={field.handleBlur}
207
+ aria-describedby={hasError ? msgId : description !== undefined ? descId : undefined}
208
+ />
209
+ {description !== undefined ? (
210
+ <FormDescription id={descId}>{description}</FormDescription>
211
+ ) : null}
212
+ {hasError ? <FormMessage id={msgId} errors={errors} /> : null}
213
+ </FormItem>
214
+ );
215
+ }}
216
+ </form.Field>
217
+ );
218
+ }
@@ -0,0 +1,123 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Boolean / toggle fields integrated with TanStack Form: FormCheckbox, FormSwitch.
5
+ */
6
+ import { useId } from 'react';
7
+ import { Checkbox } from '../Checkbox.js';
8
+ import { Switch } from '../Switch.js';
9
+ import {
10
+ FormDescription,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from './building-blocks.js';
15
+ import type { AnyFormApi, FieldRenderProps, FormFieldValidators } from './types.js';
16
+
17
+ /* ── FormCheckbox ────────────────────────────────────────────────── */
18
+
19
+ export interface FormCheckboxProps {
20
+ form: AnyFormApi;
21
+ name: string;
22
+ label: string;
23
+ description?: string;
24
+ disabled?: boolean;
25
+ className?: string;
26
+ validators?: FormFieldValidators;
27
+ }
28
+
29
+ /** Checkbox field integrated with TanStack Form. */
30
+ export function FormCheckbox({
31
+ form,
32
+ name,
33
+ label,
34
+ description,
35
+ disabled,
36
+ className,
37
+ validators,
38
+ }: FormCheckboxProps) {
39
+ const id = useId();
40
+ const descId = `${id}-desc`;
41
+ const msgId = `${id}-msg`;
42
+
43
+ return (
44
+ <form.Field name={name} {...(validators !== undefined && { validators })}>
45
+ {(field: FieldRenderProps<boolean>) => {
46
+ const errors = field.state.meta.isTouched ? field.state.meta.errors : [];
47
+ const hasError = errors.length > 0;
48
+ return (
49
+ <FormItem {...(className !== undefined && { className })}>
50
+ <div className="flex items-center gap-2">
51
+ <Checkbox
52
+ id={id}
53
+ checked={!!field.state.value}
54
+ onCheckedChange={(checked) => field.handleChange(checked === true)}
55
+ {...(disabled !== undefined && { disabled })}
56
+ aria-describedby={hasError ? msgId : description !== undefined ? descId : undefined}
57
+ />
58
+ <FormLabel htmlFor={id}>{label}</FormLabel>
59
+ </div>
60
+ {description !== undefined ? (
61
+ <FormDescription id={descId}>{description}</FormDescription>
62
+ ) : null}
63
+ {hasError ? <FormMessage id={msgId} errors={errors} /> : null}
64
+ </FormItem>
65
+ );
66
+ }}
67
+ </form.Field>
68
+ );
69
+ }
70
+
71
+ /* ── FormSwitch ──────────────────────────────────────────────────── */
72
+
73
+ export interface FormSwitchProps {
74
+ form: AnyFormApi;
75
+ name: string;
76
+ label: string;
77
+ description?: string;
78
+ disabled?: boolean;
79
+ className?: string;
80
+ validators?: FormFieldValidators;
81
+ }
82
+
83
+ /** Switch field integrated with TanStack Form. */
84
+ export function FormSwitch({
85
+ form,
86
+ name,
87
+ label,
88
+ description,
89
+ disabled,
90
+ className,
91
+ validators,
92
+ }: FormSwitchProps) {
93
+ const id = useId();
94
+ const descId = `${id}-desc`;
95
+ const msgId = `${id}-msg`;
96
+
97
+ return (
98
+ <form.Field name={name} {...(validators !== undefined && { validators })}>
99
+ {(field: FieldRenderProps<boolean>) => {
100
+ const errors = field.state.meta.isTouched ? field.state.meta.errors : [];
101
+ const hasError = errors.length > 0;
102
+ return (
103
+ <FormItem {...(className !== undefined && { className })}>
104
+ <div className="flex items-center gap-3">
105
+ <Switch
106
+ id={id}
107
+ checked={!!field.state.value}
108
+ onCheckedChange={(checked) => field.handleChange(checked)}
109
+ {...(disabled !== undefined && { disabled })}
110
+ aria-describedby={hasError ? msgId : description !== undefined ? descId : undefined}
111
+ />
112
+ <FormLabel htmlFor={id}>{label}</FormLabel>
113
+ </div>
114
+ {description !== undefined ? (
115
+ <FormDescription id={descId}>{description}</FormDescription>
116
+ ) : null}
117
+ {hasError ? <FormMessage id={msgId} errors={errors} /> : null}
118
+ </FormItem>
119
+ );
120
+ }}
121
+ </form.Field>
122
+ );
123
+ }
@@ -0,0 +1,189 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Form composition helpers: FormFieldArray, FormSubscribe, FormListenEffect,
5
+ * FormActions. These wire common TanStack Form patterns to MiHCM primitives.
6
+ */
7
+ import { useId, type ReactNode } from 'react';
8
+ import { cn } from '../internal/cn.js';
9
+ import { Button } from '../Button.js';
10
+ import {
11
+ FormDescription,
12
+ FormItem,
13
+ FormLabel,
14
+ } from './building-blocks.js';
15
+ import type { AnyFormApi, FormFieldValidators } from './types.js';
16
+
17
+ /* ── FormFieldArray ──────────────────────────────────────────────── */
18
+
19
+ /** Render props supplied to each array item's `renderField` callback. */
20
+ export interface FieldArrayItemApi {
21
+ /** Index of this item within the array. */
22
+ index: number;
23
+ /** Total number of items currently in the array. */
24
+ total: number;
25
+ /** Remove this item from the array. */
26
+ remove: () => void;
27
+ }
28
+
29
+ export interface FormFieldArrayProps {
30
+ form: AnyFormApi;
31
+ name: string;
32
+ label: string;
33
+ description?: string;
34
+ /** Render function called for each array item. */
35
+ renderField: (item: FieldArrayItemApi) => ReactNode;
36
+ /** Label text for the add button. Default: "Add item". */
37
+ addLabel?: string;
38
+ className?: string;
39
+ validators?: FormFieldValidators;
40
+ }
41
+
42
+ /** Field array integrated with TanStack Form. Provides add/remove buttons. */
43
+ export function FormFieldArray({
44
+ form,
45
+ name,
46
+ label,
47
+ description,
48
+ renderField,
49
+ addLabel = 'Add item',
50
+ className,
51
+ validators,
52
+ }: FormFieldArrayProps) {
53
+ const id = useId();
54
+ const descId = `${id}-desc`;
55
+
56
+ return (
57
+ <form.Field name={name} mode="array" {...(validators !== undefined && { validators })}>
58
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
59
+ {(field: any) => {
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ const items = (field.state.value ?? []) as any[];
62
+ return (
63
+ <FormItem {...(className !== undefined && { className })}>
64
+ <div className="flex items-center justify-between">
65
+ <FormLabel>{label}</FormLabel>
66
+ <Button type="button" variant="outline" size="sm" onClick={() => field.pushValue({})}>
67
+ + {addLabel}
68
+ </Button>
69
+ </div>
70
+ {description !== undefined ? (
71
+ <FormDescription id={descId}>{description}</FormDescription>
72
+ ) : null}
73
+ <div className="space-y-3">
74
+ {items.map((_: unknown, i: number) => (
75
+ <div
76
+ key={i}
77
+ className="rounded-lg border border-border p-3 transition-all duration-150"
78
+ >
79
+ {renderField({
80
+ index: i,
81
+ total: items.length,
82
+ remove: () => field.removeValue(i),
83
+ })}
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </FormItem>
88
+ );
89
+ }}
90
+ </form.Field>
91
+ );
92
+ }
93
+
94
+ /* ── FormSubscribe ───────────────────────────────────────────────── */
95
+
96
+ export interface FormSubscribeProps<TSelected> {
97
+ form: AnyFormApi;
98
+ /** Selector function that picks values from form state. */
99
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
100
+ selector: (state: any) => TSelected;
101
+ /** Render function receiving the selected state. */
102
+ children: (selected: TSelected) => ReactNode;
103
+ }
104
+
105
+ /** Convenience wrapper around `form.Subscribe` for reactive state display. */
106
+ export function FormSubscribe<TSelected>({
107
+ form,
108
+ selector,
109
+ children,
110
+ }: FormSubscribeProps<TSelected>) {
111
+ return <form.Subscribe selector={selector}>{children}</form.Subscribe>;
112
+ }
113
+
114
+ /* ── FormListenEffect ────────────────────────────────────────────── */
115
+
116
+ export interface FormListenEffectProps {
117
+ form: AnyFormApi;
118
+ /** Field name to listen to. */
119
+ name: string;
120
+ /** Listener callbacks (onChange, onBlur, etc.) for side effects. */
121
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
122
+ listeners: Record<string, any>;
123
+ }
124
+
125
+ /**
126
+ * Invisible field that fires side effects when another field changes.
127
+ * Useful for dependent field updates (e.g. country changes → reset city).
128
+ * Renders nothing visually.
129
+ */
130
+ export function FormListenEffect({ form, name, listeners }: FormListenEffectProps) {
131
+ return (
132
+ <form.Field name={name} listeners={listeners}>
133
+ {() => null}
134
+ </form.Field>
135
+ );
136
+ }
137
+
138
+ /* ── FormActions ─────────────────────────────────────────────────── */
139
+
140
+ export interface FormActionsProps {
141
+ form: AnyFormApi;
142
+ /** Label for the submit button. Default: "Submit". */
143
+ submitLabel?: string;
144
+ /** Label for the reset button. Default: "Reset". */
145
+ resetLabel?: string;
146
+ /** Whether to show the reset button. Default: true. */
147
+ showReset?: boolean;
148
+ /** Disable both buttons externally. */
149
+ disabled?: boolean;
150
+ className?: string;
151
+ }
152
+
153
+ /** Footer with submit and reset buttons. Reacts to form submitting state. */
154
+ export function FormActions({
155
+ form,
156
+ submitLabel = 'Submit',
157
+ resetLabel = 'Reset',
158
+ showReset = true,
159
+ disabled,
160
+ className,
161
+ }: FormActionsProps) {
162
+ return (
163
+ <form.Subscribe
164
+ selector={(s: { canSubmit: boolean; isSubmitting: boolean }) => ({
165
+ canSubmit: s.canSubmit,
166
+ isSubmitting: s.isSubmitting,
167
+ })}
168
+ >
169
+ {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
170
+ {(state: any) => (
171
+ <div className={cn('flex items-center gap-3 pt-2', className)}>
172
+ <Button type="submit" disabled={disabled || !state.canSubmit}>
173
+ {state.isSubmitting ? 'Submitting...' : submitLabel}
174
+ </Button>
175
+ {showReset ? (
176
+ <Button
177
+ type="button"
178
+ variant="outline"
179
+ disabled={disabled || state.isSubmitting}
180
+ onClick={() => form.reset()}
181
+ >
182
+ {resetLabel}
183
+ </Button>
184
+ ) : null}
185
+ </div>
186
+ )}
187
+ </form.Subscribe>
188
+ );
189
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared Form types.
3
+ *
4
+ * Wraps TanStack React Form's internals at the boundary so the rest of the
5
+ * field components can stay strongly typed without leaking 12-param generics.
6
+ */
7
+
8
+ /** Validator config passed through to TanStack Form's `form.Field`. */
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ export type FormFieldValidators = Record<string, any>;
11
+
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ export type AnyFormApi = any;
14
+
15
+ /**
16
+ * Field render-prop shape — kept minimal to avoid coupling to TanStack
17
+ * internals that change across minor versions.
18
+ */
19
+ export interface FieldRenderProps<T> {
20
+ state: {
21
+ value: T;
22
+ meta: { errors: string[]; isTouched: boolean };
23
+ };
24
+ handleChange: (v: T) => void;
25
+ handleBlur: () => void;
26
+ }