@object-ui/components 0.3.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 (295) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +170 -0
  4. package/dist/index.css +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +46186 -0
  7. package/dist/index.umd.cjs +92 -0
  8. package/dist/src/hooks/use-mobile.d.ts +1 -0
  9. package/dist/src/index.d.ts +2 -0
  10. package/dist/src/index.test.d.ts +1 -0
  11. package/dist/src/lib/utils.d.ts +4 -0
  12. package/dist/src/new-components.test.d.ts +1 -0
  13. package/dist/src/renderers/basic/div.d.ts +1 -0
  14. package/dist/src/renderers/basic/html.d.ts +1 -0
  15. package/dist/src/renderers/basic/icon.d.ts +1 -0
  16. package/dist/src/renderers/basic/image.d.ts +1 -0
  17. package/dist/src/renderers/basic/index.d.ts +0 -0
  18. package/dist/src/renderers/basic/separator.d.ts +1 -0
  19. package/dist/src/renderers/basic/span.d.ts +1 -0
  20. package/dist/src/renderers/basic/text.d.ts +1 -0
  21. package/dist/src/renderers/complex/__tests__/data-table.test.d.ts +0 -0
  22. package/dist/src/renderers/complex/calendar-view.d.ts +1 -0
  23. package/dist/src/renderers/complex/carousel.d.ts +1 -0
  24. package/dist/src/renderers/complex/chatbot.d.ts +1 -0
  25. package/dist/src/renderers/complex/chatbot.test.d.ts +1 -0
  26. package/dist/src/renderers/complex/data-table.d.ts +1 -0
  27. package/dist/src/renderers/complex/filter-builder.d.ts +1 -0
  28. package/dist/src/renderers/complex/index.d.ts +0 -0
  29. package/dist/src/renderers/complex/resizable.d.ts +1 -0
  30. package/dist/src/renderers/complex/scroll-area.d.ts +1 -0
  31. package/dist/src/renderers/complex/table.d.ts +1 -0
  32. package/dist/src/renderers/complex/timeline.d.ts +1 -0
  33. package/dist/src/renderers/data-display/alert.d.ts +1 -0
  34. package/dist/src/renderers/data-display/avatar.d.ts +1 -0
  35. package/dist/src/renderers/data-display/badge.d.ts +1 -0
  36. package/dist/src/renderers/data-display/index.d.ts +0 -0
  37. package/dist/src/renderers/data-display/list.d.ts +1 -0
  38. package/dist/src/renderers/data-display/statistic.d.ts +1 -0
  39. package/dist/src/renderers/data-display/tree-view.d.ts +1 -0
  40. package/dist/src/renderers/disclosure/accordion.d.ts +1 -0
  41. package/dist/src/renderers/disclosure/collapsible.d.ts +1 -0
  42. package/dist/src/renderers/disclosure/index.d.ts +0 -0
  43. package/dist/src/renderers/feedback/index.d.ts +0 -0
  44. package/dist/src/renderers/feedback/loading.d.ts +1 -0
  45. package/dist/src/renderers/feedback/progress.d.ts +1 -0
  46. package/dist/src/renderers/feedback/skeleton.d.ts +1 -0
  47. package/dist/src/renderers/feedback/toaster.d.ts +1 -0
  48. package/dist/src/renderers/form/button.d.ts +1 -0
  49. package/dist/src/renderers/form/calendar.d.ts +1 -0
  50. package/dist/src/renderers/form/checkbox.d.ts +1 -0
  51. package/dist/src/renderers/form/date-picker.d.ts +1 -0
  52. package/dist/src/renderers/form/file-upload.d.ts +1 -0
  53. package/dist/src/renderers/form/form.d.ts +1 -0
  54. package/dist/src/renderers/form/index.d.ts +0 -0
  55. package/dist/src/renderers/form/input-otp.d.ts +1 -0
  56. package/dist/src/renderers/form/input.d.ts +1 -0
  57. package/dist/src/renderers/form/label.d.ts +1 -0
  58. package/dist/src/renderers/form/radio-group.d.ts +1 -0
  59. package/dist/src/renderers/form/select.d.ts +1 -0
  60. package/dist/src/renderers/form/slider.d.ts +1 -0
  61. package/dist/src/renderers/form/switch.d.ts +1 -0
  62. package/dist/src/renderers/form/textarea.d.ts +1 -0
  63. package/dist/src/renderers/form/toggle.d.ts +1 -0
  64. package/dist/src/renderers/index.d.ts +0 -0
  65. package/dist/src/renderers/layout/card.d.ts +1 -0
  66. package/dist/src/renderers/layout/container.d.ts +1 -0
  67. package/dist/src/renderers/layout/flex.d.ts +1 -0
  68. package/dist/src/renderers/layout/grid.d.ts +1 -0
  69. package/dist/src/renderers/layout/index.d.ts +0 -0
  70. package/dist/src/renderers/layout/page.d.ts +7 -0
  71. package/dist/src/renderers/layout/semantic.d.ts +1 -0
  72. package/dist/src/renderers/layout/stack.d.ts +1 -0
  73. package/dist/src/renderers/layout/tabs.d.ts +1 -0
  74. package/dist/src/renderers/navigation/header-bar.d.ts +1 -0
  75. package/dist/src/renderers/navigation/index.d.ts +0 -0
  76. package/dist/src/renderers/navigation/sidebar.d.ts +1 -0
  77. package/dist/src/renderers/overlay/alert-dialog.d.ts +1 -0
  78. package/dist/src/renderers/overlay/context-menu.d.ts +1 -0
  79. package/dist/src/renderers/overlay/dialog.d.ts +1 -0
  80. package/dist/src/renderers/overlay/drawer.d.ts +1 -0
  81. package/dist/src/renderers/overlay/dropdown-menu.d.ts +1 -0
  82. package/dist/src/renderers/overlay/hover-card.d.ts +1 -0
  83. package/dist/src/renderers/overlay/index.d.ts +0 -0
  84. package/dist/src/renderers/overlay/popover.d.ts +1 -0
  85. package/dist/src/renderers/overlay/sheet.d.ts +1 -0
  86. package/dist/src/renderers/overlay/tooltip.d.ts +1 -0
  87. package/dist/src/ui/accordion.d.ts +7 -0
  88. package/dist/src/ui/alert-dialog.d.ts +14 -0
  89. package/dist/src/ui/alert.d.ts +9 -0
  90. package/dist/src/ui/aspect-ratio.d.ts +3 -0
  91. package/dist/src/ui/avatar.d.ts +6 -0
  92. package/dist/src/ui/badge.d.ts +9 -0
  93. package/dist/src/ui/breadcrumb.d.ts +11 -0
  94. package/dist/src/ui/button-group.d.ts +11 -0
  95. package/dist/src/ui/button.d.ts +13 -0
  96. package/dist/src/ui/calendar-view.d.ts +21 -0
  97. package/dist/src/ui/calendar.d.ts +8 -0
  98. package/dist/src/ui/card.d.ts +9 -0
  99. package/dist/src/ui/carousel.d.ts +19 -0
  100. package/dist/src/ui/chatbot.d.ts +36 -0
  101. package/dist/src/ui/checkbox.d.ts +4 -0
  102. package/dist/src/ui/collapsible.d.ts +5 -0
  103. package/dist/src/ui/command.d.ts +18 -0
  104. package/dist/src/ui/context-menu.d.ts +25 -0
  105. package/dist/src/ui/dialog.d.ts +15 -0
  106. package/dist/src/ui/drawer.d.ts +13 -0
  107. package/dist/src/ui/dropdown-menu.d.ts +25 -0
  108. package/dist/src/ui/empty.d.ts +11 -0
  109. package/dist/src/ui/field.d.ts +24 -0
  110. package/dist/src/ui/filter-builder.d.ts +31 -0
  111. package/dist/src/ui/form.d.ts +24 -0
  112. package/dist/src/ui/hover-card.d.ts +6 -0
  113. package/dist/src/ui/index.d.ts +56 -0
  114. package/dist/src/ui/input-group.d.ts +16 -0
  115. package/dist/src/ui/input-otp.d.ts +11 -0
  116. package/dist/src/ui/input.d.ts +3 -0
  117. package/dist/src/ui/item.d.ts +23 -0
  118. package/dist/src/ui/kbd.d.ts +3 -0
  119. package/dist/src/ui/label.d.ts +4 -0
  120. package/dist/src/ui/menubar.d.ts +26 -0
  121. package/dist/src/ui/navigation-menu.d.ts +14 -0
  122. package/dist/src/ui/pagination.d.ts +13 -0
  123. package/dist/src/ui/popover.d.ts +7 -0
  124. package/dist/src/ui/progress.d.ts +4 -0
  125. package/dist/src/ui/radio-group.d.ts +5 -0
  126. package/dist/src/ui/resizable.d.ts +10 -0
  127. package/dist/src/ui/scroll-area.d.ts +5 -0
  128. package/dist/src/ui/select.d.ts +15 -0
  129. package/dist/src/ui/separator.d.ts +4 -0
  130. package/dist/src/ui/sheet.d.ts +13 -0
  131. package/dist/src/ui/sidebar.d.ts +69 -0
  132. package/dist/src/ui/skeleton.d.ts +2 -0
  133. package/dist/src/ui/slider.d.ts +4 -0
  134. package/dist/src/ui/sonner.d.ts +3 -0
  135. package/dist/src/ui/spinner.d.ts +3 -0
  136. package/dist/src/ui/switch.d.ts +4 -0
  137. package/dist/src/ui/table.d.ts +10 -0
  138. package/dist/src/ui/tabs.d.ts +7 -0
  139. package/dist/src/ui/textarea.d.ts +3 -0
  140. package/dist/src/ui/timeline.d.ts +25 -0
  141. package/dist/src/ui/toggle-group.d.ts +9 -0
  142. package/dist/src/ui/toggle.d.ts +9 -0
  143. package/dist/src/ui/tooltip.d.ts +7 -0
  144. package/docs/FilterBuilder.md +268 -0
  145. package/metadata/Chart.component.yml +30 -0
  146. package/metadata/FilterBuilder.component.yml +39 -0
  147. package/metadata/GridLayout.component.yml +27 -0
  148. package/metadata/Menu.component.yml +31 -0
  149. package/metadata/ObjectForm.component.yml +34 -0
  150. package/metadata/ObjectTable.component.yml +41 -0
  151. package/metadata/Page.component.yml +24 -0
  152. package/package.json +87 -0
  153. package/postcss.config.js +6 -0
  154. package/src/hooks/use-mobile.tsx +19 -0
  155. package/src/index.css +76 -0
  156. package/src/index.test.ts +7 -0
  157. package/src/index.ts +10 -0
  158. package/src/lib/utils.tsx +27 -0
  159. package/src/new-components.test.ts +74 -0
  160. package/src/renderers/basic/div.tsx +41 -0
  161. package/src/renderers/basic/html.tsx +34 -0
  162. package/src/renderers/basic/icon.tsx +25 -0
  163. package/src/renderers/basic/image.tsx +37 -0
  164. package/src/renderers/basic/index.ts +7 -0
  165. package/src/renderers/basic/separator.tsx +48 -0
  166. package/src/renderers/basic/span.tsx +44 -0
  167. package/src/renderers/basic/text.tsx +42 -0
  168. package/src/renderers/complex/README-KANBAN.md +208 -0
  169. package/src/renderers/complex/TIMELINE.md +353 -0
  170. package/src/renderers/complex/__tests__/data-table.test.ts +52 -0
  171. package/src/renderers/complex/calendar-view.tsx +219 -0
  172. package/src/renderers/complex/carousel.tsx +60 -0
  173. package/src/renderers/complex/chatbot.test.ts +44 -0
  174. package/src/renderers/complex/chatbot.tsx +185 -0
  175. package/src/renderers/complex/data-table.tsx +650 -0
  176. package/src/renderers/complex/filter-builder.tsx +68 -0
  177. package/src/renderers/complex/index.ts +10 -0
  178. package/src/renderers/complex/resizable.tsx +54 -0
  179. package/src/renderers/complex/scroll-area.tsx +32 -0
  180. package/src/renderers/complex/table.tsx +86 -0
  181. package/src/renderers/complex/timeline.tsx +466 -0
  182. package/src/renderers/data-display/alert.tsx +37 -0
  183. package/src/renderers/data-display/avatar.tsx +29 -0
  184. package/src/renderers/data-display/badge.tsx +46 -0
  185. package/src/renderers/data-display/index.ts +6 -0
  186. package/src/renderers/data-display/list.tsx +95 -0
  187. package/src/renderers/data-display/statistic.tsx +98 -0
  188. package/src/renderers/data-display/tree-view.tsx +180 -0
  189. package/src/renderers/disclosure/accordion.tsx +60 -0
  190. package/src/renderers/disclosure/collapsible.tsx +44 -0
  191. package/src/renderers/disclosure/index.ts +2 -0
  192. package/src/renderers/feedback/index.ts +4 -0
  193. package/src/renderers/feedback/loading.tsx +69 -0
  194. package/src/renderers/feedback/progress.tsx +20 -0
  195. package/src/renderers/feedback/skeleton.tsx +22 -0
  196. package/src/renderers/feedback/toaster.tsx +26 -0
  197. package/src/renderers/form/button.tsx +61 -0
  198. package/src/renderers/form/calendar.tsx +25 -0
  199. package/src/renderers/form/checkbox.tsx +41 -0
  200. package/src/renderers/form/date-picker.tsx +75 -0
  201. package/src/renderers/form/file-upload.tsx +175 -0
  202. package/src/renderers/form/form.tsx +417 -0
  203. package/src/renderers/form/index.ts +16 -0
  204. package/src/renderers/form/input-otp.tsx +31 -0
  205. package/src/renderers/form/input.tsx +79 -0
  206. package/src/renderers/form/label.tsx +36 -0
  207. package/src/renderers/form/radio-group.tsx +54 -0
  208. package/src/renderers/form/select.tsx +66 -0
  209. package/src/renderers/form/slider.tsx +45 -0
  210. package/src/renderers/form/switch.tsx +39 -0
  211. package/src/renderers/form/textarea.tsx +45 -0
  212. package/src/renderers/form/toggle.tsx +76 -0
  213. package/src/renderers/index.ts +9 -0
  214. package/src/renderers/layout/card.tsx +69 -0
  215. package/src/renderers/layout/container.tsx +113 -0
  216. package/src/renderers/layout/flex.tsx +123 -0
  217. package/src/renderers/layout/grid.tsx +155 -0
  218. package/src/renderers/layout/index.ts +10 -0
  219. package/src/renderers/layout/page.tsx +82 -0
  220. package/src/renderers/layout/semantic.tsx +39 -0
  221. package/src/renderers/layout/stack.tsx +123 -0
  222. package/src/renderers/layout/tabs.tsx +63 -0
  223. package/src/renderers/navigation/header-bar.tsx +50 -0
  224. package/src/renderers/navigation/index.ts +2 -0
  225. package/src/renderers/navigation/sidebar.tsx +189 -0
  226. package/src/renderers/overlay/alert-dialog.tsx +63 -0
  227. package/src/renderers/overlay/context-menu.tsx +91 -0
  228. package/src/renderers/overlay/dialog.tsx +68 -0
  229. package/src/renderers/overlay/drawer.tsx +68 -0
  230. package/src/renderers/overlay/dropdown-menu.tsx +90 -0
  231. package/src/renderers/overlay/hover-card.tsx +46 -0
  232. package/src/renderers/overlay/index.ts +9 -0
  233. package/src/renderers/overlay/popover.tsx +47 -0
  234. package/src/renderers/overlay/sheet.tsx +68 -0
  235. package/src/renderers/overlay/tooltip.tsx +58 -0
  236. package/src/ui/accordion.tsx +64 -0
  237. package/src/ui/alert-dialog.tsx +155 -0
  238. package/src/ui/alert.tsx +78 -0
  239. package/src/ui/aspect-ratio.tsx +11 -0
  240. package/src/ui/avatar.tsx +51 -0
  241. package/src/ui/badge.tsx +46 -0
  242. package/src/ui/breadcrumb.tsx +109 -0
  243. package/src/ui/button-group.tsx +83 -0
  244. package/src/ui/button.tsx +65 -0
  245. package/src/ui/calendar-view.tsx +503 -0
  246. package/src/ui/calendar.tsx +237 -0
  247. package/src/ui/card.tsx +138 -0
  248. package/src/ui/carousel.tsx +239 -0
  249. package/src/ui/chatbot.tsx +240 -0
  250. package/src/ui/checkbox.tsx +32 -0
  251. package/src/ui/collapsible.tsx +31 -0
  252. package/src/ui/command.tsx +182 -0
  253. package/src/ui/context-menu.tsx +247 -0
  254. package/src/ui/dialog.tsx +141 -0
  255. package/src/ui/drawer.tsx +135 -0
  256. package/src/ui/dropdown-menu.tsx +254 -0
  257. package/src/ui/empty.tsx +104 -0
  258. package/src/ui/field.tsx +246 -0
  259. package/src/ui/filter-builder.tsx +359 -0
  260. package/src/ui/form.tsx +167 -0
  261. package/src/ui/hover-card.tsx +44 -0
  262. package/src/ui/index.ts +56 -0
  263. package/src/ui/input-group.tsx +170 -0
  264. package/src/ui/input-otp.tsx +81 -0
  265. package/src/ui/input.tsx +24 -0
  266. package/src/ui/item.tsx +193 -0
  267. package/src/ui/kbd.tsx +28 -0
  268. package/src/ui/label.tsx +24 -0
  269. package/src/ui/menubar.tsx +274 -0
  270. package/src/ui/navigation-menu.tsx +168 -0
  271. package/src/ui/pagination.tsx +127 -0
  272. package/src/ui/popover.tsx +48 -0
  273. package/src/ui/progress.tsx +41 -0
  274. package/src/ui/radio-group.tsx +45 -0
  275. package/src/ui/resizable.tsx +55 -0
  276. package/src/ui/scroll-area.tsx +58 -0
  277. package/src/ui/select.tsx +188 -0
  278. package/src/ui/separator.tsx +31 -0
  279. package/src/ui/sheet.tsx +137 -0
  280. package/src/ui/sidebar.tsx +726 -0
  281. package/src/ui/skeleton.tsx +20 -0
  282. package/src/ui/slider.tsx +63 -0
  283. package/src/ui/sonner.tsx +43 -0
  284. package/src/ui/spinner.tsx +38 -0
  285. package/src/ui/switch.tsx +31 -0
  286. package/src/ui/table.tsx +120 -0
  287. package/src/ui/tabs.tsx +86 -0
  288. package/src/ui/textarea.tsx +18 -0
  289. package/src/ui/timeline.tsx +266 -0
  290. package/src/ui/toggle-group.tsx +87 -0
  291. package/src/ui/toggle.tsx +50 -0
  292. package/src/ui/tooltip.tsx +61 -0
  293. package/tailwind.config.js +75 -0
  294. package/tsconfig.json +18 -0
  295. package/vite.config.ts +44 -0
