@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.
- package/CHANGELOG.md +46 -0
- package/LICENSE +21 -0
- package/README.md +170 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +46186 -0
- package/dist/index.umd.cjs +92 -0
- package/dist/src/hooks/use-mobile.d.ts +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.test.d.ts +1 -0
- package/dist/src/lib/utils.d.ts +4 -0
- package/dist/src/new-components.test.d.ts +1 -0
- package/dist/src/renderers/basic/div.d.ts +1 -0
- package/dist/src/renderers/basic/html.d.ts +1 -0
- package/dist/src/renderers/basic/icon.d.ts +1 -0
- package/dist/src/renderers/basic/image.d.ts +1 -0
- package/dist/src/renderers/basic/index.d.ts +0 -0
- package/dist/src/renderers/basic/separator.d.ts +1 -0
- package/dist/src/renderers/basic/span.d.ts +1 -0
- package/dist/src/renderers/basic/text.d.ts +1 -0
- package/dist/src/renderers/complex/__tests__/data-table.test.d.ts +0 -0
- package/dist/src/renderers/complex/calendar-view.d.ts +1 -0
- package/dist/src/renderers/complex/carousel.d.ts +1 -0
- package/dist/src/renderers/complex/chatbot.d.ts +1 -0
- package/dist/src/renderers/complex/chatbot.test.d.ts +1 -0
- package/dist/src/renderers/complex/data-table.d.ts +1 -0
- package/dist/src/renderers/complex/filter-builder.d.ts +1 -0
- package/dist/src/renderers/complex/index.d.ts +0 -0
- package/dist/src/renderers/complex/resizable.d.ts +1 -0
- package/dist/src/renderers/complex/scroll-area.d.ts +1 -0
- package/dist/src/renderers/complex/table.d.ts +1 -0
- package/dist/src/renderers/complex/timeline.d.ts +1 -0
- package/dist/src/renderers/data-display/alert.d.ts +1 -0
- package/dist/src/renderers/data-display/avatar.d.ts +1 -0
- package/dist/src/renderers/data-display/badge.d.ts +1 -0
- package/dist/src/renderers/data-display/index.d.ts +0 -0
- package/dist/src/renderers/data-display/list.d.ts +1 -0
- package/dist/src/renderers/data-display/statistic.d.ts +1 -0
- package/dist/src/renderers/data-display/tree-view.d.ts +1 -0
- package/dist/src/renderers/disclosure/accordion.d.ts +1 -0
- package/dist/src/renderers/disclosure/collapsible.d.ts +1 -0
- package/dist/src/renderers/disclosure/index.d.ts +0 -0
- package/dist/src/renderers/feedback/index.d.ts +0 -0
- package/dist/src/renderers/feedback/loading.d.ts +1 -0
- package/dist/src/renderers/feedback/progress.d.ts +1 -0
- package/dist/src/renderers/feedback/skeleton.d.ts +1 -0
- package/dist/src/renderers/feedback/toaster.d.ts +1 -0
- package/dist/src/renderers/form/button.d.ts +1 -0
- package/dist/src/renderers/form/calendar.d.ts +1 -0
- package/dist/src/renderers/form/checkbox.d.ts +1 -0
- package/dist/src/renderers/form/date-picker.d.ts +1 -0
- package/dist/src/renderers/form/file-upload.d.ts +1 -0
- package/dist/src/renderers/form/form.d.ts +1 -0
- package/dist/src/renderers/form/index.d.ts +0 -0
- package/dist/src/renderers/form/input-otp.d.ts +1 -0
- package/dist/src/renderers/form/input.d.ts +1 -0
- package/dist/src/renderers/form/label.d.ts +1 -0
- package/dist/src/renderers/form/radio-group.d.ts +1 -0
- package/dist/src/renderers/form/select.d.ts +1 -0
- package/dist/src/renderers/form/slider.d.ts +1 -0
- package/dist/src/renderers/form/switch.d.ts +1 -0
- package/dist/src/renderers/form/textarea.d.ts +1 -0
- package/dist/src/renderers/form/toggle.d.ts +1 -0
- package/dist/src/renderers/index.d.ts +0 -0
- package/dist/src/renderers/layout/card.d.ts +1 -0
- package/dist/src/renderers/layout/container.d.ts +1 -0
- package/dist/src/renderers/layout/flex.d.ts +1 -0
- package/dist/src/renderers/layout/grid.d.ts +1 -0
- package/dist/src/renderers/layout/index.d.ts +0 -0
- package/dist/src/renderers/layout/page.d.ts +7 -0
- package/dist/src/renderers/layout/semantic.d.ts +1 -0
- package/dist/src/renderers/layout/stack.d.ts +1 -0
- package/dist/src/renderers/layout/tabs.d.ts +1 -0
- package/dist/src/renderers/navigation/header-bar.d.ts +1 -0
- package/dist/src/renderers/navigation/index.d.ts +0 -0
- package/dist/src/renderers/navigation/sidebar.d.ts +1 -0
- package/dist/src/renderers/overlay/alert-dialog.d.ts +1 -0
- package/dist/src/renderers/overlay/context-menu.d.ts +1 -0
- package/dist/src/renderers/overlay/dialog.d.ts +1 -0
- package/dist/src/renderers/overlay/drawer.d.ts +1 -0
- package/dist/src/renderers/overlay/dropdown-menu.d.ts +1 -0
- package/dist/src/renderers/overlay/hover-card.d.ts +1 -0
- package/dist/src/renderers/overlay/index.d.ts +0 -0
- package/dist/src/renderers/overlay/popover.d.ts +1 -0
- package/dist/src/renderers/overlay/sheet.d.ts +1 -0
- package/dist/src/renderers/overlay/tooltip.d.ts +1 -0
- package/dist/src/ui/accordion.d.ts +7 -0
- package/dist/src/ui/alert-dialog.d.ts +14 -0
- package/dist/src/ui/alert.d.ts +9 -0
- package/dist/src/ui/aspect-ratio.d.ts +3 -0
- package/dist/src/ui/avatar.d.ts +6 -0
- package/dist/src/ui/badge.d.ts +9 -0
- package/dist/src/ui/breadcrumb.d.ts +11 -0
- package/dist/src/ui/button-group.d.ts +11 -0
- package/dist/src/ui/button.d.ts +13 -0
- package/dist/src/ui/calendar-view.d.ts +21 -0
- package/dist/src/ui/calendar.d.ts +8 -0
- package/dist/src/ui/card.d.ts +9 -0
- package/dist/src/ui/carousel.d.ts +19 -0
- package/dist/src/ui/chatbot.d.ts +36 -0
- package/dist/src/ui/checkbox.d.ts +4 -0
- package/dist/src/ui/collapsible.d.ts +5 -0
- package/dist/src/ui/command.d.ts +18 -0
- package/dist/src/ui/context-menu.d.ts +25 -0
- package/dist/src/ui/dialog.d.ts +15 -0
- package/dist/src/ui/drawer.d.ts +13 -0
- package/dist/src/ui/dropdown-menu.d.ts +25 -0
- package/dist/src/ui/empty.d.ts +11 -0
- package/dist/src/ui/field.d.ts +24 -0
- package/dist/src/ui/filter-builder.d.ts +31 -0
- package/dist/src/ui/form.d.ts +24 -0
- package/dist/src/ui/hover-card.d.ts +6 -0
- package/dist/src/ui/index.d.ts +56 -0
- package/dist/src/ui/input-group.d.ts +16 -0
- package/dist/src/ui/input-otp.d.ts +11 -0
- package/dist/src/ui/input.d.ts +3 -0
- package/dist/src/ui/item.d.ts +23 -0
- package/dist/src/ui/kbd.d.ts +3 -0
- package/dist/src/ui/label.d.ts +4 -0
- package/dist/src/ui/menubar.d.ts +26 -0
- package/dist/src/ui/navigation-menu.d.ts +14 -0
- package/dist/src/ui/pagination.d.ts +13 -0
- package/dist/src/ui/popover.d.ts +7 -0
- package/dist/src/ui/progress.d.ts +4 -0
- package/dist/src/ui/radio-group.d.ts +5 -0
- package/dist/src/ui/resizable.d.ts +10 -0
- package/dist/src/ui/scroll-area.d.ts +5 -0
- package/dist/src/ui/select.d.ts +15 -0
- package/dist/src/ui/separator.d.ts +4 -0
- package/dist/src/ui/sheet.d.ts +13 -0
- package/dist/src/ui/sidebar.d.ts +69 -0
- package/dist/src/ui/skeleton.d.ts +2 -0
- package/dist/src/ui/slider.d.ts +4 -0
- package/dist/src/ui/sonner.d.ts +3 -0
- package/dist/src/ui/spinner.d.ts +3 -0
- package/dist/src/ui/switch.d.ts +4 -0
- package/dist/src/ui/table.d.ts +10 -0
- package/dist/src/ui/tabs.d.ts +7 -0
- package/dist/src/ui/textarea.d.ts +3 -0
- package/dist/src/ui/timeline.d.ts +25 -0
- package/dist/src/ui/toggle-group.d.ts +9 -0
- package/dist/src/ui/toggle.d.ts +9 -0
- package/dist/src/ui/tooltip.d.ts +7 -0
- package/docs/FilterBuilder.md +268 -0
- package/metadata/Chart.component.yml +30 -0
- package/metadata/FilterBuilder.component.yml +39 -0
- package/metadata/GridLayout.component.yml +27 -0
- package/metadata/Menu.component.yml +31 -0
- package/metadata/ObjectForm.component.yml +34 -0
- package/metadata/ObjectTable.component.yml +41 -0
- package/metadata/Page.component.yml +24 -0
- package/package.json +87 -0
- package/postcss.config.js +6 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/index.css +76 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +10 -0
- package/src/lib/utils.tsx +27 -0
- package/src/new-components.test.ts +74 -0
- package/src/renderers/basic/div.tsx +41 -0
- package/src/renderers/basic/html.tsx +34 -0
- package/src/renderers/basic/icon.tsx +25 -0
- package/src/renderers/basic/image.tsx +37 -0
- package/src/renderers/basic/index.ts +7 -0
- package/src/renderers/basic/separator.tsx +48 -0
- package/src/renderers/basic/span.tsx +44 -0
- package/src/renderers/basic/text.tsx +42 -0
- package/src/renderers/complex/README-KANBAN.md +208 -0
- package/src/renderers/complex/TIMELINE.md +353 -0
- package/src/renderers/complex/__tests__/data-table.test.ts +52 -0
- package/src/renderers/complex/calendar-view.tsx +219 -0
- package/src/renderers/complex/carousel.tsx +60 -0
- package/src/renderers/complex/chatbot.test.ts +44 -0
- package/src/renderers/complex/chatbot.tsx +185 -0
- package/src/renderers/complex/data-table.tsx +650 -0
- package/src/renderers/complex/filter-builder.tsx +68 -0
- package/src/renderers/complex/index.ts +10 -0
- package/src/renderers/complex/resizable.tsx +54 -0
- package/src/renderers/complex/scroll-area.tsx +32 -0
- package/src/renderers/complex/table.tsx +86 -0
- package/src/renderers/complex/timeline.tsx +466 -0
- package/src/renderers/data-display/alert.tsx +37 -0
- package/src/renderers/data-display/avatar.tsx +29 -0
- package/src/renderers/data-display/badge.tsx +46 -0
- package/src/renderers/data-display/index.ts +6 -0
- package/src/renderers/data-display/list.tsx +95 -0
- package/src/renderers/data-display/statistic.tsx +98 -0
- package/src/renderers/data-display/tree-view.tsx +180 -0
- package/src/renderers/disclosure/accordion.tsx +60 -0
- package/src/renderers/disclosure/collapsible.tsx +44 -0
- package/src/renderers/disclosure/index.ts +2 -0
- package/src/renderers/feedback/index.ts +4 -0
- package/src/renderers/feedback/loading.tsx +69 -0
- package/src/renderers/feedback/progress.tsx +20 -0
- package/src/renderers/feedback/skeleton.tsx +22 -0
- package/src/renderers/feedback/toaster.tsx +26 -0
- package/src/renderers/form/button.tsx +61 -0
- package/src/renderers/form/calendar.tsx +25 -0
- package/src/renderers/form/checkbox.tsx +41 -0
- package/src/renderers/form/date-picker.tsx +75 -0
- package/src/renderers/form/file-upload.tsx +175 -0
- package/src/renderers/form/form.tsx +417 -0
- package/src/renderers/form/index.ts +16 -0
- package/src/renderers/form/input-otp.tsx +31 -0
- package/src/renderers/form/input.tsx +79 -0
- package/src/renderers/form/label.tsx +36 -0
- package/src/renderers/form/radio-group.tsx +54 -0
- package/src/renderers/form/select.tsx +66 -0
- package/src/renderers/form/slider.tsx +45 -0
- package/src/renderers/form/switch.tsx +39 -0
- package/src/renderers/form/textarea.tsx +45 -0
- package/src/renderers/form/toggle.tsx +76 -0
- package/src/renderers/index.ts +9 -0
- package/src/renderers/layout/card.tsx +69 -0
- package/src/renderers/layout/container.tsx +113 -0
- package/src/renderers/layout/flex.tsx +123 -0
- package/src/renderers/layout/grid.tsx +155 -0
- package/src/renderers/layout/index.ts +10 -0
- package/src/renderers/layout/page.tsx +82 -0
- package/src/renderers/layout/semantic.tsx +39 -0
- package/src/renderers/layout/stack.tsx +123 -0
- package/src/renderers/layout/tabs.tsx +63 -0
- package/src/renderers/navigation/header-bar.tsx +50 -0
- package/src/renderers/navigation/index.ts +2 -0
- package/src/renderers/navigation/sidebar.tsx +189 -0
- package/src/renderers/overlay/alert-dialog.tsx +63 -0
- package/src/renderers/overlay/context-menu.tsx +91 -0
- package/src/renderers/overlay/dialog.tsx +68 -0
- package/src/renderers/overlay/drawer.tsx +68 -0
- package/src/renderers/overlay/dropdown-menu.tsx +90 -0
- package/src/renderers/overlay/hover-card.tsx +46 -0
- package/src/renderers/overlay/index.ts +9 -0
- package/src/renderers/overlay/popover.tsx +47 -0
- package/src/renderers/overlay/sheet.tsx +68 -0
- package/src/renderers/overlay/tooltip.tsx +58 -0
- package/src/ui/accordion.tsx +64 -0
- package/src/ui/alert-dialog.tsx +155 -0
- package/src/ui/alert.tsx +78 -0
- package/src/ui/aspect-ratio.tsx +11 -0
- package/src/ui/avatar.tsx +51 -0
- package/src/ui/badge.tsx +46 -0
- package/src/ui/breadcrumb.tsx +109 -0
- package/src/ui/button-group.tsx +83 -0
- package/src/ui/button.tsx +65 -0
- package/src/ui/calendar-view.tsx +503 -0
- package/src/ui/calendar.tsx +237 -0
- package/src/ui/card.tsx +138 -0
- package/src/ui/carousel.tsx +239 -0
- package/src/ui/chatbot.tsx +240 -0
- package/src/ui/checkbox.tsx +32 -0
- package/src/ui/collapsible.tsx +31 -0
- package/src/ui/command.tsx +182 -0
- package/src/ui/context-menu.tsx +247 -0
- package/src/ui/dialog.tsx +141 -0
- package/src/ui/drawer.tsx +135 -0
- package/src/ui/dropdown-menu.tsx +254 -0
- package/src/ui/empty.tsx +104 -0
- package/src/ui/field.tsx +246 -0
- package/src/ui/filter-builder.tsx +359 -0
- package/src/ui/form.tsx +167 -0
- package/src/ui/hover-card.tsx +44 -0
- package/src/ui/index.ts +56 -0
- package/src/ui/input-group.tsx +170 -0
- package/src/ui/input-otp.tsx +81 -0
- package/src/ui/input.tsx +24 -0
- package/src/ui/item.tsx +193 -0
- package/src/ui/kbd.tsx +28 -0
- package/src/ui/label.tsx +24 -0
- package/src/ui/menubar.tsx +274 -0
- package/src/ui/navigation-menu.tsx +168 -0
- package/src/ui/pagination.tsx +127 -0
- package/src/ui/popover.tsx +48 -0
- package/src/ui/progress.tsx +41 -0
- package/src/ui/radio-group.tsx +45 -0
- package/src/ui/resizable.tsx +55 -0
- package/src/ui/scroll-area.tsx +58 -0
- package/src/ui/select.tsx +188 -0
- package/src/ui/separator.tsx +31 -0
- package/src/ui/sheet.tsx +137 -0
- package/src/ui/sidebar.tsx +726 -0
- package/src/ui/skeleton.tsx +20 -0
- package/src/ui/slider.tsx +63 -0
- package/src/ui/sonner.tsx +43 -0
- package/src/ui/spinner.tsx +38 -0
- package/src/ui/switch.tsx +31 -0
- package/src/ui/table.tsx +120 -0
- package/src/ui/tabs.tsx +86 -0
- package/src/ui/textarea.tsx +18 -0
- package/src/ui/timeline.tsx +266 -0
- package/src/ui/toggle-group.tsx +87 -0
- package/src/ui/toggle.tsx +50 -0
- package/src/ui/tooltip.tsx +61 -0
- package/tailwind.config.js +75 -0
- package/tsconfig.json +18 -0
- 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';
|