@object-ui/react 3.3.0 → 3.3.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.
- package/CHANGELOG.md +9 -0
- package/README.md +21 -1
- package/dist/context/AppShellContext.d.ts +26 -0
- package/dist/context/AppShellContext.js +67 -0
- package/dist/context/index.d.ts +1 -7
- package/dist/context/index.js +1 -7
- package/package.json +32 -9
- package/.turbo/turbo-build.log +0 -4
- package/src/LazyPluginLoader.tsx +0 -174
- package/src/SchemaRenderer.tsx +0 -266
- package/src/__tests__/LazyPluginLoader.test.tsx +0 -176
- package/src/__tests__/SchemaRenderer.aria.test.tsx +0 -118
- package/src/__tests__/SchemaRenderer.debug.test.tsx +0 -69
- package/src/__tests__/SchemaRenderer.errorBoundary.test.tsx +0 -114
- package/src/__tests__/SchemaRenderer.expressions.test.tsx +0 -131
- package/src/__tests__/SchemaRendererProvider.smoke.test.tsx +0 -132
- package/src/__tests__/ThemeProvider.test.tsx +0 -451
- package/src/__tests__/useNavigationOverlay.test.ts +0 -578
- package/src/__tests__/useOffline.test.ts +0 -198
- package/src/__tests__/usePageTransition.test.ts +0 -214
- package/src/__tests__/usePageVariables.test.tsx +0 -334
- package/src/__tests__/usePerformance.test.ts +0 -137
- package/src/__tests__/useViewData.test.tsx +0 -422
- package/src/__tests__/view-switching.test.tsx +0 -456
- package/src/components/form/FieldFactory.tsx +0 -516
- package/src/components/form/FormRenderer.test.tsx +0 -744
- package/src/components/form/FormRenderer.tsx +0 -258
- package/src/components/form/README.md +0 -352
- package/src/components/form/index.ts +0 -12
- package/src/context/ActionContext.tsx +0 -213
- package/src/context/DndContext.tsx +0 -260
- package/src/context/NotificationContext.tsx +0 -280
- package/src/context/SchemaRendererContext.tsx +0 -48
- package/src/context/ThemeContext.tsx +0 -325
- package/src/context/__tests__/ActionContext.test.tsx +0 -174
- package/src/context/__tests__/DndContext.test.tsx +0 -150
- package/src/context/__tests__/NotificationContext.test.tsx +0 -176
- package/src/context/__tests__/useDataScope.test.tsx +0 -91
- package/src/context/index.ts +0 -13
- package/src/hooks/__tests__/useActionEngine.test.ts +0 -170
- package/src/hooks/__tests__/useActionRunner.test.ts +0 -153
- package/src/hooks/__tests__/useAnimation.test.ts +0 -82
- package/src/hooks/__tests__/useColumnSummary.test.ts +0 -93
- package/src/hooks/__tests__/useCrudShortcuts.test.ts +0 -85
- package/src/hooks/__tests__/useDataRefresh.test.ts +0 -115
- package/src/hooks/__tests__/useDebugMode.test.ts +0 -83
- package/src/hooks/__tests__/useDensityMode.test.ts +0 -81
- package/src/hooks/__tests__/useDiscovery.test.tsx +0 -247
- package/src/hooks/__tests__/useDynamicApp.test.ts +0 -161
- package/src/hooks/__tests__/useETagCache.test.ts +0 -153
- package/src/hooks/__tests__/useExpression.test.ts +0 -86
- package/src/hooks/__tests__/useFocusTrap.test.ts +0 -294
- package/src/hooks/__tests__/useKeyboardShortcuts.test.ts +0 -144
- package/src/hooks/__tests__/usePageTransition.test.ts +0 -158
- package/src/hooks/__tests__/usePerformanceBudget.test.ts +0 -157
- package/src/hooks/__tests__/useReducedMotion.test.ts +0 -104
- package/src/hooks/__tests__/useSchemaPersistence.test.ts +0 -220
- package/src/hooks/__tests__/useViewSharing.test.ts +0 -121
- package/src/hooks/__tests__/useViewTransition.test.ts +0 -61
- package/src/hooks/index.ts +0 -36
- package/src/hooks/useActionEngine.ts +0 -84
- package/src/hooks/useActionRunner.ts +0 -119
- package/src/hooks/useAnimation.ts +0 -117
- package/src/hooks/useClientNotifications.ts +0 -251
- package/src/hooks/useColumnSummary.ts +0 -87
- package/src/hooks/useCrudShortcuts.ts +0 -126
- package/src/hooks/useDataRefresh.ts +0 -58
- package/src/hooks/useDebugMode.ts +0 -95
- package/src/hooks/useDensityMode.ts +0 -102
- package/src/hooks/useDiscovery.ts +0 -168
- package/src/hooks/useDynamicApp.ts +0 -142
- package/src/hooks/useETagCache.ts +0 -404
- package/src/hooks/useExpression.ts +0 -56
- package/src/hooks/useFocusManagement.ts +0 -248
- package/src/hooks/useFocusTrap.ts +0 -138
- package/src/hooks/useGlobalUndo.ts +0 -98
- package/src/hooks/useKeyboardShortcuts.ts +0 -119
- package/src/hooks/useNavigationOverlay.ts +0 -190
- package/src/hooks/useOffline.ts +0 -301
- package/src/hooks/usePageTransition.ts +0 -187
- package/src/hooks/usePageVariables.tsx +0 -157
- package/src/hooks/usePerformance.ts +0 -246
- package/src/hooks/usePerformanceBudget.ts +0 -313
- package/src/hooks/useReducedMotion.ts +0 -53
- package/src/hooks/useSchemaPersistence.ts +0 -222
- package/src/hooks/useViewData.ts +0 -201
- package/src/hooks/useViewSharing.ts +0 -140
- package/src/hooks/useViewTransition.ts +0 -144
- package/src/index.ts +0 -41
- package/src/spec-bridge/SpecBridge.ts +0 -64
- package/src/spec-bridge/__tests__/P1SpecBridge.test.ts +0 -855
- package/src/spec-bridge/__tests__/SpecBridge.test.ts +0 -550
- package/src/spec-bridge/bridges/dashboard.ts +0 -180
- package/src/spec-bridge/bridges/form-view.ts +0 -109
- package/src/spec-bridge/bridges/list-view.ts +0 -187
- package/src/spec-bridge/bridges/page.ts +0 -136
- package/src/spec-bridge/index.ts +0 -14
- package/src/spec-bridge/types.ts +0 -38
- package/src/utils/i18n.ts +0 -10
- package/tsconfig.json +0 -17
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -2
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { useForm, FormProvider, UseFormReturn } from 'react-hook-form';
|
|
11
|
-
import type { FormView, FormSection } from '@objectstack/spec/ui';
|
|
12
|
-
import { FieldFactory } from './FieldFactory';
|
|
13
|
-
import { ExpressionEvaluator } from '@object-ui/core';
|
|
14
|
-
import { resolveI18nLabel } from '../../utils/i18n';
|
|
15
|
-
|
|
16
|
-
export interface FormRendererProps {
|
|
17
|
-
/**
|
|
18
|
-
* The FormView schema from @objectstack/spec
|
|
19
|
-
*/
|
|
20
|
-
schema: FormView;
|
|
21
|
-
/**
|
|
22
|
-
* Initial form data
|
|
23
|
-
*/
|
|
24
|
-
data?: Record<string, any>;
|
|
25
|
-
/**
|
|
26
|
-
* Form submission handler
|
|
27
|
-
*/
|
|
28
|
-
onSubmit?: (data: Record<string, any>) => void | Promise<void>;
|
|
29
|
-
/**
|
|
30
|
-
* Form change handler
|
|
31
|
-
*/
|
|
32
|
-
onChange?: (data: Record<string, any>) => void;
|
|
33
|
-
/**
|
|
34
|
-
* Custom class name for the form
|
|
35
|
-
*/
|
|
36
|
-
className?: string;
|
|
37
|
-
/**
|
|
38
|
-
* Whether the form is disabled
|
|
39
|
-
*/
|
|
40
|
-
disabled?: boolean;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* FormRenderer component that renders forms based on FormViewSchema
|
|
45
|
-
* from @objectstack/spec
|
|
46
|
-
*/
|
|
47
|
-
export const FormRenderer: React.FC<FormRendererProps> = ({
|
|
48
|
-
schema,
|
|
49
|
-
data = {},
|
|
50
|
-
onSubmit,
|
|
51
|
-
onChange,
|
|
52
|
-
className = '',
|
|
53
|
-
disabled = false,
|
|
54
|
-
}) => {
|
|
55
|
-
// Create form methods with react-hook-form
|
|
56
|
-
const methods = useForm({
|
|
57
|
-
defaultValues: data,
|
|
58
|
-
mode: 'onChange',
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const { handleSubmit, watch } = methods;
|
|
62
|
-
|
|
63
|
-
// Watch for form changes
|
|
64
|
-
React.useEffect(() => {
|
|
65
|
-
if (onChange) {
|
|
66
|
-
const subscription = watch((value) => {
|
|
67
|
-
onChange(value as Record<string, any>);
|
|
68
|
-
});
|
|
69
|
-
return () => subscription.unsubscribe();
|
|
70
|
-
}
|
|
71
|
-
}, [watch, onChange]);
|
|
72
|
-
|
|
73
|
-
// Handle form submission
|
|
74
|
-
const onSubmitForm = handleSubmit(async (formData) => {
|
|
75
|
-
if (onSubmit) {
|
|
76
|
-
await onSubmit(formData);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Render sections or fields
|
|
81
|
-
const renderContent = () => {
|
|
82
|
-
if (!schema.sections || schema.sections.length === 0) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<div className="space-y-6">
|
|
88
|
-
{schema.sections.map((section, index) => (
|
|
89
|
-
<FormSectionRenderer
|
|
90
|
-
key={index}
|
|
91
|
-
section={section}
|
|
92
|
-
methods={methods}
|
|
93
|
-
disabled={disabled}
|
|
94
|
-
/>
|
|
95
|
-
))}
|
|
96
|
-
</div>
|
|
97
|
-
);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<FormProvider {...methods}>
|
|
102
|
-
<form onSubmit={onSubmitForm} className={`space-y-6 ${className}`}>
|
|
103
|
-
{renderContent()}
|
|
104
|
-
|
|
105
|
-
{/* Submit button - optional, can be customized */}
|
|
106
|
-
<div className="flex justify-end gap-2">
|
|
107
|
-
<button
|
|
108
|
-
type="submit"
|
|
109
|
-
disabled={disabled}
|
|
110
|
-
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
111
|
-
>
|
|
112
|
-
Submit
|
|
113
|
-
</button>
|
|
114
|
-
</div>
|
|
115
|
-
</form>
|
|
116
|
-
</FormProvider>
|
|
117
|
-
);
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
interface FormSectionRendererProps {
|
|
121
|
-
section: FormSection;
|
|
122
|
-
methods: UseFormReturn<any>;
|
|
123
|
-
disabled?: boolean;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Grid column classes for different column counts
|
|
127
|
-
const GRID_COLS = {
|
|
128
|
-
1: 'grid-cols-1',
|
|
129
|
-
2: 'grid-cols-1 md:grid-cols-2',
|
|
130
|
-
3: 'grid-cols-1 md:grid-cols-3',
|
|
131
|
-
4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
|
|
132
|
-
} as const;
|
|
133
|
-
|
|
134
|
-
// Column span classes
|
|
135
|
-
const COL_SPAN = {
|
|
136
|
-
1: 'col-span-1',
|
|
137
|
-
2: 'col-span-2',
|
|
138
|
-
3: 'col-span-3',
|
|
139
|
-
4: 'col-span-4',
|
|
140
|
-
} as const;
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Evaluate whether a field should be visible based on visibleOn expression
|
|
144
|
-
* and dependsOn parent field value.
|
|
145
|
-
*/
|
|
146
|
-
function evaluateFieldVisibility(
|
|
147
|
-
fieldConfig: any,
|
|
148
|
-
formValues: Record<string, any>,
|
|
149
|
-
): boolean {
|
|
150
|
-
// If explicitly hidden, skip
|
|
151
|
-
if (fieldConfig.hidden) {
|
|
152
|
-
return false;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Evaluate visibleOn expression
|
|
156
|
-
if (fieldConfig.visibleOn) {
|
|
157
|
-
try {
|
|
158
|
-
const evaluator = new ExpressionEvaluator({ data: formValues, form: formValues });
|
|
159
|
-
return evaluator.evaluateCondition(fieldConfig.visibleOn);
|
|
160
|
-
} catch {
|
|
161
|
-
// On error, default to visible
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Evaluate dependsOn: field is hidden if parent field has no value
|
|
167
|
-
if (fieldConfig.dependsOn) {
|
|
168
|
-
const parentValue = formValues[fieldConfig.dependsOn];
|
|
169
|
-
return parentValue !== undefined && parentValue !== null && parentValue !== '';
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Renders a form section with grid layout
|
|
177
|
-
*/
|
|
178
|
-
const FormSectionRenderer: React.FC<FormSectionRendererProps> = ({
|
|
179
|
-
section,
|
|
180
|
-
methods,
|
|
181
|
-
disabled = false,
|
|
182
|
-
}) => {
|
|
183
|
-
const [isCollapsed, setIsCollapsed] = React.useState(section.collapsed || false);
|
|
184
|
-
|
|
185
|
-
// Determine grid columns based on section.columns
|
|
186
|
-
const gridCols = GRID_COLS[section.columns || 1];
|
|
187
|
-
|
|
188
|
-
// Collect whether there are any conditional fields
|
|
189
|
-
const hasConditionalFields = React.useMemo(() => {
|
|
190
|
-
return section.fields.some((field) => {
|
|
191
|
-
if (typeof field === 'string') return false;
|
|
192
|
-
return !!field.dependsOn || !!field.visibleOn;
|
|
193
|
-
});
|
|
194
|
-
}, [section.fields]);
|
|
195
|
-
|
|
196
|
-
// Watch form values for conditional field evaluation
|
|
197
|
-
// Only watch when there are fields with dependsOn or visibleOn to avoid
|
|
198
|
-
// unnecessary re-renders in forms without conditional fields.
|
|
199
|
-
const allFormValues = methods.watch();
|
|
200
|
-
const formValues = hasConditionalFields ? allFormValues : undefined;
|
|
201
|
-
|
|
202
|
-
const handleToggleCollapse = () => {
|
|
203
|
-
if (section.collapsible) {
|
|
204
|
-
setIsCollapsed(!isCollapsed);
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
return (
|
|
209
|
-
<div className="border rounded-lg p-4">
|
|
210
|
-
{section.label && (
|
|
211
|
-
<div
|
|
212
|
-
className={`flex items-center justify-between mb-4 ${
|
|
213
|
-
section.collapsible ? 'cursor-pointer' : ''
|
|
214
|
-
}`}
|
|
215
|
-
onClick={handleToggleCollapse}
|
|
216
|
-
>
|
|
217
|
-
<h3 className="text-lg font-semibold">{resolveI18nLabel(section.label)}</h3>
|
|
218
|
-
{section.collapsible && (
|
|
219
|
-
<span className="text-sm text-gray-500">
|
|
220
|
-
{isCollapsed ? '▶' : '▼'}
|
|
221
|
-
</span>
|
|
222
|
-
)}
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
225
|
-
|
|
226
|
-
{!isCollapsed && (
|
|
227
|
-
<div className={`grid ${gridCols} gap-4`}>
|
|
228
|
-
{section.fields.map((field, index) => {
|
|
229
|
-
// Handle both string fields (legacy) and FormFieldSchema objects
|
|
230
|
-
const fieldName = typeof field === 'string' ? field : field.field;
|
|
231
|
-
const fieldConfig = typeof field === 'string' ? { field: fieldName } : field;
|
|
232
|
-
|
|
233
|
-
// Evaluate visibility: static hidden, visibleOn expression, dependsOn parent
|
|
234
|
-
if (!evaluateFieldVisibility(fieldConfig, formValues || {})) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Calculate colSpan for grid
|
|
239
|
-
const colSpan = fieldConfig.colSpan || 1;
|
|
240
|
-
const colSpanClass = COL_SPAN[Math.min(colSpan, section.columns || 1) as keyof typeof COL_SPAN];
|
|
241
|
-
|
|
242
|
-
return (
|
|
243
|
-
<div key={`${fieldName}-${index}`} className={colSpanClass}>
|
|
244
|
-
<FieldFactory
|
|
245
|
-
field={fieldConfig}
|
|
246
|
-
methods={methods}
|
|
247
|
-
disabled={disabled || fieldConfig.readonly}
|
|
248
|
-
/>
|
|
249
|
-
</div>
|
|
250
|
-
);
|
|
251
|
-
})}
|
|
252
|
-
</div>
|
|
253
|
-
)}
|
|
254
|
-
</div>
|
|
255
|
-
);
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
FormRenderer.displayName = 'FormRenderer';
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
# FormRenderer Component
|
|
2
|
-
|
|
3
|
-
The `FormRenderer` component is a React component that renders forms based on the `FormView` schema from `@objectstack/spec`. It provides a flexible and powerful way to create forms with minimal code.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- ✅ **Schema-driven**: Define forms using JSON schema from `@objectstack/spec`
|
|
8
|
-
- ✅ **Multi-column layouts**: Support for 1-4 column grid layouts
|
|
9
|
-
- ✅ **Collapsible sections**: Organize forms with collapsible sections
|
|
10
|
-
- ✅ **Form state management**: Built-in state management using `react-hook-form`
|
|
11
|
-
- ✅ **Multiple field types**: Text, Number, Checkbox, Textarea, Select, Date, DateTime, Time
|
|
12
|
-
- ⏳ **Conditional fields**: Show/hide fields based on conditions (planned)
|
|
13
|
-
- ⏳ **Schema validation**: Zod validation support (planned)
|
|
14
|
-
- ✅ **Column spanning**: Fields can span multiple columns
|
|
15
|
-
- ✅ **Help text**: Display helpful descriptions for fields
|
|
16
|
-
- ✅ **Required fields**: Mark fields as required with validation
|
|
17
|
-
|
|
18
|
-
## Installation
|
|
19
|
-
|
|
20
|
-
The FormRenderer component is part of the `@object-ui/react` package:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install @object-ui/react @objectstack/spec react-hook-form
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Or with pnpm:
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pnpm add @object-ui/react @objectstack/spec react-hook-form
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Basic Usage
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
import { FormRenderer } from '@object-ui/react';
|
|
36
|
-
import type { FormView } from '@objectstack/spec/ui';
|
|
37
|
-
|
|
38
|
-
const schema: FormView = {
|
|
39
|
-
type: 'simple',
|
|
40
|
-
sections: [
|
|
41
|
-
{
|
|
42
|
-
label: 'Contact Information',
|
|
43
|
-
collapsible: false,
|
|
44
|
-
collapsed: false,
|
|
45
|
-
columns: 2,
|
|
46
|
-
fields: [
|
|
47
|
-
{
|
|
48
|
-
field: 'firstName',
|
|
49
|
-
label: 'First Name',
|
|
50
|
-
required: true,
|
|
51
|
-
widget: 'text',
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
field: 'lastName',
|
|
55
|
-
label: 'Last Name',
|
|
56
|
-
required: true,
|
|
57
|
-
widget: 'text',
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
field: 'email',
|
|
61
|
-
label: 'Email',
|
|
62
|
-
required: true,
|
|
63
|
-
widget: 'email',
|
|
64
|
-
helpText: 'We will never share your email',
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
function MyForm() {
|
|
72
|
-
const handleSubmit = (data: Record<string, any>) => {
|
|
73
|
-
console.log('Form submitted:', data);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
return (
|
|
77
|
-
<FormRenderer
|
|
78
|
-
schema={schema}
|
|
79
|
-
onSubmit={handleSubmit}
|
|
80
|
-
/>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Props
|
|
86
|
-
|
|
87
|
-
### FormRendererProps
|
|
88
|
-
|
|
89
|
-
| Prop | Type | Required | Description |
|
|
90
|
-
|------|------|----------|-------------|
|
|
91
|
-
| `schema` | `FormView` | Yes | The FormView schema from @objectstack/spec |
|
|
92
|
-
| `data` | `Record<string, any>` | No | Initial form data |
|
|
93
|
-
| `onSubmit` | `(data: Record<string, any>) => void \| Promise<void>` | No | Form submission handler |
|
|
94
|
-
| `onChange` | `(data: Record<string, any>) => void` | No | Form change handler |
|
|
95
|
-
| `className` | `string` | No | Custom CSS class for the form |
|
|
96
|
-
| `disabled` | `boolean` | No | Whether the form is disabled |
|
|
97
|
-
|
|
98
|
-
## Schema Structure
|
|
99
|
-
|
|
100
|
-
### FormView
|
|
101
|
-
|
|
102
|
-
The main schema that defines the entire form.
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
interface FormView {
|
|
106
|
-
type: 'simple' | 'tabbed' | 'wizard';
|
|
107
|
-
sections?: FormSection[];
|
|
108
|
-
data?: DataSource; // Optional data source configuration
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### FormSection
|
|
113
|
-
|
|
114
|
-
Defines a section of the form with its own layout.
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
interface FormSection {
|
|
118
|
-
label?: string;
|
|
119
|
-
collapsible?: boolean;
|
|
120
|
-
collapsed?: boolean;
|
|
121
|
-
columns: 1 | 2 | 3 | 4;
|
|
122
|
-
fields: (string | FormField)[];
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### FormField
|
|
127
|
-
|
|
128
|
-
Defines individual form fields.
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
interface FormField {
|
|
132
|
-
field: string; // Field name
|
|
133
|
-
label?: string;
|
|
134
|
-
placeholder?: string;
|
|
135
|
-
helpText?: string;
|
|
136
|
-
readonly?: boolean;
|
|
137
|
-
required?: boolean;
|
|
138
|
-
hidden?: boolean;
|
|
139
|
-
colSpan?: number; // Number of columns to span (1-4)
|
|
140
|
-
widget?: string; // Widget type (text, number, checkbox, etc.)
|
|
141
|
-
dependsOn?: string; // Field dependency
|
|
142
|
-
visibleOn?: string; // Visibility condition
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Supported Widget Types
|
|
147
|
-
|
|
148
|
-
The `FieldFactory` component supports the following widget types:
|
|
149
|
-
|
|
150
|
-
### Text Input Types
|
|
151
|
-
- `text`, `string` - Basic text input
|
|
152
|
-
- `email` - Email input with validation
|
|
153
|
-
- `password` - Password input (masked)
|
|
154
|
-
- `url` - URL input
|
|
155
|
-
- `tel`, `phone` - Telephone/phone number input
|
|
156
|
-
- `textarea` - Multi-line text input
|
|
157
|
-
- `markdown` - Markdown text editor (textarea with monospace font)
|
|
158
|
-
- `html` - HTML/Rich text editor (textarea with monospace font)
|
|
159
|
-
|
|
160
|
-
### Numeric Input Types
|
|
161
|
-
- `number`, `integer`, `float` - Number input
|
|
162
|
-
- `currency` - Currency input with $ prefix
|
|
163
|
-
- `percent` - Percentage input with % suffix
|
|
164
|
-
|
|
165
|
-
### Boolean & Selection Types
|
|
166
|
-
- `checkbox`, `boolean` - Checkbox input
|
|
167
|
-
- `select`, `dropdown` - Select dropdown
|
|
168
|
-
|
|
169
|
-
### Date & Time Types
|
|
170
|
-
- `date` - Date picker
|
|
171
|
-
- `datetime`, `datetime-local` - DateTime picker
|
|
172
|
-
- `time` - Time picker
|
|
173
|
-
|
|
174
|
-
### File Upload Types
|
|
175
|
-
- `file` - File upload input
|
|
176
|
-
- `image` - Image upload input (defaults to image/* mime types)
|
|
177
|
-
|
|
178
|
-
### Relationship Types
|
|
179
|
-
- `lookup` - Lookup/reference field (select with options)
|
|
180
|
-
- `master_detail` - Master-detail relationship (select with options)
|
|
181
|
-
- `user`, `owner` - User selection field (select with options)
|
|
182
|
-
|
|
183
|
-
### Complex & Special Types
|
|
184
|
-
- `location` - Geographic location (latitude/longitude inputs)
|
|
185
|
-
- `object` - JSON object editor (textarea with JSON validation)
|
|
186
|
-
- `formula` - Read-only computed field
|
|
187
|
-
- `summary` - Read-only aggregation/rollup field
|
|
188
|
-
- `auto_number` - Read-only auto-generated number
|
|
189
|
-
- `vector` - Vector/embedding display (read-only)
|
|
190
|
-
- `grid` - Sub-table/grid editor (placeholder)
|
|
191
|
-
|
|
192
|
-
## Extended Field Properties
|
|
193
|
-
|
|
194
|
-
Some field types support additional properties beyond the base FormField schema:
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
interface ExtendedFormField extends FormField {
|
|
198
|
-
// For file/image uploads
|
|
199
|
-
multiple?: boolean;
|
|
200
|
-
accept?: string[];
|
|
201
|
-
|
|
202
|
-
// For select/lookup/user fields
|
|
203
|
-
options?: Array<{
|
|
204
|
-
label: string;
|
|
205
|
-
value: string;
|
|
206
|
-
disabled?: boolean;
|
|
207
|
-
}>;
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
Example usage with extended properties:
|
|
212
|
-
|
|
213
|
-
```tsx
|
|
214
|
-
{
|
|
215
|
-
field: 'avatar',
|
|
216
|
-
label: 'Profile Picture',
|
|
217
|
-
widget: 'image',
|
|
218
|
-
accept: ['image/png', 'image/jpeg'],
|
|
219
|
-
multiple: false
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
{
|
|
223
|
-
field: 'account',
|
|
224
|
-
label: 'Account',
|
|
225
|
-
widget: 'lookup',
|
|
226
|
-
options: [
|
|
227
|
-
{ label: 'Account 1', value: '1' },
|
|
228
|
-
{ label: 'Account 2', value: '2' }
|
|
229
|
-
]
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Known Limitations
|
|
234
|
-
|
|
235
|
-
### Version 0.3.0
|
|
236
|
-
|
|
237
|
-
- **Conditional Visibility**: The `visibleOn` and `dependsOn` properties are not yet implemented. Fields are visible by default unless explicitly marked as `hidden`.
|
|
238
|
-
- **Advanced Select Options**: While select/lookup/user fields support options via the `ExtendedFormField` interface, they don't yet support advanced features like search, async loading, or custom rendering.
|
|
239
|
-
- **Rich Text Editors**: The `markdown` and `html` widgets currently use plain textareas with monospace fonts. Full WYSIWYG editors are planned for future releases.
|
|
240
|
-
- **File Upload Preview**: File and image widgets support uploads but don't yet show previews or provide upload progress indicators.
|
|
241
|
-
- **Location Maps**: The location widget provides lat/lng inputs but doesn't include map visualization or geocoding.
|
|
242
|
-
- **Grid Editor**: The grid widget is a placeholder and doesn't yet provide spreadsheet-like editing capabilities.
|
|
243
|
-
- **Validation**: Currently only basic required field validation is supported via react-hook-form. Advanced Zod schema validation is planned for future releases.
|
|
244
|
-
|
|
245
|
-
These features are planned for future releases.
|
|
246
|
-
|
|
247
|
-
## Examples
|
|
248
|
-
|
|
249
|
-
### Multi-Section Form
|
|
250
|
-
|
|
251
|
-
```tsx
|
|
252
|
-
const schema: FormView = {
|
|
253
|
-
type: 'simple',
|
|
254
|
-
sections: [
|
|
255
|
-
{
|
|
256
|
-
label: 'Personal Information',
|
|
257
|
-
collapsible: true,
|
|
258
|
-
collapsed: false,
|
|
259
|
-
columns: 2,
|
|
260
|
-
fields: [
|
|
261
|
-
{ field: 'firstName', label: 'First Name', required: true, widget: 'text' },
|
|
262
|
-
{ field: 'lastName', label: 'Last Name', required: true, widget: 'text' },
|
|
263
|
-
{ field: 'email', label: 'Email', required: true, widget: 'email' },
|
|
264
|
-
{ field: 'phone', label: 'Phone', widget: 'tel' },
|
|
265
|
-
],
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
label: 'Preferences',
|
|
269
|
-
collapsible: true,
|
|
270
|
-
collapsed: true,
|
|
271
|
-
columns: 1,
|
|
272
|
-
fields: [
|
|
273
|
-
{
|
|
274
|
-
field: 'newsletter',
|
|
275
|
-
label: 'Subscribe to newsletter',
|
|
276
|
-
widget: 'checkbox',
|
|
277
|
-
helpText: 'Receive updates about our products',
|
|
278
|
-
},
|
|
279
|
-
],
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
};
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
### Form with Initial Data
|
|
286
|
-
|
|
287
|
-
```tsx
|
|
288
|
-
const initialData = {
|
|
289
|
-
firstName: 'John',
|
|
290
|
-
lastName: 'Doe',
|
|
291
|
-
email: 'john@example.com',
|
|
292
|
-
newsletter: true,
|
|
293
|
-
};
|
|
294
|
-
|
|
295
|
-
<FormRenderer
|
|
296
|
-
schema={schema}
|
|
297
|
-
data={initialData}
|
|
298
|
-
onSubmit={handleSubmit}
|
|
299
|
-
/>
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
### Form with Column Spanning
|
|
303
|
-
|
|
304
|
-
```tsx
|
|
305
|
-
const schema: FormView = {
|
|
306
|
-
type: 'simple',
|
|
307
|
-
sections: [
|
|
308
|
-
{
|
|
309
|
-
label: 'Contact',
|
|
310
|
-
columns: 2,
|
|
311
|
-
fields: [
|
|
312
|
-
{ field: 'name', label: 'Name', widget: 'text' },
|
|
313
|
-
{ field: 'email', label: 'Email', widget: 'email' },
|
|
314
|
-
{
|
|
315
|
-
field: 'message',
|
|
316
|
-
label: 'Message',
|
|
317
|
-
widget: 'textarea',
|
|
318
|
-
colSpan: 2, // Spans both columns
|
|
319
|
-
},
|
|
320
|
-
],
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
};
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
## Architecture
|
|
327
|
-
|
|
328
|
-
The FormRenderer is built on top of:
|
|
329
|
-
|
|
330
|
-
- **react-hook-form**: For form state management and validation
|
|
331
|
-
- **@objectstack/spec**: For schema definitions
|
|
332
|
-
|
|
333
|
-
## Customization
|
|
334
|
-
|
|
335
|
-
You can customize the appearance of the form by:
|
|
336
|
-
|
|
337
|
-
1. Using the `className` prop on the FormRenderer
|
|
338
|
-
2. Using Tailwind CSS classes in your application
|
|
339
|
-
3. Extending the FieldFactory component for custom widgets
|
|
340
|
-
|
|
341
|
-
## Related Components
|
|
342
|
-
|
|
343
|
-
- `FieldFactory`: Renders individual form fields based on widget type
|
|
344
|
-
- `SchemaRenderer`: General-purpose schema renderer for ObjectUI
|
|
345
|
-
|
|
346
|
-
## License
|
|
347
|
-
|
|
348
|
-
MIT
|
|
349
|
-
|
|
350
|
-
## Contributing
|
|
351
|
-
|
|
352
|
-
Contributions are welcome! Please read the [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export { FormRenderer } from './FormRenderer';
|
|
10
|
-
export type { FormRendererProps } from './FormRenderer';
|
|
11
|
-
export { FieldFactory } from './FieldFactory';
|
|
12
|
-
export type { FieldFactoryProps, ExtendedFormField } from './FieldFactory';
|