@@ -0,0 +1,75 @@
1
+ import { ComponentRegistry } from '@object-ui/core';
2
+ import type { DatePickerSchema } from '@object-ui/types';
3
+ import { Calendar, Button, Popover, PopoverTrigger, PopoverContent, Label } from '../../ui';
4
+ import { CalendarIcon } from 'lucide-react';
5
+ import { format } from 'date-fns';
6
+ import { cn } from '../../lib/utils';
7
+
8
+ ComponentRegistry.register('date-picker',
9
+ ({ schema, className, value, onChange, ...props }: { schema: DatePickerSchema; className?: string; value?: Date; onChange?: (date: Date | undefined) => void; [key: string]: any }) => {
10
+ const handleSelect = (date: Date | undefined) => {
11
+ if (onChange) {
12
+ onChange(date);
13
+ }
14
+ };
15
+
16
+ // Extract designer-related props
17
+ const {
18
+ 'data-obj-id': dataObjId,
19
+ 'data-obj-type': dataObjType,
20
+ style,
21
+ ...triggerProps
22
+ } = props;
23
+
24
+ return (
25
+ <div
26
+ className={`grid w-full max-w-sm items-center gap-1.5 ${schema.wrapperClass || ''}`}
27
+ data-obj-id={dataObjId}
28
+ data-obj-type={dataObjType}
29
+ style={style}
30
+ >
31
+ {schema.label && <Label htmlFor={schema.id}>{schema.label}</Label>}
32
+ <Popover>
33
+ <PopoverTrigger asChild>
34
+ <Button
35
+ id={schema.id}
36
+ variant="outline"
37
+ className={cn(
38
+ 'w-full justify-start text-left font-normal transition-all duration-300',
39
+ !value ? 'text-muted-foreground' : 'text-cyan-300 font-medium shadow-[0_0_10px_-5px_rgba(6,182,212,0.5)] border-cyan-500/50 bg-cyan-950/20',
40
+ className
41
+ )}
42
+ {...triggerProps}
43
+ >
44
+ <CalendarIcon className="mr-2 h-4 w-4" />
45
+ {value ? format(value, schema.format || 'PPP') : <span>{schema.placeholder || 'Pick a date'}</span>}
46
+ </Button>
47
+ </PopoverTrigger>
48
+ <PopoverContent className="w-auto p-0">
49
+ <Calendar
50
+ mode="single"
51
+ selected={value}
52
+ onSelect={handleSelect}
53
+ initialFocus
54
+ />
55
+ </PopoverContent>
56
+ </Popover>
57
+ </div>
58
+ );
59
+ },
60
+ {
61
+ label: 'Date Picker',
62
+ inputs: [
63
+ { name: 'label', type: 'string', label: 'Label' },
64
+ { name: 'placeholder', type: 'string', label: 'Placeholder' },
65
+ { name: 'format', type: 'string', label: 'Date Format', description: 'date-fns format string (e.g., "PPP", "yyyy-MM-dd")' },
66
+ { name: 'id', type: 'string', label: 'ID', required: true }
67
+ ],
68
+ defaultProps: {
69
+ label: 'Date',
70
+ placeholder: 'Pick a date',
71
+ format: 'PPP',
72
+ id: 'date-picker-field'
73
+ }
74
+ }
75
+ );
@@ -0,0 +1,175 @@
1
+ import { ComponentRegistry } from '@object-ui/core';
2
+ import type { FileUploadSchema } from '@object-ui/types';
3
+ import { Label, Button } from '../../ui';
4
+ import { Upload, X, Rocket, CheckCircle2, ScanLine } from 'lucide-react';
5
+ import { useState, useRef, useEffect } from 'react';
6
+ import { cn } from '../../lib/utils';
7
+
8
+ ComponentRegistry.register('file-upload',
9
+ ({ schema, className, value, onChange, ...props }: { schema: FileUploadSchema; className?: string; value?: File[]; onChange?: (files: File[]) => void; [key: string]: any }) => {
10
+ const [files, setFiles] = useState<File[]>(value || []);
11
+ const [isUploading, setIsUploading] = useState(false);
12
+ const [progress, setProgress] = useState(0);
13
+ const inputRef = useRef<HTMLInputElement>(null);
14
+
15
+ // Simulate upload effect
16
+ useEffect(() => {
17
+ if (isUploading) {
18
+ const interval = setInterval(() => {
19
+ setProgress(prev => {
20
+ if (prev >= 100) {
21
+ clearInterval(interval);
22
+ setIsUploading(false);
23
+ return 0;
24
+ }
25
+ return prev + 5;
26
+ });
27
+ }, 50);
28
+ return () => clearInterval(interval);
29
+ }
30
+ }, [isUploading]);
31
+
32
+ const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
33
+ const newFiles = Array.from(e.target.files || []);
34
+ if (newFiles.length > 0) {
35
+ setIsUploading(true);
36
+ setProgress(0);
37
+ const updatedFiles = schema.multiple ? [...files, ...newFiles] : newFiles;
38
+ setTimeout(() => {
39
+ setFiles(updatedFiles);
40
+ if (onChange) {
41
+ onChange(updatedFiles);
42
+ }
43
+ }, 1000); // Delay actual "completion" to match animation
44
+ }
45
+ };
46
+
47
+ const handleRemoveFile = (index: number) => {
48
+ const updatedFiles = files.filter((_, i) => i !== index);
49
+ setFiles(updatedFiles);
50
+ if (onChange) {
51
+ onChange(updatedFiles);
52
+ }
53
+ };
54
+
55
+ const handleClick = () => {
56
+ inputRef.current?.click();
57
+ };
58
+
59
+ // Extract designer-related props
60
+ const {
61
+ 'data-obj-id': dataObjId,
62
+ 'data-obj-type': dataObjType,
63
+ style,
64
+ ...inputProps
65
+ } = props;
66
+
67
+ return (
68
+ <div
69
+ className={`grid w-full max-w-sm items-center gap-2 ${schema.wrapperClass || ''}`}
70
+ data-obj-id={dataObjId}
71
+ data-obj-type={dataObjType}
72
+ style={style}
73
+ >
74
+ {schema.label && <Label htmlFor={schema.id}>{schema.label}</Label>}
75
+ <div className={cn('relative flex flex-col gap-3 group/upload', className)}>
76
+ <input
77
+ ref={inputRef}
78
+ type="file"
79
+ id={schema.id}
80
+ className="hidden"
81
+ accept={schema.accept}
82
+ multiple={schema.multiple}
83
+ onChange={handleFileChange}
84
+ {...inputProps}
85
+ />
86
+
87
+ <div
88
+ onClick={handleClick}
89
+ className={cn(
90
+ "relative overflow-hidden cursor-pointer rounded-lg border-2 border-dashed transition-all duration-300 min-h-[120px] flex flex-col items-center justify-center p-6 gap-3",
91
+ isUploading
92
+ ? "border-cyan-400 bg-cyan-950/30"
93
+ : "border-slate-700 bg-slate-900/50 hover:bg-slate-900 hover:border-cyan-500/50 hover:shadow-[0_0_20px_-5px_rgba(6,182,212,0.3)]"
94
+ )}
95
+ >
96
+ {/* Background Grid Animation */}
97
+ <div className="absolute inset-0 bg-[url('https://grainy-gradients.vercel.app/noise.svg')] opacity-5 pointer-events-none" />
98
+ <div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:14px_24px] pointer-events-none" />
99
+
100
+ {/* Launch Pad Visual */}
101
+ <div className={cn(
102
+ "relative z-10 p-3 rounded-full bg-slate-800 border border-slate-700 transition-all duration-500",
103
+ isUploading ? "scale-110 shadow-[0_0_30px_cyan] border-cyan-400 animate-bounce" : "group-hover/upload:scale-110 group-hover/upload:shadow-lg group-hover/upload:border-cyan-500"
104
+ )}>
105
+ {isUploading ? (
106
+ <Rocket className="w-8 h-8 text-cyan-300 animate-pulse" />
107
+ ) : (
108
+ <Upload className="w-6 h-6 text-slate-400 group-hover/upload:text-cyan-400" />
109
+ )}
110
+ </div>
111
+
112
+ <div className="relative z-10 text-center">
113
+ <p className={cn("text-xs font-mono uppercase tracking-widest transition-colors", isUploading ? "text-cyan-300" : "text-slate-500 group-hover/upload:text-cyan-200")}>
114
+ {isUploading ? "INITIATING LAUNCH SEQUENCE..." : (schema.buttonText || "DROP PAYLOAD OR CLICK TO UPLOAD")}
115
+ </p>
116
+ </div>
117
+
118
+ {/* Progress Bar (Holographic Beam) */}
119
+ {isUploading && (
120
+ <div className="absolute bottom-0 left-0 h-1 bg-cyan-500 shadow-[0_0_10px_cyan] transition-all duration-75 ease-out" style={{ width: `${progress}%` }} />
121
+ )}
122
+ </div>
123
+
124
+ {/* File List */}
125
+ {files.length > 0 && (
126
+ <div className="flex flex-col gap-2 mt-1 perspective-1000">
127
+ {files.map((file, index) => (
128
+ <div
129
+ key={index}
130
+ className="relative group/file overflow-hidden flex items-center justify-between p-3 border border-border bg-card/80 rounded-sm text-sm transition-all hover:border-primary/30 hover:shadow-lg hover:shadow-primary/10 animate-in slide-in-from-bottom-2 fade-in"
131
+ >
132
+ <div className="absolute left-0 top-0 bottom-0 w-[2px] bg-primary/50 group-hover/file:bg-primary group-hover/file:shadow-[0_0_8px_hsl(var(--primary))]" />
133
+
134
+ <div className="flex items-center gap-3 z-10">
135
+ <CheckCircle2 className="w-4 h-4 text-emerald-500 rounded-full" />
136
+ <span className="truncate max-w-[200px] text-muted-foreground group-hover/file:text-foreground font-mono text-xs transition-colors">{file.name}</span>
137
+ </div>
138
+
139
+ <Button
140
+ type="button"
141
+ variant="ghost"
142
+ size="sm"
143
+ className="h-6 w-6 p-0 hover:bg-destructive/10 hover:text-destructive"
144
+ onClick={() => handleRemoveFile(index)}
145
+ >
146
+ <X className="h-3 w-3" />
147
+ </Button>
148
+
149
+ {/* Scanning Effect - Fixed gradient syntax */}
150
+ <div className="absolute inset-0 bg-gradient-to-r from-transparent via-primary/5 to-transparent -translate-x-full group-hover/file:animate-[shimmer_1s_infinite] pointer-events-none" />
151
+ </div>
152
+ ))}
153
+ </div>
154
+ )}
155
+ </div>
156
+ </div>
157
+ );
158
+ },
159
+ {
160
+ label: 'File Upload',
161
+ inputs: [
162
+ { name: 'label', type: 'string', label: 'Label' },
163
+ { name: 'buttonText', type: 'string', label: 'Button Text' },
164
+ { name: 'accept', type: 'string', label: 'Accepted File Types', description: 'MIME types (e.g., "image/*,application/pdf")' },
165
+ { name: 'multiple', type: 'boolean', label: 'Allow Multiple Files' },
166
+ { name: 'id', type: 'string', label: 'ID', required: true }
167
+ ],
168
+ defaultProps: {
169
+ label: 'Upload files',
170
+ buttonText: 'Choose files',
171
+ multiple: true,
172
+ id: 'file-upload-field'
173
+ }
174
+ }
175
+ );
@@ -0,0 +1,417 @@
1
+ import { ComponentRegistry } from '@object-ui/core';
2
+ import type { FormSchema, FormField as FormFieldConfig, ValidationRule, FieldCondition, SelectOption } from '@object-ui/types';
3
+ import { useForm } from 'react-hook-form';
4
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription } from '../../ui/form';
5
+ import { Button } from '../../ui/button';
6
+ import { Input } from '../../ui/input';
7
+ import { Textarea } from '../../ui/textarea';
8
+ import { Checkbox } from '../../ui/checkbox';
9
+ import {
10
+ Select,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ SelectContent,
14
+ SelectItem
15
+ } from '../../ui/select';
16
+ import { renderChildren } from '../../lib/utils';
17
+ import { Alert, AlertDescription } from '../../ui/alert';
18
+ import { AlertCircle, Loader2 } from 'lucide-react';
19
+ import { cn } from '../../lib/utils';
20
+ import React from 'react';
21
+
22
+ // Form renderer component - Airtable-style feature-complete form
23
+ ComponentRegistry.register('form',
24
+ ({ schema, className, onAction, ...props }: { schema: FormSchema; className?: string; onAction?: (action: any) => void; [key: string]: any }) => {
25
+ const {
26
+ defaultValues = {},
27
+ fields = [],
28
+ submitLabel = 'Submit',
29
+ cancelLabel = 'Cancel',
30
+ showCancel = false,
31
+ layout = 'vertical',
32
+ columns = 1,
33
+ onSubmit: onSubmitProp,
34
+ onChange: onChangeProp,
35
+ resetOnSubmit = false,
36
+ validationMode = 'onSubmit',
37
+ disabled = false,
38
+ } = schema;
39
+
40
+ // Initialize react-hook-form
41
+ const form = useForm({
42
+ defaultValues,
43
+ mode: validationMode,
44
+ });
45
+
46
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
47
+ const [submitError, setSubmitError] = React.useState<string | null>(null);
48
+
49
+ // Watch for form changes - only track changes when onAction is available
50
+ React.useEffect(() => {
51
+ if (onAction) {
52
+ const subscription = form.watch((data) => {
53
+ onAction({
54
+ type: 'form_change',
55
+ data,
56
+ formData: data,
57
+ });
58
+ });
59
+ return () => subscription.unsubscribe();
60
+ }
61
+ }, [form, onAction]);
62
+
63
+ // Handle form submission
64
+ const handleSubmit = form.handleSubmit(async (data) => {
65
+ setIsSubmitting(true);
66
+ setSubmitError(null);
67
+
68
+ try {
69
+ if (onAction) {
70
+ const result = await onAction({
71
+ type: 'form_submit',
72
+ data,
73
+ formData: data,
74
+ }) as any;
75
+
76
+ // Check if submission returned an error
77
+ if (result?.error) {
78
+ setSubmitError(result.error);
79
+ return;
80
+ }
81
+ }
82
+
83
+ if (onSubmitProp && typeof onSubmitProp === 'function') {
84
+ await onSubmitProp(data);
85
+ }
86
+
87
+ if (resetOnSubmit) {
88
+ form.reset();
89
+ }
90
+ } catch (error) {
91
+ // Handle different error types safely
92
+ const errorMessage = error instanceof Error
93
+ ? error.message
94
+ : typeof error === 'string'
95
+ ? error
96
+ : 'An error occurred during submission';
97
+ setSubmitError(errorMessage);
98
+
99
+ // Log errors for debugging (dev environment only)
100
+ // process may not be defined in all environments
101
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'development') {
102
+ console.error('Form submission error:', error);
103
+ }
104
+ } finally {
105
+ setIsSubmitting(false);
106
+ }
107
+ });
108
+
109
+ // Handle cancel
110
+ const handleCancel = () => {
111
+ form.reset();
112
+ if (onAction) {
113
+ onAction({
114
+ type: 'form_cancel',
115
+ data: form.getValues(),
116
+ });
117
+ }
118
+ };
119
+
120
+ // Determine grid classes based on columns (explicit classes for Tailwind JIT)
121
+ const gridColsClass =
122
+ columns === 1 ? '' :
123
+ columns === 2 ? 'md:grid-cols-2' :
124
+ columns === 3 ? 'md:grid-cols-3' :
125
+ 'md:grid-cols-4';
126
+
127
+ const gridClass = columns > 1
128
+ ? cn('grid gap-4', gridColsClass)
129
+ : 'space-y-4';
130
+
131
+ // Extract designer-related props
132
+ const {
133
+ 'data-obj-id': dataObjId,
134
+ 'data-obj-type': dataObjType,
135
+ style,
136
+ ...formProps
137
+ } = props;
138
+
139
+ return (
140
+ <Form {...form}>
141
+ <form
142
+ onSubmit={handleSubmit}
143
+ className={className}
144
+ {...formProps}
145
+ // Apply designer props
146
+ data-obj-id={dataObjId}
147
+ data-obj-type={dataObjType}
148
+ style={style}
149
+ >
150
+ {/* Form Error Alert */}
151
+ {submitError && (
152
+ <Alert variant="destructive" className="mb-4">
153
+ <AlertCircle className="h-4 w-4" />
154
+ <AlertDescription>{submitError}</AlertDescription>
155
+ </Alert>
156
+ )}
157
+
158
+ {/* Form Fields */}
159
+ {schema.children ? (
160
+ // If children are provided directly, render them
161
+ <div className={schema.fieldContainerClass || 'space-y-4'}>
162
+ {renderChildren(schema.children)}
163
+ </div>
164
+ ) : (
165
+ // Otherwise render fields from schema
166
+ <div className={schema.fieldContainerClass || gridClass}>
167
+ {fields.map((field: FormFieldConfig) => {
168
+ const {
169
+ name,
170
+ label,
171
+ description,
172
+ type = 'input',
173
+ required = false,
174
+ disabled: fieldDisabled = false,
175
+ validation = {},
176
+ condition,
177
+ ...fieldProps
178
+ } = field;
179
+
180
+ // Handle conditional rendering with null/undefined safety
181
+ if (condition) {
182
+ const watchField = condition.field;
183
+ const watchValue = form.watch(watchField);
184
+
185
+ // Check for null/undefined before evaluating conditions
186
+ const hasValue = watchValue !== undefined && watchValue !== null;
187
+
188
+ if (condition.equals !== undefined && watchValue !== condition.equals) {
189
+ return null;
190
+ }
191
+ if (condition.notEquals !== undefined && watchValue === condition.notEquals) {
192
+ return null;
193
+ }
194
+ if (condition.in && (!hasValue || !condition.in.includes(watchValue))) {
195
+ return null;
196
+ }
197
+ }
198
+
199
+ // Build validation rules
200
+ const rules: any = {
201
+ ...validation,
202
+ };
203
+
204
+ if (required) {
205
+ rules.required = typeof validation.required === 'string'
206
+ ? validation.required
207
+ : `${label || name} is required`;
208
+ }
209
+
210
+ // Use field.id or field.name for stable keys (never use index alone)
211
+ const fieldKey = field.id ?? name;
212
+
213
+ return (
214
+ <FormField
215
+ key={fieldKey}
216
+ control={form.control}
217
+ name={name}
218
+ rules={rules}
219
+ render={({ field: formField }) => (
220
+ <FormItem>
221
+ {label && (
222
+ <FormLabel>
223
+ {label}
224
+ {required && (
225
+ <span className="text-destructive ml-1" aria-label="required">
226
+ *
227
+ </span>
228
+ )}
229
+ </FormLabel>
230
+ )}
231
+ <FormControl>
232
+ {/* Render the actual field component based on type */}
233
+ {renderFieldComponent(type, {
234
+ ...fieldProps,
235
+ ...formField,
236
+ inputType: fieldProps.inputType,
237
+ options: fieldProps.options,
238
+ placeholder: fieldProps.placeholder,
239
+ disabled: disabled || fieldDisabled || isSubmitting,
240
+ })}
241
+ </FormControl>
242
+ {description && (
243
+ <FormDescription>{description}</FormDescription>
244
+ )}
245
+ <FormMessage />
246
+ </FormItem>
247
+ )}
248
+ />
249
+ );
250
+ })}
251
+ </div>
252
+ )}
253
+
254
+ {/* Form Actions */}
255
+ {(schema.showActions !== false) && (
256
+ <div className={`flex gap-2 ${layout === 'horizontal' ? 'justify-end' : 'justify-start'} mt-6`}>
257
+ {showCancel && (
258
+ <Button
259
+ type="button"
260
+ variant="outline"
261
+ onClick={handleCancel}
262
+ disabled={isSubmitting || disabled}
263
+ >
264
+ {cancelLabel}
265
+ </Button>
266
+ )}
267
+ <Button
268
+ type="submit"
269
+ disabled={isSubmitting || disabled}
270
+ >
271
+ {isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
272
+ {submitLabel}
273
+ </Button>
274
+ </div>
275
+ )}
276
+ </form>
277
+ </Form>
278
+ );
279
+ },
280
+ {
281
+ label: 'Form',
282
+ inputs: [
283
+ {
284
+ name: 'fields',
285
+ type: 'array',
286
+ label: 'Fields',
287
+ description: 'Array of field configurations with name, label, type, validation, etc.'
288
+ },
289
+ {
290
+ name: 'defaultValues',
291
+ type: 'object',
292
+ label: 'Default Values',
293
+ description: 'Object with default values for form fields'
294
+ },
295
+ { name: 'submitLabel', type: 'string', label: 'Submit Button Label', defaultValue: 'Submit' },
296
+ { name: 'cancelLabel', type: 'string', label: 'Cancel Button Label', defaultValue: 'Cancel' },
297
+ { name: 'showCancel', type: 'boolean', label: 'Show Cancel Button', defaultValue: false },
298
+ {
299
+ name: 'layout',
300
+ type: 'enum',
301
+ enum: ['vertical', 'horizontal'],
302
+ label: 'Layout',
303
+ defaultValue: 'vertical'
304
+ },
305
+ {
306
+ name: 'columns',
307
+ type: 'number',
308
+ label: 'Number of Columns',
309
+ defaultValue: 1,
310
+ description: 'For multi-column layouts (1-4)'
311
+ },
312
+ {
313
+ name: 'validationMode',
314
+ type: 'enum',
315
+ enum: ['onSubmit', 'onBlur', 'onChange', 'onTouched', 'all'],
316
+ label: 'Validation Mode',
317
+ defaultValue: 'onSubmit'
318
+ },
319
+ { name: 'resetOnSubmit', type: 'boolean', label: 'Reset After Submit', defaultValue: false },
320
+ { name: 'disabled', type: 'boolean', label: 'Disabled', defaultValue: false },
321
+ { name: 'className', type: 'string', label: 'CSS Class' },
322
+ { name: 'fieldContainerClass', type: 'string', label: 'Field Container CSS Class' }
323
+ ],
324
+ defaultProps: {
325
+ submitLabel: 'Submit',
326
+ cancelLabel: 'Cancel',
327
+ showCancel: false,
328
+ layout: 'vertical',
329
+ columns: 1,
330
+ validationMode: 'onSubmit',
331
+ resetOnSubmit: false,
332
+ disabled: false,
333
+ fields: [
334
+ {
335
+ name: 'name',
336
+ label: 'Name',
337
+ type: 'input',
338
+ required: true,
339
+ placeholder: 'Enter your name',
340
+ },
341
+ {
342
+ name: 'email',
343
+ label: 'Email',
344
+ type: 'input',
345
+ inputType: 'email',
346
+ required: true,
347
+ placeholder: 'Enter your email',
348
+ },
349
+ ],
350
+ },
351
+ }
352
+ );
353
+
354
+ // Helper function to render field components with proper typing
355
+ interface RenderFieldProps {
356
+ inputType?: string;
357
+ options?: SelectOption[];
358
+ placeholder?: string;
359
+ value?: any;
360
+ onChange?: (value: any) => void;
361
+ disabled?: boolean;
362
+ [key: string]: any;
363
+ }
364
+
365
+ function renderFieldComponent(type: string, props: RenderFieldProps) {
366
+ const { inputType, options = [], placeholder, ...fieldProps } = props;
367
+
368
+ switch (type) {
369
+ case 'input':
370
+ return <Input type={inputType || 'text'} placeholder={placeholder} {...fieldProps} />;
371
+
372
+ case 'textarea':
373
+ return <Textarea placeholder={placeholder} {...fieldProps} />;
374
+
375
+ case 'checkbox': {
376
+ // For checkbox, we need to handle the value differently
377
+ const { value, onChange, ...checkboxProps } = fieldProps;
378
+ return (
379
+ <div className="flex items-center space-x-2">
380
+ <Checkbox
381
+ checked={value}
382
+ onCheckedChange={onChange}
383
+ {...checkboxProps}
384
+ />
385
+ </div>
386
+ );
387
+ }
388
+
389
+ case 'select': {
390
+ // For select with react-hook-form, we need to handle the onChange
391
+ const { value: selectValue, onChange: selectOnChange, ...selectProps } = fieldProps;
392
+
393
+ // Safety check for options
394
+ if (!options || options.length === 0) {
395
+ return <div className="text-sm text-muted-foreground">No options available</div>;
396
+ }
397
+
398
+ return (
399
+ <Select value={selectValue} onValueChange={selectOnChange} {...selectProps}>
400
+ <SelectTrigger>
401
+ <SelectValue placeholder={placeholder ?? 'Select an option'} />
402
+ </SelectTrigger>
403
+ <SelectContent>
404
+ {options.map((opt: SelectOption) => (
405
+ <SelectItem key={opt.value} value={opt.value}>
406
+ {opt.label}
407
+ </SelectItem>
408
+ ))}
409
+ </SelectContent>
410
+ </Select>
411
+ );
412
+ }
413
+
414
+ default:
415
+ return <Input type={inputType || 'text'} placeholder={placeholder} {...fieldProps} />;
416
+ }
417
+ }
@@ -0,0 +1,16 @@
1
+ import './form';
2
+ import './button';
3
+ import './label';
4
+ import './input';
5
+ import './textarea';
6
+
7
+ import './checkbox';
8
+ import './switch';
9
+ import './select';
10
+ import './radio-group';
11
+ import './slider';
12
+ import './toggle';
13
+ import './input-otp';
14
+ import './calendar';
15
+ import './date-picker';
16
+ import './file-upload';