@craftzbay/ui 0.10.0 → 0.11.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 (187) hide show
  1. package/README.md +18 -12
  2. package/dist-lib/__tests__/helpers/color.d.ts +9 -0
  3. package/dist-lib/__tests__/helpers/theme.d.ts +18 -0
  4. package/dist-lib/components/ui/Accordion.js +18 -7
  5. package/dist-lib/components/ui/Accordion.js.map +1 -1
  6. package/dist-lib/components/ui/Alert.d.ts +16 -0
  7. package/dist-lib/components/ui/Alert.js +47 -34
  8. package/dist-lib/components/ui/Alert.js.map +1 -1
  9. package/dist-lib/components/ui/Avatar.d.ts +1 -1
  10. package/dist-lib/components/ui/Avatar.js +45 -47
  11. package/dist-lib/components/ui/Avatar.js.map +1 -1
  12. package/dist-lib/components/ui/Badge.d.ts +3 -1
  13. package/dist-lib/components/ui/Badge.js +24 -15
  14. package/dist-lib/components/ui/Badge.js.map +1 -1
  15. package/dist-lib/components/ui/Breadcrumbs.d.ts +2 -0
  16. package/dist-lib/components/ui/Breadcrumbs.js +46 -28
  17. package/dist-lib/components/ui/Breadcrumbs.js.map +1 -1
  18. package/dist-lib/components/ui/Button.d.ts +5 -3
  19. package/dist-lib/components/ui/Button.js +59 -45
  20. package/dist-lib/components/ui/Button.js.map +1 -1
  21. package/dist-lib/components/ui/Calendar.d.ts +1 -1
  22. package/dist-lib/components/ui/Calendar.js +87 -59
  23. package/dist-lib/components/ui/Calendar.js.map +1 -1
  24. package/dist-lib/components/ui/Card.d.ts +2 -0
  25. package/dist-lib/components/ui/Card.js +48 -67
  26. package/dist-lib/components/ui/Card.js.map +1 -1
  27. package/dist-lib/components/ui/Carousel.d.ts +6 -6
  28. package/dist-lib/components/ui/Carousel.js +133 -102
  29. package/dist-lib/components/ui/Carousel.js.map +1 -1
  30. package/dist-lib/components/ui/Chart.d.ts +49 -10
  31. package/dist-lib/components/ui/Chart.js +415 -181
  32. package/dist-lib/components/ui/Chart.js.map +1 -1
  33. package/dist-lib/components/ui/Checkbox.js +42 -28
  34. package/dist-lib/components/ui/Checkbox.js.map +1 -1
  35. package/dist-lib/components/ui/Combobox.d.ts +2 -0
  36. package/dist-lib/components/ui/Combobox.js +142 -111
  37. package/dist-lib/components/ui/Combobox.js.map +1 -1
  38. package/dist-lib/components/ui/CommandPalette.d.ts +6 -3
  39. package/dist-lib/components/ui/CommandPalette.js +92 -87
  40. package/dist-lib/components/ui/CommandPalette.js.map +1 -1
  41. package/dist-lib/components/ui/ContextMenu.d.ts +7 -2
  42. package/dist-lib/components/ui/ContextMenu.js +77 -62
  43. package/dist-lib/components/ui/ContextMenu.js.map +1 -1
  44. package/dist-lib/components/ui/DataGrid.d.ts +17 -4
  45. package/dist-lib/components/ui/DataGrid.js +65 -50
  46. package/dist-lib/components/ui/DataGrid.js.map +1 -1
  47. package/dist-lib/components/ui/DatePicker.js +118 -99
  48. package/dist-lib/components/ui/DatePicker.js.map +1 -1
  49. package/dist-lib/components/ui/DesignSystemProvider.d.ts +94 -40
  50. package/dist-lib/components/ui/DesignSystemProvider.js +111 -55
  51. package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -1
  52. package/dist-lib/components/ui/Dialog.d.ts +18 -3
  53. package/dist-lib/components/ui/Dialog.js +105 -80
  54. package/dist-lib/components/ui/Dialog.js.map +1 -1
  55. package/dist-lib/components/ui/Drawer.d.ts +17 -2
  56. package/dist-lib/components/ui/Drawer.js +89 -55
  57. package/dist-lib/components/ui/Drawer.js.map +1 -1
  58. package/dist-lib/components/ui/DropdownMenu.js +14 -9
  59. package/dist-lib/components/ui/DropdownMenu.js.map +1 -1
  60. package/dist-lib/components/ui/EmptyState.d.ts +2 -0
  61. package/dist-lib/components/ui/EmptyState.js +30 -16
  62. package/dist-lib/components/ui/EmptyState.js.map +1 -1
  63. package/dist-lib/components/ui/ErrorState.d.ts +13 -2
  64. package/dist-lib/components/ui/ErrorState.js +44 -23
  65. package/dist-lib/components/ui/ErrorState.js.map +1 -1
  66. package/dist-lib/components/ui/FileUpload.js +59 -56
  67. package/dist-lib/components/ui/FileUpload.js.map +1 -1
  68. package/dist-lib/components/ui/Form.d.ts +15 -2
  69. package/dist-lib/components/ui/Form.js +97 -71
  70. package/dist-lib/components/ui/Form.js.map +1 -1
  71. package/dist-lib/components/ui/Icon.d.ts +1 -1
  72. package/dist-lib/components/ui/Icon.js +20 -14
  73. package/dist-lib/components/ui/Icon.js.map +1 -1
  74. package/dist-lib/components/ui/IconButton.d.ts +2 -2
  75. package/dist-lib/components/ui/IconButton.js +28 -20
  76. package/dist-lib/components/ui/IconButton.js.map +1 -1
  77. package/dist-lib/components/ui/Input.d.ts +3 -1
  78. package/dist-lib/components/ui/Input.js +102 -79
  79. package/dist-lib/components/ui/Input.js.map +1 -1
  80. package/dist-lib/components/ui/Kbd.js +1 -1
  81. package/dist-lib/components/ui/Kbd.js.map +1 -1
  82. package/dist-lib/components/ui/MultiSelect.js +121 -107
  83. package/dist-lib/components/ui/MultiSelect.js.map +1 -1
  84. package/dist-lib/components/ui/Pagination.js +67 -62
  85. package/dist-lib/components/ui/Pagination.js.map +1 -1
  86. package/dist-lib/components/ui/Popover.js +14 -14
  87. package/dist-lib/components/ui/Popover.js.map +1 -1
  88. package/dist-lib/components/ui/Progress.d.ts +3 -3
  89. package/dist-lib/components/ui/Progress.js +77 -72
  90. package/dist-lib/components/ui/Progress.js.map +1 -1
  91. package/dist-lib/components/ui/RadioGroup.d.ts +1 -1
  92. package/dist-lib/components/ui/RadioGroup.js +69 -52
  93. package/dist-lib/components/ui/RadioGroup.js.map +1 -1
  94. package/dist-lib/components/ui/RelativeTime.d.ts +19 -0
  95. package/dist-lib/components/ui/RelativeTime.js +31 -0
  96. package/dist-lib/components/ui/RelativeTime.js.map +1 -0
  97. package/dist-lib/components/ui/ScrollArea.d.ts +9 -1
  98. package/dist-lib/components/ui/ScrollArea.js +43 -27
  99. package/dist-lib/components/ui/ScrollArea.js.map +1 -1
  100. package/dist-lib/components/ui/Select.d.ts +6 -1
  101. package/dist-lib/components/ui/Select.js +79 -71
  102. package/dist-lib/components/ui/Select.js.map +1 -1
  103. package/dist-lib/components/ui/Separator.js +1 -1
  104. package/dist-lib/components/ui/Separator.js.map +1 -1
  105. package/dist-lib/components/ui/Sheet.d.ts +9 -0
  106. package/dist-lib/components/ui/Sheet.js +69 -46
  107. package/dist-lib/components/ui/Sheet.js.map +1 -1
  108. package/dist-lib/components/ui/Sidebar.d.ts +12 -9
  109. package/dist-lib/components/ui/Sidebar.js +152 -109
  110. package/dist-lib/components/ui/Sidebar.js.map +1 -1
  111. package/dist-lib/components/ui/Skeleton.d.ts +13 -2
  112. package/dist-lib/components/ui/Skeleton.js +15 -14
  113. package/dist-lib/components/ui/Skeleton.js.map +1 -1
  114. package/dist-lib/components/ui/Slider.js +41 -29
  115. package/dist-lib/components/ui/Slider.js.map +1 -1
  116. package/dist-lib/components/ui/Snackbar.js +23 -22
  117. package/dist-lib/components/ui/Snackbar.js.map +1 -1
  118. package/dist-lib/components/ui/Spinner.js +18 -6
  119. package/dist-lib/components/ui/Spinner.js.map +1 -1
  120. package/dist-lib/components/ui/Stepper.d.ts +6 -0
  121. package/dist-lib/components/ui/Stepper.js +71 -48
  122. package/dist-lib/components/ui/Stepper.js.map +1 -1
  123. package/dist-lib/components/ui/Switch.js +46 -43
  124. package/dist-lib/components/ui/Switch.js.map +1 -1
  125. package/dist-lib/components/ui/Table.d.ts +33 -5
  126. package/dist-lib/components/ui/Table.js +141 -119
  127. package/dist-lib/components/ui/Table.js.map +1 -1
  128. package/dist-lib/components/ui/Tabs.js +50 -48
  129. package/dist-lib/components/ui/Tabs.js.map +1 -1
  130. package/dist-lib/components/ui/TagInput.js +45 -45
  131. package/dist-lib/components/ui/TagInput.js.map +1 -1
  132. package/dist-lib/components/ui/Textarea.js +65 -54
  133. package/dist-lib/components/ui/Textarea.js.map +1 -1
  134. package/dist-lib/components/ui/Timeline.js +39 -35
  135. package/dist-lib/components/ui/Timeline.js.map +1 -1
  136. package/dist-lib/components/ui/Toast.js +44 -57
  137. package/dist-lib/components/ui/Toast.js.map +1 -1
  138. package/dist-lib/components/ui/Tooltip.d.ts +4 -1
  139. package/dist-lib/components/ui/Tooltip.js +24 -22
  140. package/dist-lib/components/ui/Tooltip.js.map +1 -1
  141. package/dist-lib/components/ui/TopNav.d.ts +7 -3
  142. package/dist-lib/components/ui/TopNav.js +39 -22
  143. package/dist-lib/components/ui/TopNav.js.map +1 -1
  144. package/dist-lib/components/ui/Tree.d.ts +15 -10
  145. package/dist-lib/components/ui/Tree.js +82 -79
  146. package/dist-lib/components/ui/Tree.js.map +1 -1
  147. package/dist-lib/hooks/use-debounce.d.ts +19 -0
  148. package/dist-lib/hooks/use-debounce.js +36 -0
  149. package/dist-lib/hooks/use-debounce.js.map +1 -0
  150. package/dist-lib/hooks/use-delayed-loading.d.ts +21 -0
  151. package/dist-lib/hooks/use-delayed-loading.js +20 -0
  152. package/dist-lib/hooks/use-delayed-loading.js.map +1 -0
  153. package/dist-lib/hooks/use-field-ids.d.ts +2 -1
  154. package/dist-lib/hooks/use-field-ids.js +9 -9
  155. package/dist-lib/hooks/use-field-ids.js.map +1 -1
  156. package/dist-lib/hooks/use-media-query.d.ts +3 -1
  157. package/dist-lib/hooks/use-media-query.js +16 -12
  158. package/dist-lib/hooks/use-media-query.js.map +1 -1
  159. package/dist-lib/hooks/use-modifier-key.d.ts +13 -0
  160. package/dist-lib/hooks/use-modifier-key.js +19 -0
  161. package/dist-lib/hooks/use-modifier-key.js.map +1 -0
  162. package/dist-lib/hooks/use-toast.d.ts +7 -2
  163. package/dist-lib/hooks/use-toast.js +61 -30
  164. package/dist-lib/hooks/use-toast.js.map +1 -1
  165. package/dist-lib/icons/index.d.ts +9 -0
  166. package/dist-lib/icons/index.js +148 -130
  167. package/dist-lib/icons/index.js.map +1 -1
  168. package/dist-lib/illustrations/index.d.ts +1 -0
  169. package/dist-lib/illustrations/index.js +15 -6
  170. package/dist-lib/illustrations/index.js.map +1 -1
  171. package/dist-lib/index.d.ts +6 -0
  172. package/dist-lib/index.js +280 -260
  173. package/dist-lib/index.js.map +1 -1
  174. package/dist-lib/lib/format.d.ts +65 -0
  175. package/dist-lib/lib/format.js +95 -0
  176. package/dist-lib/lib/format.js.map +1 -0
  177. package/dist-lib/lib/return-focus.d.ts +11 -0
  178. package/dist-lib/lib/return-focus.js +12 -0
  179. package/dist-lib/lib/return-focus.js.map +1 -0
  180. package/dist-lib/lib/strings.d.ts +62 -1
  181. package/dist-lib/lib/strings.js +75 -19
  182. package/dist-lib/lib/strings.js.map +1 -1
  183. package/dist-lib/lib/strings.mn.js +50 -5
  184. package/dist-lib/lib/strings.mn.js.map +1 -1
  185. package/dist-lib/styles.css +1 -1
  186. package/dist-lib/theme.css +141 -78
  187. package/package.json +11 -4
