@inui/design-system 0.0.0-stage → 1.22.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 (312) hide show
  1. package/README.md +360 -2
  2. package/dist/components/ui/accordion.d.ts +44 -0
  3. package/dist/components/ui/accordion.d.ts.map +1 -0
  4. package/dist/components/ui/accordion.js +86 -0
  5. package/dist/components/ui/accordion.js.map +1 -0
  6. package/dist/components/ui/ai-avatar.d.ts +46 -0
  7. package/dist/components/ui/ai-avatar.d.ts.map +1 -0
  8. package/dist/components/ui/ai-avatar.js +43 -0
  9. package/dist/components/ui/ai-avatar.js.map +1 -0
  10. package/dist/components/ui/ai-label.d.ts +118 -0
  11. package/dist/components/ui/ai-label.d.ts.map +1 -0
  12. package/dist/components/ui/ai-label.js +184 -0
  13. package/dist/components/ui/ai-label.js.map +1 -0
  14. package/dist/components/ui/ai-sparkle.d.ts +91 -0
  15. package/dist/components/ui/ai-sparkle.d.ts.map +1 -0
  16. package/dist/components/ui/ai-sparkle.js +200 -0
  17. package/dist/components/ui/ai-sparkle.js.map +1 -0
  18. package/dist/components/ui/alert-dialog.d.ts +79 -0
  19. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  20. package/dist/components/ui/alert-dialog.js +147 -0
  21. package/dist/components/ui/alert-dialog.js.map +1 -0
  22. package/dist/components/ui/alert.d.ts +71 -0
  23. package/dist/components/ui/alert.d.ts.map +1 -0
  24. package/dist/components/ui/alert.js +108 -0
  25. package/dist/components/ui/alert.js.map +1 -0
  26. package/dist/components/ui/aspect-ratio.d.ts +30 -0
  27. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  28. package/dist/components/ui/aspect-ratio.js +40 -0
  29. package/dist/components/ui/aspect-ratio.js.map +1 -0
  30. package/dist/components/ui/attachment.d.ts +74 -0
  31. package/dist/components/ui/attachment.d.ts.map +1 -0
  32. package/dist/components/ui/attachment.js +142 -0
  33. package/dist/components/ui/attachment.js.map +1 -0
  34. package/dist/components/ui/avatar.d.ts +82 -0
  35. package/dist/components/ui/avatar.d.ts.map +1 -0
  36. package/dist/components/ui/avatar.js +123 -0
  37. package/dist/components/ui/avatar.js.map +1 -0
  38. package/dist/components/ui/badge.d.ts +130 -0
  39. package/dist/components/ui/badge.d.ts.map +1 -0
  40. package/dist/components/ui/badge.js +226 -0
  41. package/dist/components/ui/badge.js.map +1 -0
  42. package/dist/components/ui/breadcrumb.d.ts +43 -0
  43. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  44. package/dist/components/ui/breadcrumb.js +98 -0
  45. package/dist/components/ui/breadcrumb.js.map +1 -0
  46. package/dist/components/ui/browser-tabs.d.ts +218 -0
  47. package/dist/components/ui/browser-tabs.d.ts.map +1 -0
  48. package/dist/components/ui/browser-tabs.js +528 -0
  49. package/dist/components/ui/browser-tabs.js.map +1 -0
  50. package/dist/components/ui/bubble.d.ts +80 -0
  51. package/dist/components/ui/bubble.d.ts.map +1 -0
  52. package/dist/components/ui/bubble.js +235 -0
  53. package/dist/components/ui/bubble.js.map +1 -0
  54. package/dist/components/ui/button-group.d.ts +60 -0
  55. package/dist/components/ui/button-group.d.ts.map +1 -0
  56. package/dist/components/ui/button-group.js +89 -0
  57. package/dist/components/ui/button-group.js.map +1 -0
  58. package/dist/components/ui/button.d.ts +157 -0
  59. package/dist/components/ui/button.d.ts.map +1 -0
  60. package/dist/components/ui/button.js +144 -0
  61. package/dist/components/ui/button.js.map +1 -0
  62. package/dist/components/ui/calendar.d.ts +57 -0
  63. package/dist/components/ui/calendar.d.ts.map +1 -0
  64. package/dist/components/ui/calendar.js +163 -0
  65. package/dist/components/ui/calendar.js.map +1 -0
  66. package/dist/components/ui/card.d.ts +96 -0
  67. package/dist/components/ui/card.d.ts.map +1 -0
  68. package/dist/components/ui/card.js +131 -0
  69. package/dist/components/ui/card.js.map +1 -0
  70. package/dist/components/ui/carousel.d.ts +86 -0
  71. package/dist/components/ui/carousel.d.ts.map +1 -0
  72. package/dist/components/ui/carousel.js +207 -0
  73. package/dist/components/ui/carousel.js.map +1 -0
  74. package/dist/components/ui/checkbox.d.ts +54 -0
  75. package/dist/components/ui/checkbox.d.ts.map +1 -0
  76. package/dist/components/ui/checkbox.js +78 -0
  77. package/dist/components/ui/checkbox.js.map +1 -0
  78. package/dist/components/ui/collapsible.d.ts +45 -0
  79. package/dist/components/ui/collapsible.d.ts.map +1 -0
  80. package/dist/components/ui/collapsible.js +65 -0
  81. package/dist/components/ui/collapsible.js.map +1 -0
  82. package/dist/components/ui/combobox.d.ts +111 -0
  83. package/dist/components/ui/combobox.d.ts.map +1 -0
  84. package/dist/components/ui/combobox.js +264 -0
  85. package/dist/components/ui/combobox.js.map +1 -0
  86. package/dist/components/ui/command.d.ts +71 -0
  87. package/dist/components/ui/command.d.ts.map +1 -0
  88. package/dist/components/ui/command.js +133 -0
  89. package/dist/components/ui/command.js.map +1 -0
  90. package/dist/components/ui/context-menu.d.ts +70 -0
  91. package/dist/components/ui/context-menu.d.ts.map +1 -0
  92. package/dist/components/ui/context-menu.js +163 -0
  93. package/dist/components/ui/context-menu.js.map +1 -0
  94. package/dist/components/ui/count-badge.d.ts +40 -0
  95. package/dist/components/ui/count-badge.d.ts.map +1 -0
  96. package/dist/components/ui/count-badge.js +43 -0
  97. package/dist/components/ui/count-badge.js.map +1 -0
  98. package/dist/components/ui/data-table.d.ts +1026 -0
  99. package/dist/components/ui/data-table.d.ts.map +1 -0
  100. package/dist/components/ui/data-table.js +3423 -0
  101. package/dist/components/ui/data-table.js.map +1 -0
  102. package/dist/components/ui/dialog.d.ts +73 -0
  103. package/dist/components/ui/dialog.d.ts.map +1 -0
  104. package/dist/components/ui/dialog.js +145 -0
  105. package/dist/components/ui/dialog.js.map +1 -0
  106. package/dist/components/ui/direction.d.ts +35 -0
  107. package/dist/components/ui/direction.d.ts.map +1 -0
  108. package/dist/components/ui/direction.js +35 -0
  109. package/dist/components/ui/direction.js.map +1 -0
  110. package/dist/components/ui/drawer.d.ts +53 -0
  111. package/dist/components/ui/drawer.d.ts.map +1 -0
  112. package/dist/components/ui/drawer.js +115 -0
  113. package/dist/components/ui/drawer.js.map +1 -0
  114. package/dist/components/ui/dropdown-menu.d.ts +75 -0
  115. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  116. package/dist/components/ui/dropdown-menu.js +173 -0
  117. package/dist/components/ui/dropdown-menu.js.map +1 -0
  118. package/dist/components/ui/dropzone.d.ts +141 -0
  119. package/dist/components/ui/dropzone.d.ts.map +1 -0
  120. package/dist/components/ui/dropzone.js +257 -0
  121. package/dist/components/ui/dropzone.js.map +1 -0
  122. package/dist/components/ui/empty.d.ts +94 -0
  123. package/dist/components/ui/empty.d.ts.map +1 -0
  124. package/dist/components/ui/empty.js +149 -0
  125. package/dist/components/ui/empty.js.map +1 -0
  126. package/dist/components/ui/field.d.ts +122 -0
  127. package/dist/components/ui/field.d.ts.map +1 -0
  128. package/dist/components/ui/field.js +254 -0
  129. package/dist/components/ui/field.js.map +1 -0
  130. package/dist/components/ui/hover-card.d.ts +51 -0
  131. package/dist/components/ui/hover-card.d.ts.map +1 -0
  132. package/dist/components/ui/hover-card.js +101 -0
  133. package/dist/components/ui/hover-card.js.map +1 -0
  134. package/dist/components/ui/input-group.d.ts +62 -0
  135. package/dist/components/ui/input-group.d.ts.map +1 -0
  136. package/dist/components/ui/input-group.js +115 -0
  137. package/dist/components/ui/input-group.js.map +1 -0
  138. package/dist/components/ui/input-otp.d.ts +52 -0
  139. package/dist/components/ui/input-otp.d.ts.map +1 -0
  140. package/dist/components/ui/input-otp.js +89 -0
  141. package/dist/components/ui/input-otp.js.map +1 -0
  142. package/dist/components/ui/input.d.ts +47 -0
  143. package/dist/components/ui/input.d.ts.map +1 -0
  144. package/dist/components/ui/input.js +47 -0
  145. package/dist/components/ui/input.js.map +1 -0
  146. package/dist/components/ui/item.d.ts +74 -0
  147. package/dist/components/ui/item.d.ts.map +1 -0
  148. package/dist/components/ui/item.js +176 -0
  149. package/dist/components/ui/item.js.map +1 -0
  150. package/dist/components/ui/kbd.d.ts +41 -0
  151. package/dist/components/ui/kbd.d.ts.map +1 -0
  152. package/dist/components/ui/kbd.js +58 -0
  153. package/dist/components/ui/kbd.js.map +1 -0
  154. package/dist/components/ui/key-value-pair.d.ts +56 -0
  155. package/dist/components/ui/key-value-pair.d.ts.map +1 -0
  156. package/dist/components/ui/key-value-pair.js +110 -0
  157. package/dist/components/ui/key-value-pair.js.map +1 -0
  158. package/dist/components/ui/label.d.ts +83 -0
  159. package/dist/components/ui/label.d.ts.map +1 -0
  160. package/dist/components/ui/label.js +85 -0
  161. package/dist/components/ui/label.js.map +1 -0
  162. package/dist/components/ui/marker.d.ts +54 -0
  163. package/dist/components/ui/marker.d.ts.map +1 -0
  164. package/dist/components/ui/marker.js +79 -0
  165. package/dist/components/ui/marker.js.map +1 -0
  166. package/dist/components/ui/menubar.d.ts +72 -0
  167. package/dist/components/ui/menubar.d.ts.map +1 -0
  168. package/dist/components/ui/menubar.js +174 -0
  169. package/dist/components/ui/menubar.js.map +1 -0
  170. package/dist/components/ui/message-scroller.d.ts +53 -0
  171. package/dist/components/ui/message-scroller.d.ts.map +1 -0
  172. package/dist/components/ui/message-scroller.js +95 -0
  173. package/dist/components/ui/message-scroller.js.map +1 -0
  174. package/dist/components/ui/message.d.ts +63 -0
  175. package/dist/components/ui/message.d.ts.map +1 -0
  176. package/dist/components/ui/message.js +92 -0
  177. package/dist/components/ui/message.js.map +1 -0
  178. package/dist/components/ui/meta-list.d.ts +55 -0
  179. package/dist/components/ui/meta-list.d.ts.map +1 -0
  180. package/dist/components/ui/meta-list.js +75 -0
  181. package/dist/components/ui/meta-list.js.map +1 -0
  182. package/dist/components/ui/native-select.d.ts +51 -0
  183. package/dist/components/ui/native-select.d.ts.map +1 -0
  184. package/dist/components/ui/native-select.js +75 -0
  185. package/dist/components/ui/native-select.js.map +1 -0
  186. package/dist/components/ui/navigation-menu.d.ts +56 -0
  187. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  188. package/dist/components/ui/navigation-menu.js +116 -0
  189. package/dist/components/ui/navigation-menu.js.map +1 -0
  190. package/dist/components/ui/page-header.d.ts +223 -0
  191. package/dist/components/ui/page-header.d.ts.map +1 -0
  192. package/dist/components/ui/page-header.js +233 -0
  193. package/dist/components/ui/page-header.js.map +1 -0
  194. package/dist/components/ui/pagination.d.ts +68 -0
  195. package/dist/components/ui/pagination.d.ts.map +1 -0
  196. package/dist/components/ui/pagination.js +145 -0
  197. package/dist/components/ui/pagination.js.map +1 -0
  198. package/dist/components/ui/popover.d.ts +61 -0
  199. package/dist/components/ui/popover.d.ts.map +1 -0
  200. package/dist/components/ui/popover.js +144 -0
  201. package/dist/components/ui/popover.js.map +1 -0
  202. package/dist/components/ui/progress.d.ts +35 -0
  203. package/dist/components/ui/progress.d.ts.map +1 -0
  204. package/dist/components/ui/progress.js +46 -0
  205. package/dist/components/ui/progress.js.map +1 -0
  206. package/dist/components/ui/questionnaire.d.ts +92 -0
  207. package/dist/components/ui/questionnaire.d.ts.map +1 -0
  208. package/dist/components/ui/questionnaire.js +277 -0
  209. package/dist/components/ui/questionnaire.js.map +1 -0
  210. package/dist/components/ui/radio-group.d.ts +50 -0
  211. package/dist/components/ui/radio-group.d.ts.map +1 -0
  212. package/dist/components/ui/radio-group.js +65 -0
  213. package/dist/components/ui/radio-group.js.map +1 -0
  214. package/dist/components/ui/resizable.d.ts +44 -0
  215. package/dist/components/ui/resizable.d.ts.map +1 -0
  216. package/dist/components/ui/resizable.js +73 -0
  217. package/dist/components/ui/resizable.js.map +1 -0
  218. package/dist/components/ui/scroll-area.d.ts +55 -0
  219. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  220. package/dist/components/ui/scroll-area.js +72 -0
  221. package/dist/components/ui/scroll-area.js.map +1 -0
  222. package/dist/components/ui/select.d.ts +274 -0
  223. package/dist/components/ui/select.d.ts.map +1 -0
  224. package/dist/components/ui/select.js +609 -0
  225. package/dist/components/ui/select.js.map +1 -0
  226. package/dist/components/ui/separator.d.ts +31 -0
  227. package/dist/components/ui/separator.d.ts.map +1 -0
  228. package/dist/components/ui/separator.js +38 -0
  229. package/dist/components/ui/separator.js.map +1 -0
  230. package/dist/components/ui/sheet.d.ts +59 -0
  231. package/dist/components/ui/sheet.d.ts.map +1 -0
  232. package/dist/components/ui/sheet.js +128 -0
  233. package/dist/components/ui/sheet.js.map +1 -0
  234. package/dist/components/ui/sidebar.d.ts +166 -0
  235. package/dist/components/ui/sidebar.d.ts.map +1 -0
  236. package/dist/components/ui/sidebar.js +512 -0
  237. package/dist/components/ui/sidebar.js.map +1 -0
  238. package/dist/components/ui/skeleton.d.ts +35 -0
  239. package/dist/components/ui/skeleton.d.ts.map +1 -0
  240. package/dist/components/ui/skeleton.js +46 -0
  241. package/dist/components/ui/skeleton.js.map +1 -0
  242. package/dist/components/ui/slider.d.ts +39 -0
  243. package/dist/components/ui/slider.d.ts.map +1 -0
  244. package/dist/components/ui/slider.js +75 -0
  245. package/dist/components/ui/slider.js.map +1 -0
  246. package/dist/components/ui/sonner.d.ts +56 -0
  247. package/dist/components/ui/sonner.d.ts.map +1 -0
  248. package/dist/components/ui/sonner.js +96 -0
  249. package/dist/components/ui/sonner.js.map +1 -0
  250. package/dist/components/ui/spinner.d.ts +37 -0
  251. package/dist/components/ui/spinner.d.ts.map +1 -0
  252. package/dist/components/ui/spinner.js +51 -0
  253. package/dist/components/ui/spinner.js.map +1 -0
  254. package/dist/components/ui/stepper.d.ts +84 -0
  255. package/dist/components/ui/stepper.d.ts.map +1 -0
  256. package/dist/components/ui/stepper.js +145 -0
  257. package/dist/components/ui/stepper.js.map +1 -0
  258. package/dist/components/ui/switch.d.ts +36 -0
  259. package/dist/components/ui/switch.d.ts.map +1 -0
  260. package/dist/components/ui/switch.js +44 -0
  261. package/dist/components/ui/switch.js.map +1 -0
  262. package/dist/components/ui/table.d.ts +80 -0
  263. package/dist/components/ui/table.d.ts.map +1 -0
  264. package/dist/components/ui/table.js +130 -0
  265. package/dist/components/ui/table.js.map +1 -0
  266. package/dist/components/ui/tabs.d.ts +87 -0
  267. package/dist/components/ui/tabs.d.ts.map +1 -0
  268. package/dist/components/ui/tabs.js +128 -0
  269. package/dist/components/ui/tabs.js.map +1 -0
  270. package/dist/components/ui/textarea.d.ts +35 -0
  271. package/dist/components/ui/textarea.d.ts.map +1 -0
  272. package/dist/components/ui/textarea.js +38 -0
  273. package/dist/components/ui/textarea.js.map +1 -0
  274. package/dist/components/ui/time-picker.d.ts +58 -0
  275. package/dist/components/ui/time-picker.d.ts.map +1 -0
  276. package/dist/components/ui/time-picker.js +188 -0
  277. package/dist/components/ui/time-picker.js.map +1 -0
  278. package/dist/components/ui/toggle-group.d.ts +52 -0
  279. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  280. package/dist/components/ui/toggle-group.js +94 -0
  281. package/dist/components/ui/toggle-group.js.map +1 -0
  282. package/dist/components/ui/toggle.d.ts +50 -0
  283. package/dist/components/ui/toggle.d.ts.map +1 -0
  284. package/dist/components/ui/toggle.js +70 -0
  285. package/dist/components/ui/toggle.js.map +1 -0
  286. package/dist/components/ui/tooltip.d.ts +51 -0
  287. package/dist/components/ui/tooltip.d.ts.map +1 -0
  288. package/dist/components/ui/tooltip.js +78 -0
  289. package/dist/components/ui/tooltip.js.map +1 -0
  290. package/dist/hooks/use-dark-mode.d.ts +2 -0
  291. package/dist/hooks/use-dark-mode.d.ts.map +1 -0
  292. package/dist/hooks/use-dark-mode.js +36 -0
  293. package/dist/hooks/use-dark-mode.js.map +1 -0
  294. package/dist/hooks/use-mobile.d.ts +2 -0
  295. package/dist/hooks/use-mobile.d.ts.map +1 -0
  296. package/dist/hooks/use-mobile.js +20 -0
  297. package/dist/hooks/use-mobile.js.map +1 -0
  298. package/dist/index.d.ts +88 -0
  299. package/dist/index.d.ts.map +1 -0
  300. package/dist/index.js +77 -0
  301. package/dist/lib/scope.d.ts +64 -0
  302. package/dist/lib/scope.d.ts.map +1 -0
  303. package/dist/lib/scope.js +74 -0
  304. package/dist/lib/scope.js.map +1 -0
  305. package/dist/lib/utils.d.ts +3 -0
  306. package/dist/lib/utils.d.ts.map +1 -0
  307. package/dist/lib/utils.js +11 -0
  308. package/dist/lib/utils.js.map +1 -0
  309. package/dist/styles.css +2 -0
  310. package/dist/theme-tokens.css +537 -0
  311. package/dist/theme.css +417 -0
  312. package/package.json +103 -3
