@object-ui/react 3.1.5 → 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 +25 -0
- package/README.md +28 -3
- package/dist/SchemaRenderer.js +2 -2
- package/dist/components/form/FieldFactory.js +5 -2
- 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/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useDataRefresh.d.ts +36 -0
- package/dist/hooks/useDataRefresh.js +50 -0
- package/dist/hooks/useDiscovery.d.ts +12 -0
- package/dist/hooks/useDiscovery.js +6 -0
- package/dist/hooks/usePerformanceBudget.js +1 -1
- 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 -509
- 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__/useDebugMode.test.ts +0 -83
- package/src/hooks/__tests__/useDensityMode.test.ts +0 -81
- package/src/hooks/__tests__/useDiscovery.test.tsx +0 -149
- 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 -35
- 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/useDebugMode.ts +0 -95
- package/src/hooks/useDensityMode.ts +0 -102
- package/src/hooks/useDiscovery.ts +0 -154
- 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,509 +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 { UseFormReturn } from 'react-hook-form';
|
|
11
|
-
import type { FormField } from '@objectstack/spec/ui';
|
|
12
|
-
import { resolveI18nLabel } from '../../utils/i18n';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Extended form field with additional properties for complex widgets
|
|
16
|
-
* These properties are not part of the standard FormField schema but may be
|
|
17
|
-
* provided by specific implementations
|
|
18
|
-
*/
|
|
19
|
-
export interface ExtendedFormField extends FormField {
|
|
20
|
-
multiple?: boolean;
|
|
21
|
-
accept?: string[];
|
|
22
|
-
options?: Array<{
|
|
23
|
-
label: string;
|
|
24
|
-
value: string;
|
|
25
|
-
disabled?: boolean;
|
|
26
|
-
}>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FieldFactoryProps {
|
|
30
|
-
/**
|
|
31
|
-
* Field configuration from FormFieldSchema
|
|
32
|
-
*/
|
|
33
|
-
field: FormField;
|
|
34
|
-
/**
|
|
35
|
-
* React Hook Form methods
|
|
36
|
-
*/
|
|
37
|
-
methods: UseFormReturn<any>;
|
|
38
|
-
/**
|
|
39
|
-
* Whether the field is disabled
|
|
40
|
-
*/
|
|
41
|
-
disabled?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* FieldFactory component that renders different input types based on
|
|
46
|
-
* the widget property or field type
|
|
47
|
-
*/
|
|
48
|
-
export const FieldFactory: React.FC<FieldFactoryProps> = ({
|
|
49
|
-
field,
|
|
50
|
-
methods,
|
|
51
|
-
disabled = false,
|
|
52
|
-
}) => {
|
|
53
|
-
const { register, formState: { errors } } = methods;
|
|
54
|
-
|
|
55
|
-
// Cast to extended field for properties not in base schema
|
|
56
|
-
const extendedField = field as ExtendedFormField;
|
|
57
|
-
|
|
58
|
-
// Determine the widget type
|
|
59
|
-
const widgetType = field.widget || 'text';
|
|
60
|
-
const fieldName = field.field;
|
|
61
|
-
const error = errors[fieldName];
|
|
62
|
-
|
|
63
|
-
// Resolve I18nLabel values to plain strings
|
|
64
|
-
const fieldLabel = resolveI18nLabel(field.label);
|
|
65
|
-
const fieldPlaceholder = resolveI18nLabel(field.placeholder);
|
|
66
|
-
const fieldHelpText = resolveI18nLabel(field.helpText);
|
|
67
|
-
|
|
68
|
-
// Helper function to handle multiple select value conversion
|
|
69
|
-
const handleMultipleSelectValue = (value: any) => {
|
|
70
|
-
if (extendedField.multiple && value instanceof HTMLCollection) {
|
|
71
|
-
return Array.from(value as HTMLCollectionOf<HTMLOptionElement>).map((opt) => opt.value);
|
|
72
|
-
}
|
|
73
|
-
return value;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Handle conditional visibility
|
|
77
|
-
// Fields are hidden when explicitly hidden or when visibleOn evaluates to false
|
|
78
|
-
// Note: dependsOn is handled at the FormSectionRenderer level
|
|
79
|
-
if (field.hidden) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Common field wrapper
|
|
84
|
-
const renderField = (input: React.ReactNode) => (
|
|
85
|
-
<div className="space-y-2">
|
|
86
|
-
{fieldLabel && (
|
|
87
|
-
<label htmlFor={fieldName} className="block text-sm font-medium text-gray-700">
|
|
88
|
-
{fieldLabel}
|
|
89
|
-
{field.required && <span className="text-red-500 ml-1">*</span>}
|
|
90
|
-
</label>
|
|
91
|
-
)}
|
|
92
|
-
{input}
|
|
93
|
-
{fieldHelpText && (
|
|
94
|
-
<p className="text-sm text-gray-500">{fieldHelpText}</p>
|
|
95
|
-
)}
|
|
96
|
-
{error && (
|
|
97
|
-
<p className="text-sm text-red-600">{error.message as string}</p>
|
|
98
|
-
)}
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// Render based on widget type
|
|
103
|
-
switch (widgetType.toLowerCase()) {
|
|
104
|
-
case 'text':
|
|
105
|
-
case 'string':
|
|
106
|
-
case 'email':
|
|
107
|
-
case 'password':
|
|
108
|
-
case 'url':
|
|
109
|
-
case 'tel':
|
|
110
|
-
return renderField(
|
|
111
|
-
<input
|
|
112
|
-
id={fieldName}
|
|
113
|
-
type={widgetType === 'string' ? 'text' : widgetType}
|
|
114
|
-
placeholder={fieldPlaceholder}
|
|
115
|
-
disabled={disabled}
|
|
116
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
117
|
-
{...register(fieldName, {
|
|
118
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
119
|
-
})}
|
|
120
|
-
/>
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
case 'number':
|
|
124
|
-
case 'integer':
|
|
125
|
-
case 'float':
|
|
126
|
-
return renderField(
|
|
127
|
-
<input
|
|
128
|
-
id={fieldName}
|
|
129
|
-
type="number"
|
|
130
|
-
placeholder={fieldPlaceholder}
|
|
131
|
-
disabled={disabled}
|
|
132
|
-
step={widgetType === 'integer' ? '1' : 'any'}
|
|
133
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
134
|
-
{...register(fieldName, {
|
|
135
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
136
|
-
valueAsNumber: true,
|
|
137
|
-
})}
|
|
138
|
-
/>
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
case 'checkbox':
|
|
142
|
-
case 'boolean':
|
|
143
|
-
return (
|
|
144
|
-
<div className="flex items-start space-x-2">
|
|
145
|
-
<input
|
|
146
|
-
id={fieldName}
|
|
147
|
-
type="checkbox"
|
|
148
|
-
disabled={disabled}
|
|
149
|
-
className="mt-1 h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
|
|
150
|
-
{...register(fieldName)}
|
|
151
|
-
/>
|
|
152
|
-
<div className="flex-1">
|
|
153
|
-
{fieldLabel && (
|
|
154
|
-
<label htmlFor={fieldName} className="text-sm font-medium text-gray-700">
|
|
155
|
-
{fieldLabel}
|
|
156
|
-
{field.required && <span className="text-red-500 ml-1">*</span>}
|
|
157
|
-
</label>
|
|
158
|
-
)}
|
|
159
|
-
{fieldHelpText && (
|
|
160
|
-
<p className="text-sm text-gray-500 mt-1">{fieldHelpText}</p>
|
|
161
|
-
)}
|
|
162
|
-
{error && (
|
|
163
|
-
<p className="text-sm text-red-600 mt-1">{error.message as string}</p>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
</div>
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
case 'textarea':
|
|
170
|
-
return renderField(
|
|
171
|
-
<textarea
|
|
172
|
-
id={fieldName}
|
|
173
|
-
placeholder={fieldPlaceholder}
|
|
174
|
-
disabled={disabled}
|
|
175
|
-
rows={4}
|
|
176
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
177
|
-
{...register(fieldName, {
|
|
178
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
179
|
-
})}
|
|
180
|
-
/>
|
|
181
|
-
);
|
|
182
|
-
|
|
183
|
-
case 'select':
|
|
184
|
-
case 'dropdown':
|
|
185
|
-
return renderField(
|
|
186
|
-
<select
|
|
187
|
-
id={fieldName}
|
|
188
|
-
disabled={disabled}
|
|
189
|
-
multiple={extendedField.multiple}
|
|
190
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
191
|
-
{...register(fieldName, {
|
|
192
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
193
|
-
setValueAs: handleMultipleSelectValue,
|
|
194
|
-
})}
|
|
195
|
-
>
|
|
196
|
-
{!extendedField.multiple && <option value="">{fieldPlaceholder || 'Select an option'}</option>}
|
|
197
|
-
{extendedField.options?.map((option) => (
|
|
198
|
-
<option key={option.value} value={option.value} disabled={option.disabled}>
|
|
199
|
-
{option.label}
|
|
200
|
-
</option>
|
|
201
|
-
))}
|
|
202
|
-
</select>
|
|
203
|
-
);
|
|
204
|
-
|
|
205
|
-
case 'date':
|
|
206
|
-
return renderField(
|
|
207
|
-
<input
|
|
208
|
-
id={fieldName}
|
|
209
|
-
type="date"
|
|
210
|
-
placeholder={fieldPlaceholder}
|
|
211
|
-
disabled={disabled}
|
|
212
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
213
|
-
{...register(fieldName, {
|
|
214
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
215
|
-
})}
|
|
216
|
-
/>
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
case 'datetime':
|
|
220
|
-
case 'datetime-local':
|
|
221
|
-
return renderField(
|
|
222
|
-
<input
|
|
223
|
-
id={fieldName}
|
|
224
|
-
type="datetime-local"
|
|
225
|
-
placeholder={fieldPlaceholder}
|
|
226
|
-
disabled={disabled}
|
|
227
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
228
|
-
{...register(fieldName, {
|
|
229
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
230
|
-
})}
|
|
231
|
-
/>
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
case 'time':
|
|
235
|
-
return renderField(
|
|
236
|
-
<input
|
|
237
|
-
id={fieldName}
|
|
238
|
-
type="time"
|
|
239
|
-
placeholder={fieldPlaceholder}
|
|
240
|
-
disabled={disabled}
|
|
241
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
242
|
-
{...register(fieldName, {
|
|
243
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
244
|
-
})}
|
|
245
|
-
/>
|
|
246
|
-
);
|
|
247
|
-
|
|
248
|
-
case 'currency':
|
|
249
|
-
return renderField(
|
|
250
|
-
<div className="relative">
|
|
251
|
-
<span className="absolute left-3 top-2 text-gray-500">$</span>
|
|
252
|
-
<input
|
|
253
|
-
id={fieldName}
|
|
254
|
-
type="number"
|
|
255
|
-
placeholder={fieldPlaceholder}
|
|
256
|
-
disabled={disabled}
|
|
257
|
-
step="0.01"
|
|
258
|
-
className="w-full pl-8 pr-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
259
|
-
{...register(fieldName, {
|
|
260
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
261
|
-
valueAsNumber: true,
|
|
262
|
-
})}
|
|
263
|
-
/>
|
|
264
|
-
</div>
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
case 'percent':
|
|
268
|
-
return renderField(
|
|
269
|
-
<div className="relative">
|
|
270
|
-
<input
|
|
271
|
-
id={fieldName}
|
|
272
|
-
type="number"
|
|
273
|
-
placeholder={fieldPlaceholder}
|
|
274
|
-
disabled={disabled}
|
|
275
|
-
step="0.01"
|
|
276
|
-
min="0"
|
|
277
|
-
max="100"
|
|
278
|
-
className="w-full pr-8 pl-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
279
|
-
{...register(fieldName, {
|
|
280
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
281
|
-
valueAsNumber: true,
|
|
282
|
-
})}
|
|
283
|
-
/>
|
|
284
|
-
<span className="absolute right-3 top-2 text-gray-500">%</span>
|
|
285
|
-
</div>
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
case 'phone':
|
|
289
|
-
return renderField(
|
|
290
|
-
<input
|
|
291
|
-
id={fieldName}
|
|
292
|
-
type="tel"
|
|
293
|
-
placeholder={fieldPlaceholder || '+1 (555) 000-0000'}
|
|
294
|
-
disabled={disabled}
|
|
295
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
296
|
-
{...register(fieldName, {
|
|
297
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
298
|
-
})}
|
|
299
|
-
/>
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
case 'markdown':
|
|
303
|
-
return renderField(
|
|
304
|
-
<textarea
|
|
305
|
-
id={fieldName}
|
|
306
|
-
placeholder={fieldPlaceholder || 'Enter markdown text...'}
|
|
307
|
-
disabled={disabled}
|
|
308
|
-
rows={8}
|
|
309
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed font-mono"
|
|
310
|
-
{...register(fieldName, {
|
|
311
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
312
|
-
})}
|
|
313
|
-
/>
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
case 'html':
|
|
317
|
-
return renderField(
|
|
318
|
-
<textarea
|
|
319
|
-
id={fieldName}
|
|
320
|
-
placeholder={fieldPlaceholder || 'Enter HTML...'}
|
|
321
|
-
disabled={disabled}
|
|
322
|
-
rows={8}
|
|
323
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed font-mono"
|
|
324
|
-
{...register(fieldName, {
|
|
325
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
326
|
-
})}
|
|
327
|
-
/>
|
|
328
|
-
);
|
|
329
|
-
|
|
330
|
-
case 'file':
|
|
331
|
-
return renderField(
|
|
332
|
-
<input
|
|
333
|
-
id={fieldName}
|
|
334
|
-
type="file"
|
|
335
|
-
disabled={disabled}
|
|
336
|
-
multiple={extendedField.multiple}
|
|
337
|
-
accept={extendedField.accept?.join(',')}
|
|
338
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
|
|
339
|
-
{...register(fieldName, {
|
|
340
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
341
|
-
})}
|
|
342
|
-
/>
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
case 'image':
|
|
346
|
-
return renderField(
|
|
347
|
-
<input
|
|
348
|
-
id={fieldName}
|
|
349
|
-
type="file"
|
|
350
|
-
disabled={disabled}
|
|
351
|
-
multiple={extendedField.multiple}
|
|
352
|
-
accept={extendedField.accept?.join(',') || 'image/*'}
|
|
353
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100"
|
|
354
|
-
{...register(fieldName, {
|
|
355
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
356
|
-
})}
|
|
357
|
-
/>
|
|
358
|
-
);
|
|
359
|
-
|
|
360
|
-
case 'location':
|
|
361
|
-
return renderField(
|
|
362
|
-
<div className="space-y-2">
|
|
363
|
-
<input
|
|
364
|
-
id={`${fieldName}-lat`}
|
|
365
|
-
type="number"
|
|
366
|
-
placeholder="Latitude"
|
|
367
|
-
disabled={disabled}
|
|
368
|
-
step="any"
|
|
369
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
370
|
-
{...register(`${fieldName}.lat`, {
|
|
371
|
-
required: field.required ? 'Latitude is required' : false,
|
|
372
|
-
valueAsNumber: true,
|
|
373
|
-
})}
|
|
374
|
-
/>
|
|
375
|
-
<input
|
|
376
|
-
id={`${fieldName}-lng`}
|
|
377
|
-
type="number"
|
|
378
|
-
placeholder="Longitude"
|
|
379
|
-
disabled={disabled}
|
|
380
|
-
step="any"
|
|
381
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
382
|
-
{...register(`${fieldName}.lng`, {
|
|
383
|
-
required: field.required ? 'Longitude is required' : false,
|
|
384
|
-
valueAsNumber: true,
|
|
385
|
-
})}
|
|
386
|
-
/>
|
|
387
|
-
</div>
|
|
388
|
-
);
|
|
389
|
-
|
|
390
|
-
case 'lookup':
|
|
391
|
-
case 'master_detail':
|
|
392
|
-
return renderField(
|
|
393
|
-
<select
|
|
394
|
-
id={fieldName}
|
|
395
|
-
disabled={disabled}
|
|
396
|
-
multiple={extendedField.multiple}
|
|
397
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
398
|
-
{...register(fieldName, {
|
|
399
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
400
|
-
setValueAs: handleMultipleSelectValue,
|
|
401
|
-
})}
|
|
402
|
-
>
|
|
403
|
-
{!extendedField.multiple && <option value="">{fieldPlaceholder || 'Select an option'}</option>}
|
|
404
|
-
{extendedField.options?.map((option) => (
|
|
405
|
-
<option key={option.value} value={option.value} disabled={option.disabled}>
|
|
406
|
-
{option.label}
|
|
407
|
-
</option>
|
|
408
|
-
))}
|
|
409
|
-
</select>
|
|
410
|
-
);
|
|
411
|
-
|
|
412
|
-
case 'user':
|
|
413
|
-
case 'owner':
|
|
414
|
-
return renderField(
|
|
415
|
-
<select
|
|
416
|
-
id={fieldName}
|
|
417
|
-
disabled={disabled}
|
|
418
|
-
multiple={extendedField.multiple}
|
|
419
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
420
|
-
{...register(fieldName, {
|
|
421
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
422
|
-
setValueAs: handleMultipleSelectValue,
|
|
423
|
-
})}
|
|
424
|
-
>
|
|
425
|
-
{!extendedField.multiple && <option value="">{fieldPlaceholder || 'Select user'}</option>}
|
|
426
|
-
{extendedField.options?.map((option) => (
|
|
427
|
-
<option key={option.value} value={option.value} disabled={option.disabled}>
|
|
428
|
-
{option.label}
|
|
429
|
-
</option>
|
|
430
|
-
))}
|
|
431
|
-
</select>
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
case 'formula':
|
|
435
|
-
case 'summary':
|
|
436
|
-
case 'auto_number':
|
|
437
|
-
// Read-only computed fields - display as disabled text input
|
|
438
|
-
return renderField(
|
|
439
|
-
<input
|
|
440
|
-
id={fieldName}
|
|
441
|
-
type="text"
|
|
442
|
-
disabled={true}
|
|
443
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-gray-50 cursor-not-allowed text-gray-600"
|
|
444
|
-
{...register(fieldName)}
|
|
445
|
-
/>
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
case 'object':
|
|
449
|
-
return renderField(
|
|
450
|
-
<textarea
|
|
451
|
-
id={fieldName}
|
|
452
|
-
placeholder={fieldPlaceholder || 'Enter JSON object...'}
|
|
453
|
-
disabled={disabled}
|
|
454
|
-
rows={6}
|
|
455
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed font-mono text-sm"
|
|
456
|
-
{...register(fieldName, {
|
|
457
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
458
|
-
validate: (value) => {
|
|
459
|
-
if (!value) return true;
|
|
460
|
-
try {
|
|
461
|
-
JSON.parse(value);
|
|
462
|
-
return true;
|
|
463
|
-
} catch (e) {
|
|
464
|
-
return 'Invalid JSON format';
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
})}
|
|
468
|
-
/>
|
|
469
|
-
);
|
|
470
|
-
|
|
471
|
-
case 'vector':
|
|
472
|
-
// Vector fields are typically read-only or require specialized input
|
|
473
|
-
return renderField(
|
|
474
|
-
<input
|
|
475
|
-
id={fieldName}
|
|
476
|
-
type="text"
|
|
477
|
-
placeholder={fieldPlaceholder || 'Vector data (read-only)'}
|
|
478
|
-
disabled={true}
|
|
479
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm bg-gray-50 cursor-not-allowed text-gray-600"
|
|
480
|
-
{...register(fieldName)}
|
|
481
|
-
/>
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
case 'grid':
|
|
485
|
-
// Grid fields require complex table/grid editor - placeholder for now
|
|
486
|
-
return renderField(
|
|
487
|
-
<div className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-50 text-gray-600">
|
|
488
|
-
<p className="text-sm">Grid editor not yet implemented</p>
|
|
489
|
-
</div>
|
|
490
|
-
);
|
|
491
|
-
|
|
492
|
-
default:
|
|
493
|
-
// Default to text input
|
|
494
|
-
return renderField(
|
|
495
|
-
<input
|
|
496
|
-
id={fieldName}
|
|
497
|
-
type="text"
|
|
498
|
-
placeholder={fieldPlaceholder}
|
|
499
|
-
disabled={disabled}
|
|
500
|
-
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed"
|
|
501
|
-
{...register(fieldName, {
|
|
502
|
-
required: field.required ? `${fieldLabel || fieldName} is required` : false,
|
|
503
|
-
})}
|
|
504
|
-
/>
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
FieldFactory.displayName = 'FieldFactory';
|