@@ -1 +1 @@
1
- {"version":3,"file":"FileUpload.js","sources":["../../../src/components/ui/FileUpload.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useId,\n useRef,\n useState,\n type DragEvent as ReactDragEvent,\n type ReactNode,\n} from 'react';\nimport { File as FileIcon, Upload, X } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { formatString } from '@/lib/strings';\n\nexport type FileRejectReason = 'size' | 'type';\n\nexport interface FileUploadProps {\n /** Accepted file types, e.g. `image/*` or `.pdf,.docx`. */\n accept?: string;\n /** Allow multiple files. */\n multiple?: boolean;\n /** Max file size in bytes. Files larger than this are rejected. */\n maxSize?: number;\n /** Controlled file list. */\n value?: File[];\n /** Called when files are added or removed. */\n onChange?: (files: File[]) => void;\n /** Called with the files that were dropped because of `maxSize` or `accept`. */\n onReject?: (files: File[], reason: FileRejectReason) => void;\n /** Helper text inside the drop zone. */\n hint?: ReactNode;\n /** Disable the picker entirely. */\n disabled?: boolean;\n className?: string;\n}\n\nfunction formatSize(bytes: number) {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / 1024 / 1024).toFixed(1)} MB`;\n}\n\n/** Mirror the browser's `accept` matching for dropped files (which skip the picker filter). */\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const rules = accept\n .split(',')\n .map((r) => r.trim().toLowerCase())\n .filter(Boolean);\n if (!rules.length) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return rules.some((rule) => {\n if (rule.startsWith('.')) return name.endsWith(rule);\n if (rule.endsWith('/*')) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n\n/**\n * Drag-and-drop file picker with an inline file list. Uncontrolled by default;\n * pass `value` + `onChange` to control externally.\n */\nexport const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(function FileUpload(\n { accept, multiple, maxSize, value, onChange, onReject, hint, disabled, className },\n ref,\n) {\n const strings = useStrings();\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const [internal, setInternal] = useState<File[]>([]);\n const [over, setOver] = useState(false);\n const files = value ?? internal;\n\n const update = useCallback(\n (next: File[]) => {\n if (value === undefined) setInternal(next);\n onChange?.(next);\n },\n [onChange, value],\n );\n\n const addFiles = useCallback(\n (incoming: FileList | File[]) => {\n const accepted: File[] = [];\n const tooLarge: File[] = [];\n const wrongType: File[] = [];\n for (const f of Array.from(incoming)) {\n if (!matchesAccept(f, accept)) wrongType.push(f);\n else if (maxSize && f.size > maxSize) tooLarge.push(f);\n else accepted.push(f);\n }\n if (wrongType.length) onReject?.(wrongType, 'type');\n if (tooLarge.length) onReject?.(tooLarge, 'size');\n if (!accepted.length) return;\n update(multiple ? [...files, ...accepted] : accepted.slice(0, 1));\n },\n [accept, files, maxSize, multiple, onReject, update],\n );\n\n const remove = (idx: number) => update(files.filter((_, i) => i !== idx));\n\n const onDrop = (e: ReactDragEvent<HTMLLabelElement>) => {\n e.preventDefault();\n setOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n };\n\n return (\n <div ref={ref} className={cn('flex flex-col gap-3', className)}>\n <label\n htmlFor={inputId}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setOver(true);\n }}\n onDragLeave={() => setOver(false)}\n onDrop={onDrop}\n className={cn(\n 'group relative flex cursor-pointer flex-col items-center justify-center gap-2',\n 'border-border bg-background-subtle rounded-md border border-dashed px-6 py-8',\n 'text-center transition-colors',\n 'hover:border-border-strong hover:bg-background-muted',\n 'focus-within:ring-ring focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2',\n over && 'border-accent bg-accent-soft',\n disabled && 'pointer-events-none opacity-50',\n )}\n >\n <Upload className=\"text-foreground-muted size-5\" aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"text-sm font-medium\">{strings.fileUpload.drop}</p>\n {hint && <p className=\"text-foreground-muted text-xs\">{hint}</p>}\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className=\"sr-only\"\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n // Allow re-selecting the same file after removing it.\n e.target.value = '';\n }}\n />\n </label>\n\n {files.length > 0 && (\n <ul className=\"flex flex-col gap-2\">\n {files.map((file, idx) => (\n <li\n key={`${file.name}-${idx}`}\n className=\"border-border bg-card flex items-center gap-3 rounded-md border px-3 py-2\"\n >\n <FileIcon className=\"text-foreground-muted size-4 shrink-0\" aria-hidden />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm\" title={file.name}>\n {file.name}\n </p>\n <p className=\"text-foreground-muted text-xs\">{formatSize(file.size)}</p>\n </div>\n <button\n type=\"button\"\n onClick={() => remove(idx)}\n className=\"text-foreground-muted hover:bg-background-muted hover:text-foreground focus-visible:ring-ring rounded-sm p-1 outline-none focus-visible:ring-2\"\n aria-label={formatString(strings.fileUpload.remove, { name: file.name })}\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n});\nFileUpload.displayName = 'FileUpload';\n"],"names":["formatSize","bytes","matchesAccept","file","accept","rules","name","type","rule","FileUpload","forwardRef","multiple","maxSize","value","onChange","onReject","hint","disabled","className","ref","strings","useStrings","inputRef","useRef","inputId","useId","internal","setInternal","useState","over","setOver","files","update","useCallback","next","addFiles","incoming","accepted","tooLarge","wrongType","f","remove","idx","_","i","onDrop","cn","jsxs","jsx","Upload","FileIcon","formatString","X"],"mappings":"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"}
1
+ {"version":3,"file":"FileUpload.js","sources":["../../../src/components/ui/FileUpload.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n useCallback,\n useId,\n useRef,\n useState,\n type DragEvent as ReactDragEvent,\n type ReactNode,\n} from 'react';\nimport { File as FileIcon, Upload, X } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { formatString, type UiStrings } from '@/lib/strings';\n\nexport type FileRejectReason = 'size' | 'type';\n\nexport interface FileUploadProps {\n /** Accepted file types, e.g. `image/*` or `.pdf,.docx`. */\n accept?: string;\n /** Allow multiple files. */\n multiple?: boolean;\n /** Max file size in bytes. Files larger than this are rejected. */\n maxSize?: number;\n /** Controlled file list. */\n value?: File[];\n /** Called when files are added or removed. */\n onChange?: (files: File[]) => void;\n /** Called with the files that were dropped because of `maxSize` or `accept`. */\n onReject?: (files: File[], reason: FileRejectReason) => void;\n /** Helper text inside the drop zone. */\n hint?: ReactNode;\n /** Disable the picker entirely. */\n disabled?: boolean;\n className?: string;\n}\n\nfunction formatSize(bytes: number, s: UiStrings['fileUpload']) {\n if (bytes < 1024) return formatString(s.bytes, { n: bytes });\n if (bytes < 1024 * 1024) return formatString(s.kilobytes, { n: (bytes / 1024).toFixed(1) });\n return formatString(s.megabytes, { n: (bytes / 1024 / 1024).toFixed(1) });\n}\n\n/** Mirror the browser's `accept` matching for dropped files (which skip the picker filter). */\nfunction matchesAccept(file: File, accept?: string): boolean {\n if (!accept) return true;\n const rules = accept\n .split(',')\n .map((r) => r.trim().toLowerCase())\n .filter(Boolean);\n if (!rules.length) return true;\n const name = file.name.toLowerCase();\n const type = file.type.toLowerCase();\n return rules.some((rule) => {\n if (rule.startsWith('.')) return name.endsWith(rule);\n if (rule.endsWith('/*')) return type.startsWith(rule.slice(0, -1));\n return type === rule;\n });\n}\n\n/**\n * Drag-and-drop file picker with an inline file list. Uncontrolled by default;\n * pass `value` + `onChange` to control externally.\n */\nexport const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(function FileUpload(\n { accept, multiple, maxSize, value, onChange, onReject, hint, disabled, className },\n ref,\n) {\n const strings = useStrings();\n const inputRef = useRef<HTMLInputElement>(null);\n const inputId = useId();\n const [internal, setInternal] = useState<File[]>([]);\n const [over, setOver] = useState(false);\n // dragenter/dragleave fire for every child element; count nesting depth so\n // the highlight does not flicker while the pointer crosses the label's children.\n const dragDepth = useRef(0);\n const files = value ?? internal;\n\n const update = useCallback(\n (next: File[]) => {\n if (value === undefined) setInternal(next);\n onChange?.(next);\n },\n [onChange, value],\n );\n\n const addFiles = useCallback(\n (incoming: FileList | File[]) => {\n const accepted: File[] = [];\n const tooLarge: File[] = [];\n const wrongType: File[] = [];\n for (const f of Array.from(incoming)) {\n if (!matchesAccept(f, accept)) wrongType.push(f);\n else if (maxSize && f.size > maxSize) tooLarge.push(f);\n else accepted.push(f);\n }\n if (wrongType.length) onReject?.(wrongType, 'type');\n if (tooLarge.length) onReject?.(tooLarge, 'size');\n if (!accepted.length) return;\n update(multiple ? [...files, ...accepted] : accepted.slice(0, 1));\n },\n [accept, files, maxSize, multiple, onReject, update],\n );\n\n const remove = (idx: number) => update(files.filter((_, i) => i !== idx));\n\n const onDrop = (e: ReactDragEvent<HTMLLabelElement>) => {\n e.preventDefault();\n dragDepth.current = 0;\n setOver(false);\n if (disabled) return;\n addFiles(e.dataTransfer.files);\n };\n\n return (\n <div ref={ref} className={cn('flex flex-col gap-3', className)}>\n <label\n htmlFor={inputId}\n onDragEnter={() => {\n dragDepth.current += 1;\n if (!disabled) setOver(true);\n }}\n onDragOver={(e) => e.preventDefault()}\n onDragLeave={() => {\n dragDepth.current = Math.max(0, dragDepth.current - 1);\n if (dragDepth.current === 0) setOver(false);\n }}\n onDrop={onDrop}\n className={cn(\n 'group relative flex cursor-pointer flex-col items-center justify-center gap-2',\n 'border-border-input bg-background-subtle rounded-md border border-dashed px-6 py-8',\n 'text-center transition-colors',\n 'hover:border-border-strong hover:bg-background-muted',\n 'focus-within:ring-ring focus-within:ring-offset-background focus-within:ring-2 focus-within:ring-offset-2',\n over && 'border-accent bg-accent-soft',\n disabled && 'pointer-events-none opacity-50',\n )}\n >\n <Upload className=\"text-foreground-muted size-5\" aria-hidden />\n <div className=\"space-y-1\">\n <p className=\"text-sm font-medium\">{strings.fileUpload.drop}</p>\n {hint && <p className=\"text-foreground-muted text-xs\">{hint}</p>}\n </div>\n <input\n ref={inputRef}\n id={inputId}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n disabled={disabled}\n className=\"sr-only\"\n onChange={(e) => {\n if (e.target.files) addFiles(e.target.files);\n // Allow re-selecting the same file after removing it.\n e.target.value = '';\n }}\n />\n </label>\n\n {files.length > 0 && (\n <ul className=\"flex flex-col gap-2\">\n {files.map((file, idx) => (\n <li\n key={`${file.name}-${file.size}-${file.lastModified}-${idx}`}\n className=\"border-border bg-card flex items-center gap-3 rounded-md border px-3 py-2\"\n >\n <FileIcon className=\"text-foreground-muted size-4 shrink-0\" aria-hidden />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm\" title={file.name}>\n {file.name}\n </p>\n <p className=\"text-foreground-muted text-xs\">\n {formatSize(file.size, strings.fileUpload)}\n </p>\n </div>\n <button\n type=\"button\"\n onClick={() => remove(idx)}\n className=\"text-foreground-muted hover:bg-background-muted hover:text-foreground focus-visible:ring-ring focus-visible:ring-offset-background rounded-sm p-1 outline-none focus-visible:ring-2 focus-visible:ring-offset-2\"\n aria-label={formatString(strings.fileUpload.remove, { name: file.name })}\n >\n <X className=\"size-4\" aria-hidden />\n </button>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n});\nFileUpload.displayName = 'FileUpload';\n"],"names":["formatSize","bytes","s","formatString","matchesAccept","file","accept","rules","name","type","rule","FileUpload","forwardRef","multiple","maxSize","value","onChange","onReject","hint","disabled","className","ref","strings","useStrings","inputRef","useRef","inputId","useId","internal","setInternal","useState","over","setOver","dragDepth","files","update","useCallback","next","addFiles","incoming","accepted","tooLarge","wrongType","f","remove","idx","_","i","onDrop","cn","jsxs","jsx","Upload","FileIcon"],"mappings":"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"}
@@ -1,7 +1,15 @@
1
1
  import { HTMLAttributes } from 'react';