@@ -0,0 +1,254 @@
1
+ "use client";
2
+ import { cn } from "../../lib/utils.js";
3
+ import { Separator } from "./separator.js";
4
+ import { Label, OptionalHint, RequiredDot } from "./label.js";
5
+ import * as React from "react";
6
+ import { useMemo } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { cva } from "class-variance-authority";
9
+ //#region src/components/ui/field.tsx
10
+ /**
11
+ * Field — form field wrapper (label + control + description + error).
12
+ *
13
+ * A composite family, not a single component: Field lays out one control
14
+ * with its label/description/error slots; FieldSet+FieldLegend group
15
+ * several Fields for assistive tech; FieldGroup stacks Fields together;
16
+ * FieldSeparator divides sections. Invalid state is driven by
17
+ * `data-invalid` on Field plus `aria-invalid` on the control.
18
+ */
19
+ var FieldIdContext = React.createContext(null);
20
+ var FieldSetIdContext = React.createContext(null);
21
+ function FieldSet({ className, "aria-labelledby": ariaLabelledby, ...props }) {
22
+ const [legendId, setLegendId] = React.useState(null);
23
+ const idContext = React.useMemo(() => ({
24
+ legendId,
25
+ setLegendId
26
+ }), [legendId]);
27
+ return /* @__PURE__ */ jsx(FieldSetIdContext.Provider, {
28
+ value: idContext,
29
+ children: /* @__PURE__ */ jsx("fieldset", {
30
+ "data-slot": "field-set",
31
+ "aria-labelledby": ariaLabelledby ?? legendId ?? void 0,
32
+ className: cn("flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 has-[>[data-slot=field-legend-row]]:!gap-0", className),
33
+ ...props
34
+ })
35
+ });
36
+ }
37
+ function FieldLegend({ className, id, variant = "legend", required = false, optional = false, children, aiLabel, aiLabelAlign = "inline", ...props }) {
38
+ const generatedId = React.useId();
39
+ const legendId = id ?? generatedId;
40
+ const idContext = React.useContext(FieldSetIdContext);
41
+ React.useLayoutEffect(() => {
42
+ idContext?.setLegendId(legendId);
43
+ return () => idContext?.setLegendId(null);
44
+ }, [legendId]);
45
+ return /* @__PURE__ */ jsxs("div", {
46
+ "data-slot": "field-legend-row",
47
+ className: cn("group/field-legend flex h-6 w-full items-start gap-2", aiLabel && aiLabelAlign === "end" && "justify-between"),
48
+ children: [/* @__PURE__ */ jsxs("legend", {
49
+ id: legendId,
50
+ "data-slot": "field-legend",
51
+ "data-variant": variant,
52
+ "data-required": required || void 0,
53
+ "data-optional": optional && !required || void 0,
54
+ className: cn("font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base", className),
55
+ ...props,
56
+ children: [children, required ? /* @__PURE__ */ jsx(RequiredDot, { className: "ml-1 align-top" }) : optional ? /* @__PURE__ */ jsx(OptionalHint, { className: "ml-1" }) : null]
57
+ }), aiLabel]
58
+ });
59
+ }
60
+ function FieldGroup({ className, ...props }) {
61
+ return /* @__PURE__ */ jsx("div", {
62
+ "data-slot": "field-group",
63
+ className: cn("group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4", className),
64
+ ...props
65
+ });
66
+ }
67
+ var fieldVariants = cva("group/field flex w-full gap-2 data-[invalid=true]:text-destructive", {
68
+ variants: { orientation: {
69
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
70
+ horizontal: "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
71
+ responsive: "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
72
+ } },
73
+ defaultVariants: { orientation: "vertical" }
74
+ });
75
+ function Field({ className, orientation = "vertical", ...props }) {
76
+ const [titleId, setTitleId] = React.useState(null);
77
+ const [descriptionId, setDescriptionId] = React.useState(null);
78
+ const [errorId, setErrorId] = React.useState(null);
79
+ const idContext = React.useMemo(() => ({
80
+ titleId,
81
+ setTitleId,
82
+ descriptionId,
83
+ setDescriptionId,
84
+ errorId,
85
+ setErrorId
86
+ }), [
87
+ titleId,
88
+ descriptionId,
89
+ errorId
90
+ ]);
91
+ return /* @__PURE__ */ jsx(FieldIdContext.Provider, {
92
+ value: idContext,
93
+ children: /* @__PURE__ */ jsx("div", {
94
+ role: "group",
95
+ "data-slot": "field",
96
+ "data-orientation": orientation,
97
+ className: cn(fieldVariants({ orientation }), className),
98
+ ...props
99
+ })
100
+ });
101
+ }
102
+ function FieldContent({ className, ...props }) {
103
+ return /* @__PURE__ */ jsx("div", {
104
+ "data-slot": "field-content",
105
+ className: cn("group/field-content flex flex-1 flex-col gap-0.5 leading-snug", className),
106
+ ...props
107
+ });
108
+ }
109
+ function FieldLabel({ className, aiLabel, aiLabelAlign = "end", ...props }) {
110
+ const label = /* @__PURE__ */ jsx(Label, {
111
+ "data-slot": aiLabel ? void 0 : "field-label",
112
+ className: cn("group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:text-disabled-foreground has-data-[state=checked]:border-primary/30 has-data-[state=checked]:bg-primary/5 has-[>[data-slot=field]]:rounded-xl has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-[state=checked]:border-primary/20 dark:has-data-[state=checked]:bg-primary/10", "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col", !aiLabel && className),
113
+ ...props
114
+ });
115
+ if (!aiLabel) return label;
116
+ return /* @__PURE__ */ jsxs("div", {
117
+ "data-slot": "field-label",
118
+ className: cn("group/field-label flex h-6 w-full items-start gap-2 -mb-2", aiLabelAlign === "end" && "justify-between", className),
119
+ children: [label, aiLabel]
120
+ });
121
+ }
122
+ function FieldTitle({ className, id, ...props }) {
123
+ const generatedId = React.useId();
124
+ const titleId = id ?? generatedId;
125
+ const idContext = React.useContext(FieldIdContext);
126
+ React.useLayoutEffect(() => {
127
+ idContext?.setTitleId(titleId);
128
+ return () => idContext?.setTitleId(null);
129
+ }, [titleId]);
130
+ return /* @__PURE__ */ jsx("div", {
131
+ "data-slot": "field-label",
132
+ id: titleId,
133
+ className: cn("flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:text-disabled-foreground", className),
134
+ ...props
135
+ });
136
+ }
137
+ function FieldDescription({ className, id, ...props }) {
138
+ const generatedId = React.useId();
139
+ const descriptionId = id ?? generatedId;
140
+ const idContext = React.useContext(FieldIdContext);
141
+ React.useLayoutEffect(() => {
142
+ idContext?.setDescriptionId(descriptionId);
143
+ return () => idContext?.setDescriptionId(null);
144
+ }, [descriptionId]);
145
+ return /* @__PURE__ */ jsx("p", {
146
+ "data-slot": "field-description",
147
+ id: descriptionId,
148
+ className: cn("text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5", "last:mt-0 nth-last-2:-mt-1", "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary", className),
149
+ ...props
150
+ });
151
+ }
152
+ function FieldSeparator({ children, className, ...props }) {
153
+ return /* @__PURE__ */ jsxs("div", {
154
+ "data-slot": "field-separator",
155
+ "data-content": !!children,
156
+ className: cn("relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2", className),
157
+ ...props,
158
+ children: [/* @__PURE__ */ jsx(Separator, { className: "absolute inset-0 top-1/2" }), children && /* @__PURE__ */ jsx("span", {
159
+ className: "relative mx-auto block w-fit bg-background px-2 text-muted-foreground",
160
+ "data-slot": "field-separator-content",
161
+ children
162
+ })]
163
+ });
164
+ }
165
+ function FieldError({ className, children, errors, id, ...props }) {
166
+ const generatedId = React.useId();
167
+ const errorId = id ?? generatedId;
168
+ const idContext = React.useContext(FieldIdContext);
169
+ const content = useMemo(() => {
170
+ if (children) return children;
171
+ if (!errors?.length) return null;
172
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
173
+ if (uniqueErrors?.length == 1) return uniqueErrors[0]?.message;
174
+ return /* @__PURE__ */ jsx("ul", {
175
+ className: "ml-4 flex list-disc flex-col gap-1",
176
+ children: uniqueErrors.map((error, index) => error?.message && /* @__PURE__ */ jsx("li", { children: error.message }, index))
177
+ });
178
+ }, [children, errors]);
179
+ React.useLayoutEffect(() => {
180
+ idContext?.setErrorId(content ? errorId : null);
181
+ return () => idContext?.setErrorId(null);
182
+ }, [errorId, !!content]);
183
+ if (!content) return null;
184
+ return /* @__PURE__ */ jsx("div", {
185
+ role: "alert",
186
+ "data-slot": "field-error",
187
+ id: errorId,
188
+ className: cn("text-sm font-normal text-destructive", className),
189
+ ...props,
190
+ children: content
191
+ });
192
+ }
193
+ /**
194
+ * WHEN TO USE
195
+ * - Any labelled form field that needs help text or validation messaging — wraps any input primitive (Input, Textarea, Checkbox, ...)
196
+ * - `orientation="horizontal"` for label-beside-control rows (e.g. a Checkbox with title/description via FieldContent + FieldTitle)
197
+ * - FieldSet + FieldLegend to group related Fields for assistive tech; FieldGroup to stack them with consistent spacing
198
+ * - FieldSeparator to divide sections within a FieldGroup, optionally with a label (e.g. "OR")
199
+ *
200
+ * NOT WHEN
201
+ * - A bare label with no description or error text — use Label directly, it's lighter
202
+ * - Display-only key/value pairs with no input — this is a form-control wrapper, not a data display
203
+ *
204
+ * KEY RULES
205
+ * - orientation: vertical (default), horizontal, responsive (stacks on narrow, horizontal at @md container width)
206
+ * - Required/optional are marked on the label, nowhere else: `<FieldLabel required>` (red dot) or `<FieldLabel optional>` (muted "(optional)"), and the same two props on FieldLegend for a whole group. Visual only — the control still carries `required`/`aria-required` for assistive tech. Pick one convention per form: mark the required fields or the optional ones, not both.
207
+ * - Mark invalid state with `data-invalid` on Field AND `aria-invalid` on the control — Field's red text styling depends on `data-invalid`, the control's border/ring styling depends on `aria-invalid`
208
+ * - FieldError accepts either `children` (a fixed message) or an `errors` array of `{ message }` objects (react-hook-form shape) — duplicates are deduped by message, renders a single line for one error or a bulleted list for multiple; renders nothing if there's no content
209
+ * - FieldDescription/FieldError generate an id and register it on Field's context; Checkbox reads that context and defaults its own `aria-describedby`/`aria-labelledby` (to FieldTitle's id) whenever the consumer hasn't already set one — for every other control, still give each element an explicit `id` and set the control's `aria-describedby` to match (see USAGE below), or a screen reader user who tabs to the control never hears the hint/error
210
+ * - FieldLabel wraps Label — use it (not FieldTitle) when the row has an actual form control; use FieldTitle for the horizontal FieldContent pattern where the "label" is really a heading beside a standalone control. FieldTitle is a plain heading-styled `<div>`, not a `<label>` — for controls other than Checkbox, give the control its own `aria-label`/`aria-labelledby` matching FieldTitle's text (see USAGE below) or it ships with no accessible name at all.
211
+ *
212
+ * USAGE
213
+ * <Field>
214
+ * <FieldLabel htmlFor="email">Email</FieldLabel>
215
+ * <Input id="email" type="email" aria-describedby="email-hint" />
216
+ * <FieldDescription id="email-hint">We'll never share your address.</FieldDescription>
217
+ * </Field>
218
+ *
219
+ * // With validation — aria-describedby lists both ids so an invalid field
220
+ * // still announces its hint alongside the error, not just one or the other
221
+ * <Field data-invalid={!!error}>
222
+ * <FieldLabel htmlFor="email">Email</FieldLabel>
223
+ * <Input id="email" type="email" aria-invalid={!!error} aria-describedby="email-hint email-error" />
224
+ * <FieldDescription id="email-hint">We'll never share your address.</FieldDescription>
225
+ * <FieldError id="email-error">{error}</FieldError>
226
+ * </Field>
227
+ *
228
+ * <Field data-invalid orientation="horizontal">
229
+ * <FieldContent>
230
+ * <FieldTitle>Marketing emails</FieldTitle>
231
+ * <FieldDescription>Receive product news and updates.</FieldDescription>
232
+ * </FieldContent>
233
+ * <Checkbox />
234
+ * </Field>
235
+ * // Checkbox picks up FieldTitle as its aria-labelledby and
236
+ * // FieldDescription/FieldError as its aria-describedby automatically —
237
+ * // pass an explicit aria-label/aria-labelledby/aria-describedby yourself
238
+ * // only to override that default. Any other control in this position
239
+ * // (there's no `<label>` wrapping it) still needs its own aria-label/
240
+ * // aria-labelledby matching FieldTitle's text.
241
+ *
242
+ * <FieldSet>
243
+ * <FieldLegend>Project details</FieldLegend>
244
+ * <FieldGroup>
245
+ * <Field>...</Field>
246
+ * <FieldSeparator />
247
+ * <Field>...</Field>
248
+ * </FieldGroup>
249
+ * </FieldSet>
250
+ */
251
+ //#endregion
252
+ export { Field, FieldContent, FieldDescription, FieldError, FieldGroup, FieldIdContext, FieldLabel, FieldLegend, FieldSeparator, FieldSet, FieldTitle };
253
+
254
+ //# sourceMappingURL=field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field.js","names":[],"sources":["../../../src/components/ui/field.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * Field — form field wrapper (label + control + description + error).\n *\n * A composite family, not a single component: Field lays out one control\n * with its label/description/error slots; FieldSet+FieldLegend group\n * several Fields for assistive tech; FieldGroup stacks Fields together;\n * FieldSeparator divides sections. Invalid state is driven by\n * `data-invalid` on Field plus `aria-invalid` on the control.\n */\n\nimport * as React from \"react\"\nimport { useMemo } from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Label, OptionalHint, RequiredDot } from \"@/components/ui/label\"\nimport { Separator } from \"@/components/ui/separator\"\n\n// FieldTitle (unlike FieldLabel) is a plain heading `<div>`, not a `<label>`,\n// for the horizontal FieldContent pattern where the control is a standalone\n// sibling rather than something a `<label>` wraps — so it carries no\n// `htmlFor`/id wiring of its own. Without this context, that documented\n// pattern shipped controls (Checkbox, ...) with no accessible name at all.\n// Consumer-supplied `aria-label`/`aria-labelledby`/`aria-describedby` on the\n// control always wins; this only fills the gap when none was given.\nconst FieldIdContext = React.createContext<{\n titleId: string | null\n setTitleId: (id: string | null) => void\n descriptionId: string | null\n setDescriptionId: (id: string | null) => void\n errorId: string | null\n setErrorId: (id: string | null) => void\n} | null>(null)\n\n// FieldLegend now always renders `<legend>` inside a row `<div>` (so it can\n// sit next to an optional `aiLabel` at a fixed height — see FieldLegend\n// below), which means `<legend>` is no longer FieldSet's literal direct\n// child. That breaks the native fieldset/legend accessible-name link (the\n// browser only picks up `<legend>` as the fieldset's name when it's a\n// direct child), so FieldSet has to restore it explicitly via\n// `aria-labelledby` — this context is how FieldLegend's id reaches it.\nconst FieldSetIdContext = React.createContext<{\n legendId: string | null\n setLegendId: (id: string | null) => void\n} | null>(null)\n\nfunction FieldSet({\n className,\n \"aria-labelledby\": ariaLabelledby,\n ...props\n}: React.ComponentProps<\"fieldset\">) {\n const [legendId, setLegendId] = React.useState<string | null>(null)\n const idContext = React.useMemo(\n () => ({ legendId, setLegendId }),\n [legendId]\n )\n\n return (\n <FieldSetIdContext.Provider value={idContext}>\n <fieldset\n data-slot=\"field-set\"\n aria-labelledby={ariaLabelledby ?? legendId ?? undefined}\n className={cn(\n // `field-legend-row` (FieldLegend's own wrapper) forces gap to 0:\n // FieldLegend's row is a fixed h-6 (24px) and that height is the\n // whole label-to-control gap by design, so FieldSet must not\n // stack its own gap on top. `!` because this has to win over the\n // checkbox-group/radio-group gap-3 rules below when a FieldSet\n // has both a legend row AND one of those as its two children —\n // same `:has()` specificity, so source order alone can't be\n // trusted (see RadioGroup's AI story, which hit exactly this).\n \"flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 has-[>[data-slot=field-legend-row]]:!gap-0\",\n className\n )}\n {...props}\n />\n </FieldSetIdContext.Provider>\n )\n}\n\nfunction FieldLegend({\n className,\n id,\n variant = \"legend\",\n required = false,\n optional = false,\n children,\n aiLabel,\n aiLabelAlign = \"inline\",\n ...props\n}: React.ComponentProps<\"legend\"> & {\n variant?: \"legend\" | \"label\"\n /** Red dot after the legend — the group (checkboxes, radios) is mandatory. Visual only: put `required`/`aria-required` on the group's control(s) too. */\n required?: boolean\n /** Muted \"(optional)\" after the legend. Mutually exclusive with `required`. */\n optional?: boolean\n /** An `AILabel` rendered in the same fixed-height row as the legend — a sibling, never nested inside `<legend>` (AILabelTrigger is a button; a button inside a legend/label is invalid HTML). */\n aiLabel?: React.ReactNode\n /** \"inline\" (default) places `aiLabel` immediately after the legend text — the usual placement for a per-group AI marker (Checkbox/RadioGroup's own AI-flavored stories). \"end\" pushes it to the row's far end instead. */\n aiLabelAlign?: \"inline\" | \"end\"\n}) {\n const generatedId = React.useId()\n const legendId = id ?? generatedId\n const idContext = React.useContext(FieldSetIdContext)\n\n React.useLayoutEffect(() => {\n idContext?.setLegendId(legendId)\n return () => idContext?.setLegendId(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [legendId])\n\n return (\n <div\n data-slot=\"field-legend-row\"\n className={cn(\n \"group/field-legend flex h-6 w-full items-start gap-2\",\n aiLabel && aiLabelAlign === \"end\" && \"justify-between\"\n )}\n >\n <legend\n id={legendId}\n data-slot=\"field-legend\"\n data-variant={variant}\n data-required={required || undefined}\n data-optional={(optional && !required) || undefined}\n className={cn(\n \"font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base\",\n className\n )}\n {...props}\n >\n {children}\n {required ? (\n <RequiredDot className=\"ml-1 align-top\" />\n ) : optional ? (\n <OptionalHint className=\"ml-1\" />\n ) : null}\n </legend>\n {aiLabel}\n </div>\n )\n}\n\nfunction FieldGroup({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-group\"\n className={cn(\n \"group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4\",\n className\n )}\n {...props}\n />\n )\n}\n\nconst fieldVariants = cva(\n \"group/field flex w-full gap-2 data-[invalid=true]:text-destructive\",\n {\n variants: {\n orientation: {\n vertical: \"flex-col *:w-full [&>.sr-only]:w-auto\",\n horizontal:\n \"flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n responsive:\n \"flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px\",\n },\n },\n defaultVariants: {\n orientation: \"vertical\",\n },\n }\n)\n\nfunction Field({\n className,\n orientation = \"vertical\",\n ...props\n}: React.ComponentProps<\"div\"> & VariantProps<typeof fieldVariants>) {\n const [titleId, setTitleId] = React.useState<string | null>(null)\n const [descriptionId, setDescriptionId] = React.useState<string | null>(\n null\n )\n const [errorId, setErrorId] = React.useState<string | null>(null)\n const idContext = React.useMemo(\n () => ({\n titleId,\n setTitleId,\n descriptionId,\n setDescriptionId,\n errorId,\n setErrorId,\n }),\n [titleId, descriptionId, errorId]\n )\n\n return (\n <FieldIdContext.Provider value={idContext}>\n <div\n role=\"group\"\n data-slot=\"field\"\n data-orientation={orientation}\n className={cn(fieldVariants({ orientation }), className)}\n {...props}\n />\n </FieldIdContext.Provider>\n )\n}\n\nfunction FieldContent({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"field-content\"\n className={cn(\n \"group/field-content flex flex-1 flex-col gap-0.5 leading-snug\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldLabel({\n className,\n aiLabel,\n aiLabelAlign = \"end\",\n ...props\n}: React.ComponentProps<typeof Label> & {\n /** An `AILabel` rendered in the same row as the label — a sibling, never\n * nested inside `<label>` (AILabelTrigger is a button; a button inside a\n * label is invalid HTML). Passing this switches FieldLabel to a fixed\n * `h-6` (24px) row, so a grid mixing AI and non-AI fields still aligns —\n * plain FieldLabel (no `aiLabel`) keeps its normal auto height, since\n * forcing every label to a fixed row would also force the unrelated\n * \"FieldLabel wrapping a nested Field\" option-card pattern (see\n * `has-[>[data-slot=field]]` below) into that same fixed height, which\n * needs to stay auto/flex-col instead. */\n aiLabel?: React.ReactNode\n /** \"end\" (default) pushes `aiLabel` to the row's far end — Input/Textarea/Select's own AI-flavored stories. \"inline\" places it immediately after the label text instead. */\n aiLabelAlign?: \"inline\" | \"end\"\n}) {\n const label = (\n <Label\n data-slot={aiLabel ? undefined : \"field-label\"}\n className={cn(\n \"group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:text-disabled-foreground has-data-[state=checked]:border-primary/30 has-data-[state=checked]:bg-primary/5 has-[>[data-slot=field]]:rounded-xl has-[>[data-slot=field]]:border *:data-[slot=field]:p-2.5 dark:has-data-[state=checked]:border-primary/20 dark:has-data-[state=checked]:bg-primary/10\",\n \"has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col\",\n !aiLabel && className\n )}\n {...props}\n />\n )\n\n if (!aiLabel) {\n return label\n }\n\n return (\n <div\n data-slot=\"field-label\"\n className={cn(\n // `-mb-2` cancels Field's own `gap-2` after this row, so the whole\n // gap to the control is this row's own height (24px) — one source\n // of spacing, not two stacked ones. Field's `gap-2` itself stays\n // untouched (it still spaces the control from FieldDescription/\n // FieldError below), so nothing else using Field regresses.\n \"group/field-label flex h-6 w-full items-start gap-2 -mb-2\",\n aiLabelAlign === \"end\" && \"justify-between\",\n className\n )}\n >\n {label}\n {aiLabel}\n </div>\n )\n}\n\nfunction FieldTitle({\n className,\n id,\n ...props\n}: React.ComponentProps<\"div\">) {\n const generatedId = React.useId()\n const titleId = id ?? generatedId\n const idContext = React.useContext(FieldIdContext)\n\n React.useLayoutEffect(() => {\n idContext?.setTitleId(titleId)\n return () => idContext?.setTitleId(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [titleId])\n\n return (\n <div\n data-slot=\"field-label\"\n id={titleId}\n className={cn(\n \"flex w-fit items-center gap-2 text-sm font-medium group-data-[disabled=true]/field:text-disabled-foreground\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldDescription({\n className,\n id,\n ...props\n}: React.ComponentProps<\"p\">) {\n const generatedId = React.useId()\n const descriptionId = id ?? generatedId\n const idContext = React.useContext(FieldIdContext)\n\n React.useLayoutEffect(() => {\n idContext?.setDescriptionId(descriptionId)\n return () => idContext?.setDescriptionId(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [descriptionId])\n\n return (\n <p\n data-slot=\"field-description\"\n id={descriptionId}\n className={cn(\n \"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5\",\n \"last:mt-0 nth-last-2:-mt-1\",\n \"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary\",\n className\n )}\n {...props}\n />\n )\n}\n\nfunction FieldSeparator({\n children,\n className,\n ...props\n}: React.ComponentProps<\"div\"> & {\n children?: React.ReactNode\n}) {\n return (\n <div\n data-slot=\"field-separator\"\n data-content={!!children}\n className={cn(\n \"relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2\",\n className\n )}\n {...props}\n >\n <Separator className=\"absolute inset-0 top-1/2\" />\n {children && (\n <span\n className=\"relative mx-auto block w-fit bg-background px-2 text-muted-foreground\"\n data-slot=\"field-separator-content\"\n >\n {children}\n </span>\n )}\n </div>\n )\n}\n\nfunction FieldError({\n className,\n children,\n errors,\n id,\n ...props\n}: React.ComponentProps<\"div\"> & {\n errors?: Array<{ message?: string } | undefined>\n}) {\n const generatedId = React.useId()\n const errorId = id ?? generatedId\n const idContext = React.useContext(FieldIdContext)\n\n const content = useMemo(() => {\n if (children) {\n return children\n }\n\n if (!errors?.length) {\n return null\n }\n\n const uniqueErrors = [\n ...new Map(errors.map((error) => [error?.message, error])).values(),\n ]\n\n if (uniqueErrors?.length == 1) {\n return uniqueErrors[0]?.message\n }\n\n return (\n <ul className=\"ml-4 flex list-disc flex-col gap-1\">\n {uniqueErrors.map(\n (error, index) =>\n error?.message && <li key={index}>{error.message}</li>\n )}\n </ul>\n )\n }, [children, errors])\n\n React.useLayoutEffect(() => {\n idContext?.setErrorId(content ? errorId : null)\n return () => idContext?.setErrorId(null)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [errorId, !!content])\n\n if (!content) {\n return null\n }\n\n return (\n <div\n role=\"alert\"\n data-slot=\"field-error\"\n id={errorId}\n className={cn(\"text-sm font-normal text-destructive\", className)}\n {...props}\n >\n {content}\n </div>\n )\n}\n\nexport {\n Field,\n FieldLabel,\n FieldDescription,\n FieldError,\n FieldGroup,\n FieldLegend,\n FieldSeparator,\n FieldSet,\n FieldContent,\n FieldTitle,\n FieldIdContext,\n}\n\n/**\n * WHEN TO USE\n * - Any labelled form field that needs help text or validation messaging — wraps any input primitive (Input, Textarea, Checkbox, ...)\n * - `orientation=\"horizontal\"` for label-beside-control rows (e.g. a Checkbox with title/description via FieldContent + FieldTitle)\n * - FieldSet + FieldLegend to group related Fields for assistive tech; FieldGroup to stack them with consistent spacing\n * - FieldSeparator to divide sections within a FieldGroup, optionally with a label (e.g. \"OR\")\n *\n * NOT WHEN\n * - A bare label with no description or error text — use Label directly, it's lighter\n * - Display-only key/value pairs with no input — this is a form-control wrapper, not a data display\n *\n * KEY RULES\n * - orientation: vertical (default), horizontal, responsive (stacks on narrow, horizontal at @md container width)\n * - Required/optional are marked on the label, nowhere else: `<FieldLabel required>` (red dot) or `<FieldLabel optional>` (muted \"(optional)\"), and the same two props on FieldLegend for a whole group. Visual only — the control still carries `required`/`aria-required` for assistive tech. Pick one convention per form: mark the required fields or the optional ones, not both.\n * - Mark invalid state with `data-invalid` on Field AND `aria-invalid` on the control — Field's red text styling depends on `data-invalid`, the control's border/ring styling depends on `aria-invalid`\n * - FieldError accepts either `children` (a fixed message) or an `errors` array of `{ message }` objects (react-hook-form shape) — duplicates are deduped by message, renders a single line for one error or a bulleted list for multiple; renders nothing if there's no content\n * - FieldDescription/FieldError generate an id and register it on Field's context; Checkbox reads that context and defaults its own `aria-describedby`/`aria-labelledby` (to FieldTitle's id) whenever the consumer hasn't already set one — for every other control, still give each element an explicit `id` and set the control's `aria-describedby` to match (see USAGE below), or a screen reader user who tabs to the control never hears the hint/error\n * - FieldLabel wraps Label — use it (not FieldTitle) when the row has an actual form control; use FieldTitle for the horizontal FieldContent pattern where the \"label\" is really a heading beside a standalone control. FieldTitle is a plain heading-styled `<div>`, not a `<label>` — for controls other than Checkbox, give the control its own `aria-label`/`aria-labelledby` matching FieldTitle's text (see USAGE below) or it ships with no accessible name at all.\n *\n * USAGE\n * <Field>\n * <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n * <Input id=\"email\" type=\"email\" aria-describedby=\"email-hint\" />\n * <FieldDescription id=\"email-hint\">We'll never share your address.</FieldDescription>\n * </Field>\n *\n * // With validation — aria-describedby lists both ids so an invalid field\n * // still announces its hint alongside the error, not just one or the other\n * <Field data-invalid={!!error}>\n * <FieldLabel htmlFor=\"email\">Email</FieldLabel>\n * <Input id=\"email\" type=\"email\" aria-invalid={!!error} aria-describedby=\"email-hint email-error\" />\n * <FieldDescription id=\"email-hint\">We'll never share your address.</FieldDescription>\n * <FieldError id=\"email-error\">{error}</FieldError>\n * </Field>\n *\n * <Field data-invalid orientation=\"horizontal\">\n * <FieldContent>\n * <FieldTitle>Marketing emails</FieldTitle>\n * <FieldDescription>Receive product news and updates.</FieldDescription>\n * </FieldContent>\n * <Checkbox />\n * </Field>\n * // Checkbox picks up FieldTitle as its aria-labelledby and\n * // FieldDescription/FieldError as its aria-describedby automatically —\n * // pass an explicit aria-label/aria-labelledby/aria-describedby yourself\n * // only to override that default. Any other control in this position\n * // (there's no `<label>` wrapping it) still needs its own aria-label/\n * // aria-labelledby matching FieldTitle's text.\n *\n * <FieldSet>\n * <FieldLegend>Project details</FieldLegend>\n * <FieldGroup>\n * <Field>...</Field>\n * <FieldSeparator />\n * <Field>...</Field>\n * </FieldGroup>\n * </FieldSet>\n */\n"],"mappings":"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"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * HoverCard — rich preview panel that opens on hover, with open/close delays.
3
+ *
4
+ * Pointer-driven — there is no click or
5
+ * keyboard-only path to the same content, so treat it as a progressive
6
+ * enhancement, not the only route to the information it shows.
7
+ */
8
+ import * as React from "react";
9
+ import { HoverCard as HoverCardPrimitive } from "radix-ui";
10
+ declare function HoverCard({ ...props }: React.ComponentProps<typeof HoverCardPrimitive.Root>): React.JSX.Element;
11
+ declare function HoverCardTrigger({ asChild, href, tabIndex, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Trigger>): React.JSX.Element;
12
+ declare function HoverCardContent({ className, align, sideOffset, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>): React.JSX.Element;
13
+ export { HoverCard, HoverCardTrigger, HoverCardContent };
14
+ /**
15
+ * WHEN TO USE
16
+ * - Previewing an entity on hover without navigating away — a user profile,
17
+ * a linked record's summary, a card behind a link
18
+ *
19
+ * NOT WHEN
20
+ * - Touch-primary interfaces — there's no hover event, so the content
21
+ * becomes unreachable; don't gate essential info behind it
22
+ * - A short, plain-text label — use Tooltip
23
+ * - The panel must open on click or hold interactive controls the user
24
+ * must act on — use Popover
25
+ *
26
+ * KEY RULES
27
+ * - Opens on hover (or focus), not click — this is the trigger mechanism
28
+ * that separates it from Popover
29
+ * - `openDelay`/`closeDelay` default to radix's built-in delays; set both
30
+ * to 0 for instant preview
31
+ * - HoverCardContent is 16rem (w-64) wide by default; override via className
32
+ * - `align` defaults to "center", `sideOffset` defaults to 4
33
+ *
34
+ * USAGE
35
+ * <HoverCard>
36
+ * <HoverCardTrigger asChild><Button variant="link">@priya</Button></HoverCardTrigger>
37
+ * <HoverCardContent className="w-72">
38
+ * <div className="flex gap-3">
39
+ * <Avatar><AvatarImage src="..." /><AvatarFallback>CN</AvatarFallback></Avatar>
40
+ * <div className="grid gap-1">
41
+ * <p className="text-sm font-medium">Priya Nair</p>
42
+ * <p className="text-sm text-muted-foreground">Bio text.</p>
43
+ * </div>
44
+ * </div>
45
+ * </HoverCardContent>
46
+ * </HoverCard>
47
+ *
48
+ * // Instant, for reviewing content without waiting on hover delay
49
+ * <HoverCard openDelay={0} closeDelay={0}>...</HoverCard>
50
+ */
51
+ //# sourceMappingURL=hover-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover-card.d.ts","sourceRoot":"","sources":["../../../src/components/ui/hover-card.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,SAAS,IAAI,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAU1D,iBAAS,SAAS,CAAC,EACjB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,qBAOtD;AAED,iBAAS,gBAAgB,CAAC,EACxB,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,qBA6BzD;AAED,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,KAAgB,EAChB,UAAc,EACd,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAC,qBA0CzD;AAED,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAA;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG"}
@@ -0,0 +1,101 @@
1
+ import { cn } from "../../lib/utils.js";
2
+ import { getPortalContainer } from "../../lib/scope.js";
3
+ import * as React from "react";
4
+ import { HoverCard as HoverCard$1 } from "radix-ui";
5
+ import { jsx } from "react/jsx-runtime";
6
+ //#region src/components/ui/hover-card.tsx
7
+ /**
8
+ * HoverCard — rich preview panel that opens on hover, with open/close delays.
9
+ *
10
+ * Pointer-driven — there is no click or
11
+ * keyboard-only path to the same content, so treat it as a progressive
12
+ * enhancement, not the only route to the information it shows.
13
+ */
14
+ var HoverCardIdContext = React.createContext(null);
15
+ function HoverCard({ ...props }) {
16
+ const contentId = React.useId();
17
+ return /* @__PURE__ */ jsx(HoverCardIdContext.Provider, {
18
+ value: contentId,
19
+ children: /* @__PURE__ */ jsx(HoverCard$1.Root, {
20
+ "data-slot": "hover-card",
21
+ ...props
22
+ })
23
+ });
24
+ }
25
+ function HoverCardTrigger({ asChild, href, tabIndex, ...props }) {
26
+ const contentId = React.useContext(HoverCardIdContext);
27
+ const needsFallbackSemantics = !asChild && !href;
28
+ return /* @__PURE__ */ jsx(HoverCard$1.Trigger, {
29
+ "data-slot": "hover-card-trigger",
30
+ asChild,
31
+ href,
32
+ tabIndex: tabIndex ?? (needsFallbackSemantics ? 0 : void 0),
33
+ role: needsFallbackSemantics ? "button" : void 0,
34
+ "aria-describedby": contentId ?? void 0,
35
+ ...props
36
+ });
37
+ }
38
+ function HoverCardContent({ className, align = "center", sideOffset = 4, ...props }) {
39
+ const contentId = React.useContext(HoverCardIdContext);
40
+ const contentRef = React.useRef(null);
41
+ React.useEffect(() => {
42
+ if (process.env.NODE_ENV === "production") return;
43
+ const el = contentRef.current;
44
+ if (!el) return;
45
+ if (el.querySelectorAll("a[href], button, input, select, textarea, [tabindex]").length > 0) console.warn("HoverCardContent contains interactive/focusable content — HoverCard's content is never part of the tab order (it's dismissed on trigger blur), so these controls are unreachable by keyboard. Use Popover instead if the content needs to hold interactive controls.");
46
+ }, []);
47
+ return /* @__PURE__ */ jsx(HoverCard$1.Portal, {
48
+ "data-slot": "hover-card-portal",
49
+ container: getPortalContainer(),
50
+ children: /* @__PURE__ */ jsx(HoverCard$1.Content, {
51
+ ref: contentRef,
52
+ id: contentId ?? void 0,
53
+ "data-slot": "hover-card-content",
54
+ align,
55
+ sideOffset,
56
+ className: cn("z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-popover ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95", className),
57
+ ...props
58
+ })
59
+ });
60
+ }
61
+ /**
62
+ * WHEN TO USE
63
+ * - Previewing an entity on hover without navigating away — a user profile,
64
+ * a linked record's summary, a card behind a link
65
+ *
66
+ * NOT WHEN
67
+ * - Touch-primary interfaces — there's no hover event, so the content
68
+ * becomes unreachable; don't gate essential info behind it
69
+ * - A short, plain-text label — use Tooltip
70
+ * - The panel must open on click or hold interactive controls the user
71
+ * must act on — use Popover
72
+ *
73
+ * KEY RULES
74
+ * - Opens on hover (or focus), not click — this is the trigger mechanism
75
+ * that separates it from Popover
76
+ * - `openDelay`/`closeDelay` default to radix's built-in delays; set both
77
+ * to 0 for instant preview
78
+ * - HoverCardContent is 16rem (w-64) wide by default; override via className
79
+ * - `align` defaults to "center", `sideOffset` defaults to 4
80
+ *
81
+ * USAGE
82
+ * <HoverCard>
83
+ * <HoverCardTrigger asChild><Button variant="link">@priya</Button></HoverCardTrigger>
84
+ * <HoverCardContent className="w-72">
85
+ * <div className="flex gap-3">
86
+ * <Avatar><AvatarImage src="..." /><AvatarFallback>CN</AvatarFallback></Avatar>
87
+ * <div className="grid gap-1">
88
+ * <p className="text-sm font-medium">Priya Nair</p>
89
+ * <p className="text-sm text-muted-foreground">Bio text.</p>
90
+ * </div>
91
+ * </div>
92
+ * </HoverCardContent>
93
+ * </HoverCard>
94
+ *
95
+ * // Instant, for reviewing content without waiting on hover delay
96
+ * <HoverCard openDelay={0} closeDelay={0}>...</HoverCard>
97
+ */
98
+ //#endregion
99
+ export { HoverCard, HoverCardContent, HoverCardTrigger };
100
+
101
+ //# sourceMappingURL=hover-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hover-card.js","names":[],"sources":["../../../src/components/ui/hover-card.tsx"],"sourcesContent":["/**\n * HoverCard — rich preview panel that opens on hover, with open/close delays.\n *\n * Pointer-driven — there is no click or\n * keyboard-only path to the same content, so treat it as a progressive\n * enhancement, not the only route to the information it shows.\n */\nimport * as React from \"react\"\nimport { HoverCard as HoverCardPrimitive } from \"radix-ui\"\n\nimport { cn } from \"@/lib/utils\"\nimport { getPortalContainer } from \"@/lib/scope\"\n\n// Radix's HoverCard has no Title/Description parts of its own (unlike\n// Dialog/Popover) and generates no id — this is the one thing that lets\n// Trigger and Content reference each other at all.\nconst HoverCardIdContext = React.createContext<string | null>(null)\n\nfunction HoverCard({\n ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Root>) {\n const contentId = React.useId()\n return (\n <HoverCardIdContext.Provider value={contentId}>\n <HoverCardPrimitive.Root data-slot=\"hover-card\" {...props} />\n </HoverCardIdContext.Provider>\n )\n}\n\nfunction HoverCardTrigger({\n asChild,\n href,\n tabIndex,\n ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Trigger>) {\n const contentId = React.useContext(HoverCardIdContext)\n // Radix's Trigger renders a bare `Primitive.a` with pointer/focus\n // handlers wired for the open/close timing, but no `href`, `role`, or\n // `tabIndex` of its own — a plain, non-`asChild`, no-`href` trigger (the\n // one thing the doc-comment's own USAGE example avoids by always\n // wrapping a Button) is otherwise excluded from the tab order entirely,\n // so keyboard/AT users never trigger the open. Only apply the fallback\n // when `asChild` isn't wrapping something that's presumably already\n // interactive.\n const needsFallbackSemantics = !asChild && !href\n return (\n <HoverCardPrimitive.Trigger\n data-slot=\"hover-card-trigger\"\n asChild={asChild}\n href={href}\n tabIndex={tabIndex ?? (needsFallbackSemantics ? 0 : undefined)}\n role={needsFallbackSemantics ? \"button\" : undefined}\n // Nothing else ties the trigger to its preview content — Radix\n // generates no id for either side to reference (confirmed: no\n // Title/Description parts exist here, unlike Dialog/Popover).\n // aria-describedby (not aria-labelledby: this is supplementary\n // preview content, not the trigger's own name) is the only realistic\n // non-visual channel, since HoverCardContentImpl deliberately pulls\n // its interior out of the tab order.\n aria-describedby={contentId ?? undefined}\n {...props}\n />\n )\n}\n\nfunction HoverCardContent({\n className,\n align = \"center\",\n sideOffset = 4,\n ...props\n}: React.ComponentProps<typeof HoverCardPrimitive.Content>) {\n const contentId = React.useContext(HoverCardIdContext)\n const contentRef = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return\n const el = contentRef.current\n if (!el) return\n // Radix force-sets tabindex=\"-1\" on every tabbable descendant here\n // (hover-only content can't safely hold focus) — the doc comment\n // already warns against putting interactive controls inside, but\n // nothing enforces it; this just surfaces the mistake in dev instead\n // of only failing silently under keyboard-only testing.\n const focusable = el.querySelectorAll(\n \"a[href], button, input, select, textarea, [tabindex]\"\n )\n if (focusable.length > 0) {\n console.warn(\n \"HoverCardContent contains interactive/focusable content — HoverCard's content is never part of the tab order (it's dismissed on trigger blur), so these controls are unreachable by keyboard. Use Popover instead if the content needs to hold interactive controls.\"\n )\n }\n }, [])\n\n return (\n <HoverCardPrimitive.Portal\n data-slot=\"hover-card-portal\"\n container={getPortalContainer()}\n >\n <HoverCardPrimitive.Content\n ref={contentRef}\n id={contentId ?? undefined}\n data-slot=\"hover-card-content\"\n align={align}\n sideOffset={sideOffset}\n className={cn(\n \"z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-popover ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95\",\n className\n )}\n {...props}\n />\n </HoverCardPrimitive.Portal>\n )\n}\n\nexport { HoverCard, HoverCardTrigger, HoverCardContent }\n\n/**\n * WHEN TO USE\n * - Previewing an entity on hover without navigating away — a user profile,\n * a linked record's summary, a card behind a link\n *\n * NOT WHEN\n * - Touch-primary interfaces — there's no hover event, so the content\n * becomes unreachable; don't gate essential info behind it\n * - A short, plain-text label — use Tooltip\n * - The panel must open on click or hold interactive controls the user\n * must act on — use Popover\n *\n * KEY RULES\n * - Opens on hover (or focus), not click — this is the trigger mechanism\n * that separates it from Popover\n * - `openDelay`/`closeDelay` default to radix's built-in delays; set both\n * to 0 for instant preview\n * - HoverCardContent is 16rem (w-64) wide by default; override via className\n * - `align` defaults to \"center\", `sideOffset` defaults to 4\n *\n * USAGE\n * <HoverCard>\n * <HoverCardTrigger asChild><Button variant=\"link\">@priya</Button></HoverCardTrigger>\n * <HoverCardContent className=\"w-72\">\n * <div className=\"flex gap-3\">\n * <Avatar><AvatarImage src=\"...\" /><AvatarFallback>CN</AvatarFallback></Avatar>\n * <div className=\"grid gap-1\">\n * <p className=\"text-sm font-medium\">Priya Nair</p>\n * <p className=\"text-sm text-muted-foreground\">Bio text.</p>\n * </div>\n * </div>\n * </HoverCardContent>\n * </HoverCard>\n *\n * // Instant, for reviewing content without waiting on hover delay\n * <HoverCard openDelay={0} closeDelay={0}>...</HoverCard>\n */\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAM,qBAAqB,MAAM,cAA6B,IAAI;AAElE,SAAS,UAAU,EACjB,GAAG,SACoD;CACvD,MAAM,YAAY,MAAM,MAAM;CAC9B,OACE,oBAAC,mBAAmB,UAApB;EAA6B,OAAO;EAClC,UAAA,oBAAC,YAAmB,MAApB;GAAyB,aAAU;GAAa,GAAI;EAAQ,CAAA;CACjC,CAAA;AAEjC;AAEA,SAAS,iBAAiB,EACxB,SACA,MACA,UACA,GAAG,SACuD;CAC1D,MAAM,YAAY,MAAM,WAAW,kBAAkB;CASrD,MAAM,yBAAyB,CAAC,WAAW,CAAC;CAC5C,OACE,oBAAC,YAAmB,SAApB;EACE,aAAU;EACD;EACH;EACN,UAAU,aAAa,yBAAyB,IAAI,KAAA;EACpD,MAAM,yBAAyB,WAAW,KAAA;EAQ1C,oBAAkB,aAAa,KAAA;EAC/B,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,iBAAiB,EACxB,WACA,QAAQ,UACR,aAAa,GACb,GAAG,SACuD;CAC1D,MAAM,YAAY,MAAM,WAAW,kBAAkB;CACrD,MAAM,aAAa,MAAM,OAAuB,IAAI;CAEpD,MAAM,gBAAgB;EACpB,IAAA,QAAA,IAAA,aAA6B,cAAc;EAC3C,MAAM,KAAK,WAAW;EACtB,IAAI,CAAC,IAAI;EAST,IAHkB,GAAG,iBACnB,sDAEE,CAAA,CAAU,SAAS,GACrB,QAAQ,KACN,sQACF;CAEJ,GAAG,CAAC,CAAC;CAEL,OACE,oBAAC,YAAmB,QAApB;EACE,aAAU;EACV,WAAW,mBAAmB;EAE9B,UAAA,oBAAC,YAAmB,SAApB;GACE,KAAK;GACL,IAAI,aAAa,KAAA;GACjB,aAAU;GACH;GACK;GACZ,WAAW,GACT,ohBACA,SACF;GACA,GAAI;EACL,CAAA;CACwB,CAAA;AAE/B"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * InputGroup — Input/Textarea with attached icons, affixes, or buttons.
3
+ *
4
+ * A composite family: InputGroup is the bordered shell; InputGroupInput /
5
+ * InputGroupTextarea render the actual control borderless inside it;
6
+ * InputGroupAddon positions icons/text/buttons at any edge.
7
+ * The group's focus/error rings live on the shell (InputGroup), not the
8
+ * inner control.
9
+ */
10
+ import * as React from "react";
11
+ import { type VariantProps } from "class-variance-authority";
12
+ import { Button } from "../../components/ui/button";
13
+ declare function InputGroup({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
14
+ declare const inputGroupAddonVariants: (props?: ({
15
+ align?: "inline-start" | "inline-end" | "block-end" | "block-start" | null | undefined;
16
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
17
+ declare function InputGroupAddon({ className, align, ...props }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>): React.JSX.Element;
18
+ type InputGroupButtonSize = "xs" | "sm";
19
+ /** @deprecated use `iconOnly` with "xs" | "sm" — same two-axis split as Button. */
20
+ type InputGroupButtonIconSize = "icon-xs" | "icon-sm";
21
+ declare function InputGroupButton({ className, type, variant, size, iconOnly, ...props }: React.ComponentProps<"button"> & Pick<React.ComponentProps<typeof Button>, "variant"> & {
22
+ asChild?: boolean;
23
+ /** Height step inside the group: "xs" (default) or "sm". Icon-only is a separate axis — `iconOnly`. */
24
+ size?: InputGroupButtonSize | InputGroupButtonIconSize;
25
+ /** Square, icon-only button at the chosen `size` step (the same split Button uses). Pass `aria-label`. */
26
+ iconOnly?: boolean;
27
+ }): React.JSX.Element;
28
+ declare function InputGroupText({ className, ...props }: React.ComponentProps<"span">): React.JSX.Element;
29
+ declare function InputGroupInput({ className, ...props }: Omit<React.ComponentProps<"input">, "size">): React.JSX.Element;
30
+ declare function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">): React.JSX.Element;
31
+ export { InputGroup, InputGroupAddon, InputGroupButton, InputGroupText, InputGroupInput, InputGroupTextarea, };
32
+ /**
33
+ * WHEN TO USE
34
+ * - A search field with a leading icon
35
+ * - A URL field with an "https://" prefix or a units suffix
36
+ * - A textarea with a send button beneath it (a composer) — use `align="block-end"` on the addon
37
+ *
38
+ * NOT WHEN
39
+ * - A plain field with no icon/affix — use Input or Textarea directly
40
+ * - Two or more unrelated buttons standing alone — use ButtonGroup
41
+ *
42
+ * KEY RULES
43
+ * - Use InputGroupInput / InputGroupTextarea inside, not the plain Input/Textarea — they strip the inner border/ring so only the InputGroup shell shows focus/error state
44
+ * - InputGroupAddon align: inline-start, inline-end (default row, sit beside the control), block-start, block-end (full-width row above/below the control — this is what turns the group into a multi-row composer)
45
+ * - Clicking an addon focuses the control automatically, unless the click target is a button inside it
46
+ * - InputGroupButton sizes: xs (default), sm — plus `iconOnly` for a square icon button at that step
47
+ * (the old icon-xs/icon-sm values still work as deprecated aliases) — matches the group's compact height
48
+ *
49
+ * USAGE
50
+ * <InputGroup>
51
+ * <InputGroupAddon><SearchIcon /></InputGroupAddon>
52
+ * <InputGroupInput placeholder="Search..." />
53
+ * </InputGroup>
54
+ *
55
+ * <InputGroup>
56
+ * <InputGroupTextarea placeholder="Message..." />
57
+ * <InputGroupAddon align="block-end">
58
+ * <InputGroupButton size="sm" iconOnly aria-label="Send"><SendIcon /></InputGroupButton>
59
+ * </InputGroupAddon>
60
+ * </InputGroup>
61
+ */
62
+ //# sourceMappingURL=input-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input-group.d.ts","sourceRoot":"","sources":["../../../src/components/ui/input-group.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAGjE,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAA;AAI/C,iBAAS,UAAU,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,qBAYvE;AAED,QAAA,MAAM,uBAAuB;;8EAmB5B,CAAA;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,KAAsB,EACtB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,OAAO,uBAAuB,CAAC,qBAgB5E;AAoBD,KAAK,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAAA;AACvC,mFAAmF;AACnF,KAAK,wBAAwB,GAAG,SAAS,GAAG,SAAS,CAAA;AAMrD,iBAAS,gBAAgB,CAAC,EACxB,SAAS,EACT,IAAe,EACf,OAAiB,EACjB,IAAW,EACX,QAAgB,EAChB,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,GAC/B,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,MAAM,CAAC,EAAE,SAAS,CAAC,GAAG;IACrD,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,uGAAuG;IACvG,IAAI,CAAC,EAAE,oBAAoB,GAAG,wBAAwB,CAAA;IACtD,0GAA0G;IAC1G,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,qBAqBF;AAED,iBAAS,cAAc,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,qBAU5E;AAED,iBAAS,eAAe,CAAC,EACvB,SAAS,EACT,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,qBAW7C;AAED,iBAAS,kBAAkB,CAAC,EAC1B,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC,qBAWlC;AAED,OAAO,EACL,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,kBAAkB,GACnB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG"}