2
- import { ControllerProps, FieldPath, FieldValues } from 'react-hook-form';
2
+ import { ControllerProps, FieldPath, FieldValues, FormProviderProps } from 'react-hook-form';
3
3
  import * as LabelPrimitive from '@radix-ui/react-label';
4
- export declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>(props: import('react-hook-form').FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
4
+ /**
5
+ * Form context provider. Spread the `useForm()` return value into it, exactly
6
+ * like react-hook-form's `FormProvider` — plus one shared live region for
7
+ * validation announcements.
8
+ */
9
+ export declare function Form<TFieldValues extends FieldValues = FieldValues, TContext = unknown, TTransformedValues = TFieldValues>({ children, ...form }: FormProviderProps<TFieldValues, TContext, TTransformedValues>): import("react/jsx-runtime").JSX.Element;
10
+ export declare namespace Form {
11
+ var displayName: string;
12
+ }
5
13
  export declare function FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(props: ControllerProps<TFieldValues, TName>): import("react/jsx-runtime").JSX.Element;
6
14
  export declare function useFormField(): {
7
15
  invalid: boolean;
@@ -20,4 +28,9 @@ export declare const FormItem: import('react').ForwardRefExoticComponent<HTMLAtt
20
28
  export declare const FormLabel: import('react').ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & import('react').RefAttributes<HTMLLabelElement>, "ref"> & import('react').RefAttributes<HTMLLabelElement>>;
21
29
  export declare const FormControl: import('react').ForwardRefExoticComponent<Omit<import('@radix-ui/react-slot').SlotProps & import('react').RefAttributes<HTMLElement>, "ref"> & import('react').RefAttributes<HTMLElement>>;
22
30
  export declare const FormDescription: import('react').ForwardRefExoticComponent<HTMLAttributes<HTMLParagraphElement> & import('react').RefAttributes<HTMLParagraphElement>>;
31
+ /**
32
+ * Field-level validation message. Not a live region on its own — it is
33
+ * referenced from the control via `aria-describedby`, and the form's single
34
+ * live region announces changes.
35
+ */
23
36
  export declare const FormError: import('react').ForwardRefExoticComponent<HTMLAttributes<HTMLParagraphElement> & import('react').RefAttributes<HTMLParagraphElement>>;
@@ -1,105 +1,131 @@
1
1
  "use client";
2
- import { jsx as m } from "react/jsx-runtime";
3
- import { forwardRef as a, useContext as f, useEffect as I, useId as g, useState as b, createContext as F } from "react";
4
- import * as h from "@radix-ui/react-label";
5
- import { FormProvider as v, Controller as D, useFormContext as N } from "react-hook-form";
6
- import { Slot as y } from "@radix-ui/react-slot";
7
- import { cn as c } from "../../lib/utils.js";
8
- const k = v, p = F(null);
9
- function q(n) {
10
- return /* @__PURE__ */ m(p.Provider, { value: { name: n.name }, children: /* @__PURE__ */ m(D, { ...n }) });
2
+ import { jsxs as b, jsx as i } from "react/jsx-runtime";
3
+ import { forwardRef as a, useId as D, useContext as f, createContext as u, Children as N, isValidElement as h } from "react";
4
+ import * as C from "@radix-ui/react-label";
5
+ import { FormProvider as j, Controller as S, useFormContext as p } from "react-hook-form";
6
+ import { Slot as E } from "@radix-ui/react-slot";
7
+ import { cn as l } from "../../lib/utils.js";
8
+ function F(o, e = []) {
9
+ if (!o) return e;
10
+ for (const r of Object.values(o)) {
11
+ if (!r || typeof r != "object") continue;
12
+ const t = r.message;
13
+ typeof t == "string" && t ? e.push(t) : "type" in r || F(r, e);
14
+ }
15
+ return e;
16
+ }
17
+ function L() {
18
+ const { formState: o } = p(), e = F(o.errors);
19
+ return /* @__PURE__ */ i("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", children: e.join(". ") });
20
+ }
21
+ function M({ children: o, ...e }) {
22
+ return /* @__PURE__ */ b(j, { ...e, children: [
23
+ o,
24
+ /* @__PURE__ */ i(L, {})
25
+ ] });
26
+ }
27
+ M.displayName = "Form";
28
+ const y = u(null);
29
+ function q(o) {
30
+ return /* @__PURE__ */ i(y.Provider, { value: { name: o.name }, children: /* @__PURE__ */ i(S, { ...o }) });
11
31
  }
12
- const u = F(null);
32
+ const x = u(null);
13
33
  function d() {
14
- const n = f(p), o = f(u), { getFieldState: s, formState: e } = N();
15
- if (!n)
34
+ const o = f(y), e = f(x), { getFieldState: r, formState: t } = p();
35
+ if (!o)
16
36
  throw new Error("useFormField must be used inside <FormField>");
17
- const i = s(n.name, e), r = (o == null ? void 0 : o.id) ?? "";
37
+ const s = r(o.name, t), n = (e == null ? void 0 : e.id) ?? "";
18
38
  return {
19
- id: r,
20
- name: n.name,
21
- formItemId: `${r}-item`,
22
- formDescriptionId: `${r}-desc`,
23
- formMessageId: `${r}-error`,
24
- hasDescription: (o == null ? void 0 : o.hasDescription) ?? !1,
25
- ...i
39
+ id: n,
40
+ name: o.name,
41
+ formItemId: `${n}-item`,
42
+ formDescriptionId: `${n}-desc`,
43
+ formMessageId: `${n}-error`,
44
+ hasDescription: (e == null ? void 0 : e.hasDescription) ?? !1,
45
+ ...s
26
46
  };
27
47
  }
28
- const C = a(
29
- function({ className: o, ...s }, e) {
30
- const i = g(), [r, t] = b(!1);
31
- return /* @__PURE__ */ m(u.Provider, { value: { id: i, hasDescription: r, setHasDescription: t }, children: /* @__PURE__ */ m("div", { ref: e, className: c("flex flex-col gap-1.5", o), ...s }) });
48
+ function g(o) {
49
+ return N.toArray(o).some((e) => {
50
+ if (!h(e)) return !1;
51
+ if (e.type === I) return !0;
52
+ const r = e.props.children;
53
+ return (typeof e.type == "string" || typeof e.type == "symbol") && r ? g(r) : !1;
54
+ });
55
+ }
56
+ const P = a(
57
+ function({ className: e, children: r, ...t }, s) {
58
+ const n = D(), m = g(r);
59
+ return /* @__PURE__ */ i(x.Provider, { value: { id: n, hasDescription: m }, children: /* @__PURE__ */ i("div", { ref: s, className: l("flex flex-col gap-1.5", e), ...t, children: r }) });
32
60
  }
33
61
  );
34
- C.displayName = "FormItem";
35
- const P = a(function({ className: o, ...s }, e) {
36
- const { formItemId: i, error: r } = d();
37
- return /* @__PURE__ */ m(
38
- h.Root,
62
+ P.displayName = "FormItem";
63
+ const w = a(function({ className: e, ...r }, t) {
64
+ const { formItemId: s, error: n } = d();
65
+ return /* @__PURE__ */ i(
66
+ C.Root,
39
67
  {
40
- ref: e,
41
- htmlFor: i,
42
- className: c("text-foreground text-sm font-medium", r && "text-danger-text", o),
43
- ...s
68
+ ref: t,
69
+ htmlFor: s,
70
+ className: l("text-foreground text-sm font-medium", n && "text-danger-text", e),
71
+ ...r
44
72
  }
45
73
  );
46
74
  });
47
- P.displayName = "FormLabel";
48
- const S = a(
49
- function(o, s) {
50
- const { error: e, formItemId: i, formDescriptionId: r, formMessageId: t, hasDescription: l } = d(), x = [l ? r : null, e ? t : null].filter(Boolean).join(" ") || void 0;
51
- return /* @__PURE__ */ m(
52
- y,
75
+ w.displayName = "FormLabel";
76
+ const R = a(
77
+ function(e, r) {
78
+ const { error: t, formItemId: s, formDescriptionId: n, formMessageId: m, hasDescription: c } = d(), v = [c ? n : null, t ? m : null].filter(Boolean).join(" ") || void 0;
79
+ return /* @__PURE__ */ i(
80
+ E,
53
81
  {
54
- ref: s,
55
- id: i,
56
- "aria-describedby": x,
57
- "aria-invalid": !!e,
58
- ...e ? { tone: "error" } : {},
59
- ...o
82
+ ref: r,
83
+ id: s,
84
+ "aria-describedby": v,
85
+ "aria-invalid": !!t,
86
+ ...e
60
87
  }
61
88
  );
62
89
  }
63
90
  );
64
- S.displayName = "FormControl";
65
- const E = a(function({ className: o, ...s }, e) {
66
- const { formDescriptionId: i } = d(), r = f(u), t = r == null ? void 0 : r.setHasDescription;
67
- return I(() => (t == null || t(!0), () => t == null ? void 0 : t(!1)), [t]), /* @__PURE__ */ m(
91
+ R.displayName = "FormControl";
92
+ const I = a(function({ className: e, ...r }, t) {
93
+ const { formDescriptionId: s } = d();
94
+ return /* @__PURE__ */ i(
68
95
  "p",
69
96
  {
70
- ref: e,
71
- id: i,
72
- className: c("text-foreground-subtle text-xs", o),
73
- ...s
97
+ ref: t,
98
+ id: s,
99
+ className: l("text-foreground-subtle text-xs", e),
100
+ ...r
74
101
  }
75
102
  );
76
103
  });
77
- E.displayName = "FormDescription";
78
- const L = a(
79
- function({ className: o, children: s, ...e }, i) {
80
- const { error: r, formMessageId: t } = d(), l = r ? String((r == null ? void 0 : r.message) ?? "") : s;
81
- return l ? /* @__PURE__ */ m(
104
+ I.displayName = "FormDescription";
105
+ const $ = a(
106
+ function({ className: e, children: r, ...t }, s) {
107
+ const { error: n, formMessageId: m } = d(), c = n ? String((n == null ? void 0 : n.message) ?? "") : r;
108
+ return c ? /* @__PURE__ */ i(
82
109
  "p",
83
110
  {
84
- ref: i,
85
- id: t,
86
- role: "alert",
87
- className: c("text-danger-text text-xs", o),
88
- ...e,
89
- children: l
111
+ ref: s,
112
+ id: m,
113
+ className: l("text-danger-text text-xs", e),
114
+ ...t,
115
+ children: c
90
116
  }
91
117
  ) : null;
92
118
  }
93
119
  );
94
- L.displayName = "FormError";
120
+ $.displayName = "FormError";
95
121
  export {
96
- k as Form,
97
- S as FormControl,
98
- E as FormDescription,
99
- L as FormError,
122
+ M as Form,
123
+ R as FormControl,
124
+ I as FormDescription,
125
+ $ as FormError,
100
126
  q as FormField,
101
- C as FormItem,
102
- P as FormLabel,
127
+ P as FormItem,
128
+ w as FormLabel,
103
129
  d as useFormField
104
130
  };
105
131
  //# sourceMappingURL=Form.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Form.js","sources":["../../../src/components/ui/Form.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useId,\n useState,\n type HTMLAttributes,\n} from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldPath,\n type FieldValues,\n} from 'react-hook-form';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn } from '@/lib/utils';\n\n/* -----------------------------------------------------------------------------\n * react-hook-form bindings + accessible label / description / error wiring.\n *\n * Usage:\n * const form = useForm<Values>(…);\n * <Form {...form}>\n * <FormField\n * control={form.control}\n * name=\"email\"\n * render={({ field }) => (\n * <FormItem>\n * <FormLabel>Email</FormLabel>\n * <FormControl><Input type=\"email\" {...field} /></FormControl>\n * <FormDescription>We never share this.</FormDescription>\n * <FormError />\n * </FormItem>\n * )}\n * />\n * </Form>\n * --------------------------------------------------------------------------- */\n\nexport const Form = FormProvider;\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = createContext<FormFieldContextValue | null>(null);\n\nexport function FormField<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>(props: ControllerProps<TFieldValues, TName>) {\n return (\n <FormFieldContext.Provider value={{ name: props.name as string }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n}\n\ninterface FormItemContextValue {\n id: string;\n /** Whether a <FormDescription> is mounted — drives aria-describedby. */\n hasDescription: boolean;\n setHasDescription: (v: boolean) => void;\n}\nconst FormItemContext = createContext<FormItemContextValue | null>(null);\n\nexport function useFormField() {\n const fieldContext = useContext(FormFieldContext);\n const itemContext = useContext(FormItemContext);\n const { getFieldState, formState } = useFormContext();\n if (!fieldContext) {\n throw new Error('useFormField must be used inside <FormField>');\n }\n const fieldState = getFieldState(fieldContext.name, formState);\n const id = itemContext?.id ?? '';\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-item`,\n formDescriptionId: `${id}-desc`,\n formMessageId: `${id}-error`,\n hasDescription: itemContext?.hasDescription ?? false,\n ...fieldState,\n };\n}\n\nexport const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FormItem({ className, ...props }, ref) {\n const id = useId();\n const [hasDescription, setHasDescription] = useState(false);\n return (\n <FormItemContext.Provider value={{ id, hasDescription, setHasDescription }}>\n <div ref={ref} className={cn('flex flex-col gap-1.5', className)} {...props} />\n </FormItemContext.Provider>\n );\n },\n);\nFormItem.displayName = 'FormItem';\n\nexport const FormLabel = forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(function FormLabel({ className, ...props }, ref) {\n const { formItemId, error } = useFormField();\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={formItemId}\n className={cn('text-foreground text-sm font-medium', error && 'text-danger-text', className)}\n {...props}\n />\n );\n});\nFormLabel.displayName = 'FormLabel';\n\nexport const FormControl = forwardRef<HTMLElement, React.ComponentPropsWithoutRef<typeof Slot>>(\n function FormControl(props, ref) {\n const { error, formItemId, formDescriptionId, formMessageId, hasDescription } = useFormField();\n // Only reference ids that actually render: the description when mounted,\n // the message only while there is an error (FormError returns null otherwise).\n const describedBy =\n [hasDescription ? formDescriptionId : null, error ? formMessageId : null]\n .filter(Boolean)\n .join(' ') || undefined;\n // Input-style children read `tone=\"error\"` for the red border; plain\n // elements ignore the unknown prop via Slot merging only when they accept it,\n // so it is only set while there is an error.\n const toneProps = error ? { tone: 'error' as const } : {};\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={describedBy}\n aria-invalid={!!error}\n {...toneProps}\n {...props}\n />\n );\n },\n);\nFormControl.displayName = 'FormControl';\n\nexport const FormDescription = forwardRef<\n HTMLParagraphElement,\n HTMLAttributes<HTMLParagraphElement>\n>(function FormDescription({ className, ...props }, ref) {\n const { formDescriptionId } = useFormField();\n const itemContext = useContext(FormItemContext);\n const setHasDescription = itemContext?.setHasDescription;\n useEffect(() => {\n setHasDescription?.(true);\n return () => setHasDescription?.(false);\n }, [setHasDescription]);\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn('text-foreground-subtle text-xs', className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = 'FormDescription';\n\nexport const FormError = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n function FormError({ className, children, ...props }, ref) {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? '') : children;\n if (!body) return null;\n return (\n <p\n ref={ref}\n id={formMessageId}\n role=\"alert\"\n className={cn('text-danger-text text-xs', className)}\n {...props}\n >\n {body}\n </p>\n );\n },\n);\nFormError.displayName = 'FormError';\n"],"names":["Form","FormProvider","FormFieldContext","createContext","FormField","props","jsx","Controller","FormItemContext","useFormField","fieldContext","useContext","itemContext","getFieldState","formState","useFormContext","fieldState","id","FormItem","forwardRef","className","ref","useId","hasDescription","setHasDescription","useState","cn","FormLabel","formItemId","error","LabelPrimitive","FormControl","formDescriptionId","formMessageId","describedBy","Slot","FormDescription","useEffect","FormError","children","body"],"mappings":"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"}
1
+ {"version":3,"file":"Form.js","sources":["../../../src/components/ui/Form.tsx"],"sourcesContent":["'use client';\n\nimport {\n Children,\n createContext,\n forwardRef,\n isValidElement,\n useContext,\n useId,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport {\n Controller,\n FormProvider,\n useFormContext,\n type ControllerProps,\n type FieldErrors,\n type FieldPath,\n type FieldValues,\n type FormProviderProps,\n} from 'react-hook-form';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cn } from '@/lib/utils';\n\n/* -----------------------------------------------------------------------------\n * react-hook-form bindings + accessible label / description / error wiring.\n *\n * Usage:\n * const form = useForm<Values>(…);\n * <Form {...form}>\n * <FormField\n * control={form.control}\n * name=\"email\"\n * render={({ field }) => (\n * <FormItem>\n * <FormLabel>Email</FormLabel>\n * <FormControl><Input type=\"email\" {...field} /></FormControl>\n * <FormDescription>We never share this.</FormDescription>\n * <FormError />\n * </FormItem>\n * )}\n * />\n * </Form>\n *\n * Ids: `<item>-item` (control), `<item>-desc`, `<item>-error` — the same\n * `-desc` / `-error` suffixes the standalone fields use (useFieldIds).\n * Invalid styling is driven purely by `aria-invalid` on the control; no\n * `aria-invalid` is set on the control; style from `aria-invalid:` (no `tone` injection).\n * --------------------------------------------------------------------------- */\n\nfunction collectMessages(errors: FieldErrors | undefined, out: string[] = []): string[] {\n if (!errors) return out;\n for (const value of Object.values(errors)) {\n if (!value || typeof value !== 'object') continue;\n const message = (value as { message?: unknown }).message;\n if (typeof message === 'string' && message) out.push(message);\n else if (!('type' in value)) collectMessages(value as FieldErrors, out);\n }\n return out;\n}\n\n/**\n * Single polite live region per form. Field-level `FormError` elements are\n * plain text wired via `aria-describedby`; this region announces the current\n * error set once instead of one `role=\"alert\"` per field firing in parallel.\n */\nfunction FormLiveRegion() {\n const { formState } = useFormContext();\n const messages = collectMessages(formState.errors);\n return (\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {messages.join('. ')}\n </div>\n );\n}\n\n/**\n * Form context provider. Spread the `useForm()` return value into it, exactly\n * like react-hook-form's `FormProvider` — plus one shared live region for\n * validation announcements.\n */\nexport function Form<\n TFieldValues extends FieldValues = FieldValues,\n TContext = unknown,\n TTransformedValues = TFieldValues,\n>({ children, ...form }: FormProviderProps<TFieldValues, TContext, TTransformedValues>) {\n return (\n <FormProvider {...form}>\n {children}\n <FormLiveRegion />\n </FormProvider>\n );\n}\nForm.displayName = 'Form';\n\ninterface FormFieldContextValue<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n> {\n name: TName;\n}\n\nconst FormFieldContext = createContext<FormFieldContextValue | null>(null);\n\nexport function FormField<\n TFieldValues extends FieldValues = FieldValues,\n TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,\n>(props: ControllerProps<TFieldValues, TName>) {\n return (\n <FormFieldContext.Provider value={{ name: props.name as string }}>\n <Controller {...props} />\n </FormFieldContext.Provider>\n );\n}\n\ninterface FormItemContextValue {\n id: string;\n /** Whether a <FormDescription> is rendered — drives aria-describedby. */\n hasDescription: boolean;\n}\nconst FormItemContext = createContext<FormItemContextValue | null>(null);\n\nexport function useFormField() {\n const fieldContext = useContext(FormFieldContext);\n const itemContext = useContext(FormItemContext);\n const { getFieldState, formState } = useFormContext();\n if (!fieldContext) {\n throw new Error('useFormField must be used inside <FormField>');\n }\n const fieldState = getFieldState(fieldContext.name, formState);\n const id = itemContext?.id ?? '';\n return {\n id,\n name: fieldContext.name,\n formItemId: `${id}-item`,\n formDescriptionId: `${id}-desc`,\n formMessageId: `${id}-error`,\n hasDescription: itemContext?.hasDescription ?? false,\n ...fieldState,\n };\n}\n\n/** Synchronous scan for a <FormDescription> among the item's children (through fragments / host elements). */\nfunction containsDescription(children: ReactNode): boolean {\n return Children.toArray(children).some((child) => {\n if (!isValidElement(child)) return false;\n if (child.type === FormDescription) return true;\n const nested = (child.props as { children?: ReactNode }).children;\n // Only descend into host elements / fragments — custom components own their subtree.\n return (typeof child.type === 'string' || typeof child.type === 'symbol') && nested\n ? containsDescription(nested)\n : false;\n });\n}\n\nexport const FormItem = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(\n function FormItem({ className, children, ...props }, ref) {\n const id = useId();\n // Derived during render (no effect → no second pass / hydration flip).\n const hasDescription = containsDescription(children);\n return (\n <FormItemContext.Provider value={{ id, hasDescription }}>\n <div ref={ref} className={cn('flex flex-col gap-1.5', className)} {...props}>\n {children}\n </div>\n </FormItemContext.Provider>\n );\n },\n);\nFormItem.displayName = 'FormItem';\n\nexport const FormLabel = forwardRef<\n React.ElementRef<typeof LabelPrimitive.Root>,\n React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>\n>(function FormLabel({ className, ...props }, ref) {\n const { formItemId, error } = useFormField();\n return (\n <LabelPrimitive.Root\n ref={ref}\n htmlFor={formItemId}\n className={cn('text-foreground text-sm font-medium', error && 'text-danger-text', className)}\n {...props}\n />\n );\n});\nFormLabel.displayName = 'FormLabel';\n\nexport const FormControl = forwardRef<HTMLElement, React.ComponentPropsWithoutRef<typeof Slot>>(\n function FormControl(props, ref) {\n const { error, formItemId, formDescriptionId, formMessageId, hasDescription } = useFormField();\n // Only reference ids that actually render: the description when mounted,\n // the message only while there is an error (FormError returns null otherwise).\n const describedBy =\n [hasDescription ? formDescriptionId : null, error ? formMessageId : null]\n .filter(Boolean)\n .join(' ') || undefined;\n return (\n <Slot\n ref={ref}\n id={formItemId}\n aria-describedby={describedBy}\n aria-invalid={!!error}\n {...props}\n />\n );\n },\n);\nFormControl.displayName = 'FormControl';\n\nexport const FormDescription = forwardRef<\n HTMLParagraphElement,\n HTMLAttributes<HTMLParagraphElement>\n>(function FormDescription({ className, ...props }, ref) {\n const { formDescriptionId } = useFormField();\n return (\n <p\n ref={ref}\n id={formDescriptionId}\n className={cn('text-foreground-subtle text-xs', className)}\n {...props}\n />\n );\n});\nFormDescription.displayName = 'FormDescription';\n\n/**\n * Field-level validation message. Not a live region on its own — it is\n * referenced from the control via `aria-describedby`, and the form's single\n * live region announces changes.\n */\nexport const FormError = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n function FormError({ className, children, ...props }, ref) {\n const { error, formMessageId } = useFormField();\n const body = error ? String(error?.message ?? '') : children;\n if (!body) return null;\n return (\n <p\n ref={ref}\n id={formMessageId}\n className={cn('text-danger-text text-xs', className)}\n {...props}\n >\n {body}\n </p>\n );\n },\n);\nFormError.displayName = 'FormError';\n"],"names":["collectMessages","errors","out","value","message","FormLiveRegion","formState","useFormContext","messages","jsx","Form","children","form","jsxs","FormProvider","FormFieldContext","createContext","FormField","props","Controller","FormItemContext","useFormField","fieldContext","useContext","itemContext","getFieldState","fieldState","id","containsDescription","Children","child","isValidElement","FormDescription","nested","FormItem","forwardRef","className","ref","useId","hasDescription","cn","FormLabel","formItemId","error","LabelPrimitive","FormControl","formDescriptionId","formMessageId","describedBy","Slot","FormError","body"],"mappings":"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"}
@@ -31,4 +31,4 @@ export interface IconProps extends Omit<LucideProps, 'ref'> {
31
31
  * @dont Reach for this in hot lists with hundreds of distinct icons on first
32
32
  * paint — statically imported icons from `Icons` skip the lazy hop.
33
33
  */
34
- export declare function Icon({ name, fallback, ...props }: IconProps): import("react/jsx-runtime").JSX.Element;
34
+ export declare function Icon({ name, fallback, ...props }: IconProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,26 +1,32 @@
1
1
  "use client";
2
- import { jsx as n } from "react/jsx-runtime";
3
- import { useMemo as a, Suspense as l, lazy as d } from "react";
4
- import i from "lucide-react/dynamicIconImports";
5
- const p = Object.keys(i), o = /* @__PURE__ */ new Map();
6
- function u(e) {
7
- const t = o.get(e);
2
+ import { jsx as o } from "react/jsx-runtime";
3
+ import { useMemo as l, Suspense as u, lazy as d } from "react";
4
+ import s from "lucide-react/dynamicIconImports";
5
+ const y = Object.keys(s), i = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Set();
6
+ function f(e) {
7
+ const t = i.get(e);
8
8
  if (t) return t;
9
- const c = d(i[e]);
10
- return o.set(e, c), c;
9
+ const n = s[e];
10
+ if (typeof n != "function")
11
+ return process.env.NODE_ENV !== "production" && !r.has(e) && (r.add(e), console.warn(
12
+ `[@craftzbay/ui] <Icon name="${e}"> is not a lucide icon; rendering nothing.`
13
+ )), null;
14
+ const c = d(n);
15
+ return i.set(e, c), c;
11
16
  }
12
- function y({ name: e, fallback: t, ...c }) {
13
- const s = a(() => u(e), [e]), r = t ?? /* @__PURE__ */ n(
17
+ function I({ name: e, fallback: t, ...n }) {
18
+ const c = l(() => f(e), [e]), a = t ?? /* @__PURE__ */ o(
14
19
  "span",
15
20
  {
16
21
  "aria-hidden": !0,
17
- style: { display: "inline-block", width: c.size ?? "1em", height: c.size ?? "1em" }
22
+ className: n.className,
23
+ style: { display: "inline-block", width: n.size ?? "1em", height: n.size ?? "1em" }
18
24
  }
19
25
  );
20
- return /* @__PURE__ */ n(l, { fallback: r, children: /* @__PURE__ */ n(s, { ...c }) });
26
+ return c ? /* @__PURE__ */ o(u, { fallback: a, children: /* @__PURE__ */ o(c, { ...n }) }) : null;
21
27
  }
22
28
  export {
23
- y as Icon,
24
- p as iconNames
29
+ I as Icon,
30
+ y as iconNames
25
31
  };
26
32
  //# sourceMappingURL=Icon.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Icon.js","sources":["../../../src/components/ui/Icon.tsx"],"sourcesContent":["'use client';\n\nimport {\n lazy,\n Suspense,\n useMemo,\n type LazyExoticComponent,\n type ReactNode,\n} from 'react';\nimport dynamicIconImports from 'lucide-react/dynamicIconImports';\nimport type { LucideIcon, LucideProps } from 'lucide-react';\n\n/** Every lucide icon name, kebab-case — e.g. `calendar`, `chevrons-up-down`. */\nexport type IconName = keyof typeof dynamicIconImports;\n\n/** All available icon names (useful for pickers and docs). */\nexport const iconNames = Object.keys(dynamicIconImports) as IconName[];\n\n// lazy() must return a stable component per name or React remounts (and\n// re-suspends) on every render.\nconst cache = new Map<IconName, LazyExoticComponent<LucideIcon>>();\n\nfunction getIcon(name: IconName): LazyExoticComponent<LucideIcon> {\n const cached = cache.get(name);\n if (cached) return cached;\n const created = lazy(dynamicIconImports[name]);\n cache.set(name, created);\n return created;\n}\n\nexport interface IconProps extends Omit<LucideProps, 'ref'> {\n /** Lucide icon name in kebab-case, e.g. `name=\"calendar\"`. Autocompletes. */\n name: IconName;\n /**\n * Shown while the icon chunk loads (first use only — icons are cached).\n * Defaults to an invisible square so layout doesn't shift.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Name-addressed lucide icon. Use when the icon is dynamic (data-driven\n * nav items, user-configurable dashboards) or when importing components for\n * every icon is friction.\n *\n * Each icon is code-split and loaded on first use, so referencing the full\n * lucide set costs nothing up front — `<Icon name=\"rocket\" />` ships only\n * the rocket.\n *\n * @example\n * <Icon name=\"calendar\" className=\"size-4\" />\n * <Icon name=\"chevrons-up-down\" size={16} strokeWidth={1.5} />\n *\n * @do Use `size-4` (16px) inline with text, `size-5` (20px) inside buttons.\n * @dont Reach for this in hot lists with hundreds of distinct icons on first\n * paint — statically imported icons from `Icons` skip the lazy hop.\n */\nexport function Icon({ name, fallback, ...props }: IconProps) {\n const LucideIcon = useMemo(() => getIcon(name), [name]);\n\n const placeholder = fallback ?? (\n <span\n aria-hidden\n style={{ display: 'inline-block', width: props.size ?? '1em', height: props.size ?? '1em' }}\n />\n );\n\n return (\n <Suspense fallback={placeholder}>\n <LucideIcon {...props} />\n </Suspense>\n );\n}\n"],"names":["iconNames","dynamicIconImports","cache","getIcon","name","cached","created","lazy","Icon","fallback","props","LucideIcon","useMemo","placeholder","jsx","Suspense"],"mappings":";;;;AAgBO,MAAMA,IAAY,OAAO,KAAKC,CAAkB,GAIjDC,wBAAY,IAAA;AAElB,SAASC,EAAQC,GAAiD;AAChE,QAAMC,IAASH,EAAM,IAAIE,CAAI;AAC7B,MAAIC,EAAQ,QAAOA;AACnB,QAAMC,IAAUC,EAAKN,EAAmBG,CAAI,CAAC;AAC7C,SAAAF,EAAM,IAAIE,GAAME,CAAO,GAChBA;AACT;AA6BO,SAASE,EAAK,EAAE,MAAAJ,GAAM,UAAAK,GAAU,GAAGC,KAAoB;AAC5D,QAAMC,IAAaC,EAAQ,MAAMT,EAAQC,CAAI,GAAG,CAACA,CAAI,CAAC,GAEhDS,IAAcJ,KAClB,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAW;AAAA,MACX,OAAO,EAAE,SAAS,gBAAgB,OAAOJ,EAAM,QAAQ,OAAO,QAAQA,EAAM,QAAQ,MAAA;AAAA,IAAM;AAAA,EAAA;AAI9F,SACE,gBAAAI,EAACC,KAAS,UAAUF,GAClB,4BAACF,GAAA,EAAY,GAAGD,GAAO,EAAA,CACzB;AAEJ;"}
1
+ {"version":3,"file":"Icon.js","sources":["../../../src/components/ui/Icon.tsx"],"sourcesContent":["'use client';\n\nimport { lazy, Suspense, useMemo, type LazyExoticComponent, type ReactNode } from 'react';\nimport dynamicIconImports from 'lucide-react/dynamicIconImports';\nimport type { LucideIcon, LucideProps } from 'lucide-react';\n\n/** Every lucide icon name, kebab-case — e.g. `calendar`, `chevrons-up-down`. */\nexport type IconName = keyof typeof dynamicIconImports;\n\n/** All available icon names (useful for pickers and docs). */\nexport const iconNames = Object.keys(dynamicIconImports) as IconName[];\n\n// lazy() must return a stable component per name or React remounts (and\n// re-suspends) on every render.\nconst cache = new Map<IconName, LazyExoticComponent<LucideIcon>>();\nconst warned = new Set<string>();\n\nfunction getIcon(name: IconName): LazyExoticComponent<LucideIcon> | null {\n const cached = cache.get(name);\n if (cached) return cached;\n const loader = dynamicIconImports[name];\n if (typeof loader !== 'function') {\n // Unknown name (data-driven input) → render nothing, warn once in dev.\n if (process.env.NODE_ENV !== 'production' && !warned.has(name)) {\n warned.add(name);\n console.warn(\n `[@craftzbay/ui] <Icon name=\"${name}\"> is not a lucide icon; rendering nothing.`,\n );\n }\n return null;\n }\n const created = lazy(loader);\n cache.set(name, created);\n return created;\n}\n\nexport interface IconProps extends Omit<LucideProps, 'ref'> {\n /** Lucide icon name in kebab-case, e.g. `name=\"calendar\"`. Autocompletes. */\n name: IconName;\n /**\n * Shown while the icon chunk loads (first use only — icons are cached).\n * Defaults to an invisible square so layout doesn't shift.\n */\n fallback?: ReactNode;\n}\n\n/**\n * Name-addressed lucide icon. Use when the icon is dynamic (data-driven\n * nav items, user-configurable dashboards) or when importing components for\n * every icon is friction.\n *\n * Each icon is code-split and loaded on first use, so referencing the full\n * lucide set costs nothing up front — `<Icon name=\"rocket\" />` ships only\n * the rocket.\n *\n * @example\n * <Icon name=\"calendar\" className=\"size-4\" />\n * <Icon name=\"chevrons-up-down\" size={16} strokeWidth={1.5} />\n *\n * @do Use `size-4` (16px) inline with text, `size-5` (20px) inside buttons.\n * @dont Reach for this in hot lists with hundreds of distinct icons on first\n * paint — statically imported icons from `Icons` skip the lazy hop.\n */\nexport function Icon({ name, fallback, ...props }: IconProps) {\n const LucideIcon = useMemo(() => getIcon(name), [name]);\n\n const placeholder = fallback ?? (\n <span\n aria-hidden\n className={props.className}\n style={{ display: 'inline-block', width: props.size ?? '1em', height: props.size ?? '1em' }}\n />\n );\n\n if (!LucideIcon) return null;\n\n return (\n <Suspense fallback={placeholder}>\n <LucideIcon {...props} />\n </Suspense>\n );\n}\n"],"names":["iconNames","dynamicIconImports","cache","warned","getIcon","name","cached","loader","created","lazy","Icon","fallback","props","LucideIcon","useMemo","placeholder","jsx","Suspense"],"mappings":";;;;AAUO,MAAMA,IAAY,OAAO,KAAKC,CAAkB,GAIjDC,wBAAY,IAAA,GACZC,wBAAa,IAAA;AAEnB,SAASC,EAAQC,GAAwD;AACvE,QAAMC,IAASJ,EAAM,IAAIG,CAAI;AAC7B,MAAIC,EAAQ,QAAOA;AACnB,QAAMC,IAASN,EAAmBI,CAAI;AACtC,MAAI,OAAOE,KAAW;AAEpB,WAAI,QAAQ,IAAI,aAAa,gBAAgB,CAACJ,EAAO,IAAIE,CAAI,MAC3DF,EAAO,IAAIE,CAAI,GACf,QAAQ;AAAA,MACN,+BAA+BA,CAAI;AAAA,IAAA,IAGhC;AAET,QAAMG,IAAUC,EAAKF,CAAM;AAC3B,SAAAL,EAAM,IAAIG,GAAMG,CAAO,GAChBA;AACT;AA6BO,SAASE,EAAK,EAAE,MAAAL,GAAM,UAAAM,GAAU,GAAGC,KAAoB;AAC5D,QAAMC,IAAaC,EAAQ,MAAMV,EAAQC,CAAI,GAAG,CAACA,CAAI,CAAC,GAEhDU,IAAcJ,KAClB,gBAAAK;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,eAAW;AAAA,MACX,WAAWJ,EAAM;AAAA,MACjB,OAAO,EAAE,SAAS,gBAAgB,OAAOA,EAAM,QAAQ,OAAO,QAAQA,EAAM,QAAQ,MAAA;AAAA,IAAM;AAAA,EAAA;AAI9F,SAAKC,IAGH,gBAAAG,EAACC,KAAS,UAAUF,GAClB,4BAACF,GAAA,EAAY,GAAGD,GAAO,EAAA,CACzB,IALsB;AAO1B;"}
@@ -2,7 +2,7 @@ import { ButtonHTMLAttributes, ReactNode } from 'react';
2
2
  import { VariantProps } from '../../lib/cva.js';
3
3
  declare const iconButton: (props?: ({
4
4
  variant?: "outline" | "primary" | "secondary" | "ghost" | "destructive" | null | undefined;
5
- size?: "md" | "sm" | "lg" | null | undefined;
5
+ size?: "md" | "sm" | "lg" | "xl" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
7
  export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof iconButton> {
8
8
  /**
@@ -12,7 +12,7 @@ export interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>
12
12
  'aria-label': string;
13
13
  /** The Lucide icon to render. */
14
14
  icon: ReactNode;
15
- /** Show a spinner instead of the icon. */
15
+ /** Show a spinner instead of the icon; blocks interaction but keeps focus (`aria-disabled`). */
16
16
  loading?: boolean;
17
17
  }
18
18
  /**
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { jsx as r } from "react/jsx-runtime";
3
- import { forwardRef as u } from "react";
2
+ import { jsx as t } from "react/jsx-runtime";
3
+ import { forwardRef as g } from "react";
4
4
  import { Loader2 as b } from "../../icons/index.js";
5
- import { cn as g } from "../../lib/utils.js";
6
- import { cva as v } from "class-variance-authority";
7
- const f = v(
5
+ import { cn as f } from "../../lib/utils.js";
6
+ import { cva as m } from "class-variance-authority";
7
+ const l = m(
8
8
  [
9
9
  "inline-flex items-center justify-center shrink-0",
10
10
  "rounded-md",
@@ -13,21 +13,24 @@ const f = v(
13
13
  "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
14
14
  "focus-visible:ring-offset-background",
15
15
  "disabled:pointer-events-none disabled:opacity-50",
16
+ "aria-busy:pointer-events-none",
16
17
  "[&_svg]:pointer-events-none"
17
18
  ],
18
19
  {
19
20
  variants: {
20
21
  variant: {
21
22
  primary: "bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-active",
22
- secondary: "bg-background-muted text-foreground border border-border hover:bg-surface-hover active:bg-surface-active",
23
- outline: "border border-border bg-transparent text-foreground hover:bg-background-muted",
23
+ secondary: "bg-background-muted text-foreground border border-border-input hover:bg-surface-hover active:bg-surface-active",
24
+ outline: "border border-border-input bg-transparent text-foreground hover:bg-background-muted",
24
25
  ghost: "bg-transparent text-foreground-muted hover:bg-background-muted hover:text-foreground",
25
26
  destructive: "bg-danger text-on-danger hover:bg-danger-hover active:bg-danger-active"
26
27
  },
27
28
  size: {
28
- sm: "h-7 w-7 [&_svg]:size-3.5",
29
- md: "h-8 w-8 [&_svg]:size-4",
30
- lg: "h-10 w-10 [&_svg]:size-5"
29
+ // Matches Button heights: sm 32 / md 36 / lg 40 / xl 44 (touch / marketing).
30
+ sm: "h-8 w-8 [&_svg]:size-4",
31
+ md: "h-9 w-9 [&_svg]:size-4",
32
+ lg: "h-10 w-10 [&_svg]:size-5",
33
+ xl: "h-11 w-11 [&_svg]:size-5"
31
34
  }
32
35
  },
33
36
  defaultVariants: {
@@ -35,22 +38,27 @@ const f = v(
35
38
  size: "md"
36
39
  }
37
40
  }
38
- ), m = u(function({ className: t, variant: o, size: n, icon: i, loading: e, disabled: a, type: s = "button", ...d }, c) {
39
- return /* @__PURE__ */ r(
41
+ ), p = g(function({ className: o, variant: n, size: i, icon: a, loading: e, disabled: s, type: c = "button", onClick: d, ...u }, v) {
42
+ return /* @__PURE__ */ t(
40
43
  "button",
41
44
  {
42
- ref: c,
43
- type: s,
45
+ ref: v,
46
+ type: c,
44
47
  "aria-busy": e || void 0,
45
- disabled: a || e,
46
- className: g(f({ variant: o, size: n }), t),
47
- ...d,
48
- children: e ? /* @__PURE__ */ r(b, { className: "animate-spin", "aria-hidden": "true" }) : i
48
+ "aria-disabled": e || void 0,
49
+ disabled: s,
50
+ className: f(l({ variant: n, size: i }), o),
51
+ onClick: e ? h : d,
52
+ ...u,
53
+ children: e ? /* @__PURE__ */ t(b, { className: "animate-spin", "aria-hidden": "true" }) : a
49
54
  }
50
55
  );
51
56
  });
52
- m.displayName = "IconButton";
57
+ p.displayName = "IconButton";
58
+ function h(r) {
59
+ r.preventDefault(), r.stopPropagation();
60
+ }
53
61
  export {
54
- m as IconButton
62
+ p as IconButton
55
63
  };
56
64
  //# sourceMappingURL=IconButton.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"IconButton.js","sources":["../../../src/components/ui/IconButton.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';\nimport { Loader2 } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { cva, type VariantProps } from '@/lib/cva';\n\nconst iconButton = cva(\n [\n 'inline-flex items-center justify-center shrink-0',\n 'rounded-md',\n 'transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]',\n 'outline-none',\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n '[&_svg]:pointer-events-none',\n ],\n {\n variants: {\n variant: {\n primary: 'bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-active',\n secondary:\n 'bg-background-muted text-foreground border border-border hover:bg-surface-hover active:bg-surface-active',\n outline:\n 'border border-border bg-transparent text-foreground hover:bg-background-muted',\n ghost: 'bg-transparent text-foreground-muted hover:bg-background-muted hover:text-foreground',\n destructive: 'bg-danger text-on-danger hover:bg-danger-hover active:bg-danger-active',\n },\n size: {\n sm: 'h-7 w-7 [&_svg]:size-3.5',\n md: 'h-8 w-8 [&_svg]:size-4',\n lg: 'h-10 w-10 [&_svg]:size-5',\n },\n },\n defaultVariants: {\n variant: 'ghost',\n size: 'md',\n },\n },\n);\n\nexport interface IconButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>,\n VariantProps<typeof iconButton> {\n /**\n * Accessible label for screen readers. **Required** — an icon-only button\n * with no label is invisible to assistive tech.\n */\n 'aria-label': string;\n /** The Lucide icon to render. */\n icon: ReactNode;\n /** Show a spinner instead of the icon. */\n loading?: boolean;\n}\n\n/**\n * Square, icon-only button. Default variant is `ghost` because most\n * IconButtons sit in dense toolbars or tables. Always pass `aria-label`.\n *\n * @example Toolbar action\n * <IconButton aria-label=\"Edit row\" icon={<Edit2 />} onClick={onEdit} />\n *\n * @example Dismissible chip\n * <IconButton aria-label=\"Remove tag\" size=\"sm\" icon={<X />} />\n *\n * @do Use `ghost` for in-row actions; `outline` when next to a label-less\n * primary button you want users to consider as an alternative.\n * @dont Use IconButton when the action's meaning isn't universally\n * recognised. If users need to learn the icon, ship a Button with text.\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n { className, variant, size, icon, loading, disabled, type = 'button', ...props },\n ref,\n) {\n const isDisabled = disabled || loading;\n return (\n <button\n ref={ref}\n type={type}\n aria-busy={loading || undefined}\n disabled={isDisabled}\n className={cn(iconButton({ variant, size }), className)}\n {...props}\n >\n {loading ? <Loader2 className=\"animate-spin\" aria-hidden=\"true\" /> : icon}\n </button>\n );\n});\n\nIconButton.displayName = 'IconButton';\n"],"names":["iconButton","cva","IconButton","forwardRef","className","variant","size","icon","loading","disabled","type","props","ref","jsx","cn","Loader2"],"mappings":";;;;;;AAOA,MAAMA,IAAaC;AAAA,EACjB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,WACE;AAAA,QACF,SACE;AAAA,QACF,OAAO;AAAA,QACP,aAAa;AAAA,MAAA;AAAA,MAEf,MAAM;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MAAA;AAAA,IACN;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ,GA+BaC,IAAaC,EAA+C,SACvE,EAAE,WAAAC,GAAW,SAAAC,GAAS,MAAAC,GAAM,MAAAC,GAAM,SAAAC,GAAS,UAAAC,GAAU,MAAAC,IAAO,UAAU,GAAGC,EAAA,GACzEC,GACA;AAEA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,MAAAF;AAAA,MACA,aAAWF,KAAW;AAAA,MACtB,UANeC,KAAYD;AAAA,MAO3B,WAAWM,EAAGd,EAAW,EAAE,SAAAK,GAAS,MAAAC,EAAA,CAAM,GAAGF,CAAS;AAAA,MACrD,GAAGO;AAAA,MAEH,cAAU,gBAAAE,EAACE,GAAA,EAAQ,WAAU,gBAAe,eAAY,QAAO,IAAKR;AAAA,IAAA;AAAA,EAAA;AAG3E,CAAC;AAEDL,EAAW,cAAc;"}
1
+ {"version":3,"file":"IconButton.js","sources":["../../../src/components/ui/IconButton.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode, type SyntheticEvent } from 'react';\nimport { Loader2 } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { cva, type VariantProps } from '@/lib/cva';\n\nconst iconButton = cva(\n [\n 'inline-flex items-center justify-center shrink-0',\n 'rounded-md',\n 'transition-colors duration-[var(--duration-fast)] ease-[var(--ease-out)]',\n 'outline-none',\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'focus-visible:ring-offset-background',\n 'disabled:pointer-events-none disabled:opacity-50',\n 'aria-busy:pointer-events-none',\n '[&_svg]:pointer-events-none',\n ],\n {\n variants: {\n variant: {\n primary: 'bg-accent text-on-accent hover:bg-accent-hover active:bg-accent-active',\n secondary:\n 'bg-background-muted text-foreground border border-border-input hover:bg-surface-hover active:bg-surface-active',\n outline:\n 'border border-border-input bg-transparent text-foreground hover:bg-background-muted',\n ghost:\n 'bg-transparent text-foreground-muted hover:bg-background-muted hover:text-foreground',\n destructive: 'bg-danger text-on-danger hover:bg-danger-hover active:bg-danger-active',\n },\n size: {\n // Matches Button heights: sm 32 / md 36 / lg 40 / xl 44 (touch / marketing).\n sm: 'h-8 w-8 [&_svg]:size-4',\n md: 'h-9 w-9 [&_svg]:size-4',\n lg: 'h-10 w-10 [&_svg]:size-5',\n xl: 'h-11 w-11 [&_svg]:size-5',\n },\n },\n defaultVariants: {\n variant: 'ghost',\n size: 'md',\n },\n },\n);\n\nexport interface IconButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof iconButton> {\n /**\n * Accessible label for screen readers. **Required** — an icon-only button\n * with no label is invisible to assistive tech.\n */\n 'aria-label': string;\n /** The Lucide icon to render. */\n icon: ReactNode;\n /** Show a spinner instead of the icon; blocks interaction but keeps focus (`aria-disabled`). */\n loading?: boolean;\n}\n\n/**\n * Square, icon-only button. Default variant is `ghost` because most\n * IconButtons sit in dense toolbars or tables. Always pass `aria-label`.\n *\n * @example Toolbar action\n * <IconButton aria-label=\"Edit row\" icon={<Edit2 />} onClick={onEdit} />\n *\n * @example Dismissible chip\n * <IconButton aria-label=\"Remove tag\" size=\"sm\" icon={<X />} />\n *\n * @do Use `ghost` for in-row actions; `outline` when next to a label-less\n * primary button you want users to consider as an alternative.\n * @dont Use IconButton when the action's meaning isn't universally\n * recognised. If users need to learn the icon, ship a Button with text.\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n { className, variant, size, icon, loading, disabled, type = 'button', onClick, ...props },\n ref,\n) {\n return (\n <button\n ref={ref}\n type={type}\n aria-busy={loading || undefined}\n aria-disabled={loading || undefined}\n disabled={disabled}\n className={cn(iconButton({ variant, size }), className)}\n onClick={loading ? blockEvent : onClick}\n {...props}\n >\n {loading ? <Loader2 className=\"animate-spin\" aria-hidden=\"true\" /> : icon}\n </button>\n );\n});\n\nIconButton.displayName = 'IconButton';\n\nfunction blockEvent(e: SyntheticEvent) {\n e.preventDefault();\n e.stopPropagation();\n}\n"],"names":["iconButton","cva","IconButton","forwardRef","className","variant","size","icon","loading","disabled","type","onClick","props","ref","jsx","cn","blockEvent","Loader2","e"],"mappings":";;;;;;AAOA,MAAMA,IAAaC;AAAA,EACjB;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,SAAS;AAAA,QACT,WACE;AAAA,QACF,SACE;AAAA,QACF,OACE;AAAA,QACF,aAAa;AAAA,MAAA;AAAA,MAEf,MAAM;AAAA;AAAA,QAEJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,QACJ,IAAI;AAAA,MAAA;AAAA,IACN;AAAA,IAEF,iBAAiB;AAAA,MACf,SAAS;AAAA,MACT,MAAM;AAAA,IAAA;AAAA,EACR;AAEJ,GA8BaC,IAAaC,EAA+C,SACvE,EAAE,WAAAC,GAAW,SAAAC,GAAS,MAAAC,GAAM,MAAAC,GAAM,SAAAC,GAAS,UAAAC,GAAU,MAAAC,IAAO,UAAU,SAAAC,GAAS,GAAGC,EAAA,GAClFC,GACA;AACA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,MAAAH;AAAA,MACA,aAAWF,KAAW;AAAA,MACtB,iBAAeA,KAAW;AAAA,MAC1B,UAAAC;AAAA,MACA,WAAWM,EAAGf,EAAW,EAAE,SAAAK,GAAS,MAAAC,EAAA,CAAM,GAAGF,CAAS;AAAA,MACtD,SAASI,IAAUQ,IAAaL;AAAA,MAC/B,GAAGC;AAAA,MAEH,cAAU,gBAAAE,EAACG,GAAA,EAAQ,WAAU,gBAAe,eAAY,QAAO,IAAKV;AAAA,IAAA;AAAA,EAAA;AAG3E,CAAC;AAEDL,EAAW,cAAc;AAEzB,SAASc,EAAWE,GAAmB;AACrC,EAAAA,EAAE,eAAA,GACFA,EAAE,gBAAA;AACJ;"}
@@ -5,7 +5,9 @@ declare const field: (props?: ({
5
5
  tone?: "default" | "error" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
7
  type InputType = NonNullable<InputHTMLAttributes<HTMLInputElement>['type']>;
8
- export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix'>, VariantProps<typeof field> {
8
+ export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'prefix' | 'value'>, VariantProps<typeof field> {
9
+ /** Controlled value. `null` is treated as an empty controlled value (no React warning). */
10
+ value?: InputHTMLAttributes<HTMLInputElement>['value'] | null;
9
11
  /** Field type. `password` enables a show/hide toggle; `search` adds a clear button. */
10
12
  type?: InputType;
11
13
  /** Visible label rendered above the input. */