@object-ui/plugin-form 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.
Files changed (42) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +116 -73
  5. package/dist/index.umd.cjs +2 -2
  6. package/dist/{plugin-form → packages/plugin-form}/src/DrawerForm.d.ts +2 -0
  7. package/dist/{plugin-form → packages/plugin-form}/src/autoLayout.d.ts +11 -4
  8. package/package.json +43 -11
  9. package/.turbo/turbo-build.log +0 -32
  10. package/src/DrawerForm.tsx +0 -410
  11. package/src/EmbeddableForm.tsx +0 -240
  12. package/src/FormAnalytics.tsx +0 -209
  13. package/src/FormSection.tsx +0 -152
  14. package/src/FormVariants.test.tsx +0 -219
  15. package/src/ModalForm.tsx +0 -485
  16. package/src/ObjectForm.msw.test.tsx +0 -156
  17. package/src/ObjectForm.stories.tsx +0 -85
  18. package/src/ObjectForm.test.tsx +0 -61
  19. package/src/ObjectForm.tsx +0 -609
  20. package/src/SplitForm.tsx +0 -300
  21. package/src/TabbedForm.tsx +0 -395
  22. package/src/WizardForm.tsx +0 -502
  23. package/src/__tests__/EmbeddableFormPrefill.test.tsx +0 -186
  24. package/src/__tests__/MobileUX.test.tsx +0 -433
  25. package/src/__tests__/NewVariants.test.tsx +0 -684
  26. package/src/__tests__/autoLayout.test.ts +0 -339
  27. package/src/__tests__/form-validation-submit.test.tsx +0 -286
  28. package/src/autoLayout.ts +0 -166
  29. package/src/index.tsx +0 -134
  30. package/tsconfig.json +0 -9
  31. package/vite.config.ts +0 -57
  32. package/vitest.config.ts +0 -12
  33. package/vitest.setup.ts +0 -1
  34. /package/dist/{plugin-form → packages/plugin-form}/src/EmbeddableForm.d.ts +0 -0
  35. /package/dist/{plugin-form → packages/plugin-form}/src/FormAnalytics.d.ts +0 -0
  36. /package/dist/{plugin-form → packages/plugin-form}/src/FormSection.d.ts +0 -0
  37. /package/dist/{plugin-form → packages/plugin-form}/src/ModalForm.d.ts +0 -0
  38. /package/dist/{plugin-form → packages/plugin-form}/src/ObjectForm.d.ts +0 -0
  39. /package/dist/{plugin-form → packages/plugin-form}/src/SplitForm.d.ts +0 -0
  40. /package/dist/{plugin-form → packages/plugin-form}/src/TabbedForm.d.ts +0 -0
  41. /package/dist/{plugin-form → packages/plugin-form}/src/WizardForm.d.ts +0 -0
  42. /package/dist/{plugin-form → packages/plugin-form}/src/index.d.ts +0 -0
package/src/SplitForm.tsx DELETED
@@ -1,300 +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
- /**
10
- * SplitForm Component
11
- *
12
- * A form variant that displays sections in a resizable split-panel layout.
13
- * The first section renders in the left/top panel, remaining sections in the right/bottom panel.
14
- * Aligns with @objectstack/spec FormView type: 'split'
15
- */
16
-
17
- import React, { useState, useCallback, useEffect, useMemo } from 'react';
18
- import type { FormField, DataSource } from '@object-ui/types';
19
- import {
20
- ResizablePanelGroup,
21
- ResizablePanel,
22
- ResizableHandle,
23
- cn,
24
- } from '@object-ui/components';
25
- import { FormSection } from './FormSection';
26
- import { SchemaRenderer, useSafeFieldLabel } from '@object-ui/react';
27
- import { mapFieldTypeToFormType, buildValidationRules } from '@object-ui/fields';
28
-
29
- export interface SplitFormSectionConfig {
30
- name?: string;
31
- label?: string;
32
- description?: string;
33
- columns?: 1 | 2 | 3 | 4;
34
- fields: (string | FormField)[];
35
- }
36
-
37
- export interface SplitFormSchema {
38
- type: 'object-form';
39
- formType: 'split';
40
- objectName: string;
41
- mode: 'create' | 'edit' | 'view';
42
- recordId?: string | number;
43
- sections: SplitFormSectionConfig[];
44
-
45
- /**
46
- * Split direction.
47
- * @default 'horizontal'
48
- */
49
- splitDirection?: 'horizontal' | 'vertical';
50
-
51
- /**
52
- * Size of the first panel (percentage 1-99).
53
- * @default 50
54
- */
55
- splitSize?: number;
56
-
57
- /**
58
- * Whether panels can be resized.
59
- * @default true
60
- */
61
- splitResizable?: boolean;
62
-
63
- // Common form props
64
- showSubmit?: boolean;
65
- submitText?: string;
66
- showCancel?: boolean;
67
- cancelText?: string;
68
- initialValues?: Record<string, any>;
69
- initialData?: Record<string, any>;
70
- readOnly?: boolean;
71
- onSuccess?: (data: any) => void | Promise<void>;
72
- onError?: (error: Error) => void;
73
- onCancel?: () => void;
74
- className?: string;
75
- }
76
-
77
- export interface SplitFormProps {
78
- schema: SplitFormSchema;
79
- dataSource?: DataSource;
80
- className?: string;
81
- }
82
-
83
- export const SplitForm: React.FC<SplitFormProps> = ({
84
- schema,
85
- dataSource,
86
- className,
87
- }) => {
88
- const { fieldLabel } = useSafeFieldLabel();
89
- const [objectSchema, setObjectSchema] = useState<any>(null);
90
- const [formData, setFormData] = useState<Record<string, any>>({});
91
- const [loading, setLoading] = useState(true);
92
- const [error, setError] = useState<Error | null>(null);
93
-
94
- // Fetch object schema
95
- useEffect(() => {
96
- const fetchSchema = async () => {
97
- if (!dataSource) {
98
- setLoading(false);
99
- return;
100
- }
101
- try {
102
- const data = await dataSource.getObjectSchema(schema.objectName);
103
- setObjectSchema(data);
104
- } catch (err) {
105
- setError(err as Error);
106
- setLoading(false);
107
- }
108
- };
109
- fetchSchema();
110
- }, [schema.objectName, dataSource]);
111
-
112
- // Fetch initial data
113
- useEffect(() => {
114
- const fetchData = async () => {
115
- if (schema.mode === 'create' || !schema.recordId) {
116
- setFormData(schema.initialData || schema.initialValues || {});
117
- setLoading(false);
118
- return;
119
- }
120
-
121
- if (!dataSource) {
122
- setFormData(schema.initialData || schema.initialValues || {});
123
- setLoading(false);
124
- return;
125
- }
126
-
127
- try {
128
- const data = await dataSource.findOne(schema.objectName, schema.recordId);
129
- setFormData(data || {});
130
- } catch (err) {
131
- setError(err as Error);
132
- } finally {
133
- setLoading(false);
134
- }
135
- };
136
-
137
- if (objectSchema || !dataSource) {
138
- fetchData();
139
- }
140
- }, [objectSchema, schema.mode, schema.recordId, schema.initialData, schema.initialValues, dataSource, schema.objectName]);
141
-
142
- // Build form fields from section config
143
- const buildSectionFields = useCallback((section: SplitFormSectionConfig): FormField[] => {
144
- const fields: FormField[] = [];
145
-
146
- for (const fieldDef of section.fields) {
147
- const fieldName = typeof fieldDef === 'string' ? fieldDef : fieldDef.name;
148
-
149
- if (typeof fieldDef === 'object') {
150
- fields.push(fieldDef);
151
- } else if (objectSchema?.fields?.[fieldName]) {
152
- const field = objectSchema.fields[fieldName];
153
- fields.push({
154
- name: fieldName,
155
- label: fieldLabel(schema.objectName, fieldName, field.label || fieldName),
156
- type: mapFieldTypeToFormType(field.type),
157
- required: field.required || false,
158
- disabled: schema.readOnly || schema.mode === 'view' || field.readonly,
159
- placeholder: field.placeholder,
160
- description: field.help || field.description,
161
- validation: buildValidationRules(field),
162
- field: field,
163
- options: field.options,
164
- multiple: field.multiple,
165
- });
166
- } else {
167
- fields.push({
168
- name: fieldName,
169
- label: fieldName,
170
- type: 'input',
171
- });
172
- }
173
- }
174
-
175
- return fields;
176
- }, [objectSchema, schema.readOnly, schema.mode]);
177
-
178
- // Handle form submission
179
- const handleSubmit = useCallback(async (data: Record<string, any>) => {
180
- if (!dataSource) {
181
- if (schema.onSuccess) {
182
- await schema.onSuccess(data);
183
- }
184
- return data;
185
- }
186
-
187
- try {
188
- let result;
189
- if (schema.mode === 'create') {
190
- result = await dataSource.create(schema.objectName, data);
191
- } else if (schema.mode === 'edit' && schema.recordId) {
192
- result = await dataSource.update(schema.objectName, schema.recordId, data);
193
- }
194
- if (schema.onSuccess) {
195
- await schema.onSuccess(result);
196
- }
197
- return result;
198
- } catch (err) {
199
- if (schema.onError) {
200
- schema.onError(err as Error);
201
- }
202
- throw err;
203
- }
204
- }, [schema, dataSource]);
205
-
206
- // Handle cancel
207
- const handleCancel = useCallback(() => {
208
- if (schema.onCancel) {
209
- schema.onCancel();
210
- }
211
- }, [schema]);
212
-
213
- // Split sections: first section in panel 1, rest in panel 2
214
- const leftSections = useMemo(() => schema.sections.slice(0, 1), [schema.sections]);
215
- const rightSections = useMemo(() => schema.sections.slice(1), [schema.sections]);
216
-
217
- // Collect all fields for a unified form submission
218
- const allFields: FormField[] = useMemo(
219
- () => schema.sections.flatMap(section => buildSectionFields(section)),
220
- [schema.sections, buildSectionFields]
221
- );
222
-
223
- const direction = schema.splitDirection || 'horizontal';
224
- const panelSize = schema.splitSize || 50;
225
-
226
- if (error) {
227
- return (
228
- <div className="p-4 border border-red-300 bg-red-50 rounded-md">
229
- <h3 className="text-red-800 font-semibold">Error loading form</h3>
230
- <p className="text-red-600 text-sm mt-1">{error.message}</p>
231
- </div>
232
- );
233
- }
234
-
235
- if (loading) {
236
- return (
237
- <div className="p-8 text-center">
238
- <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
239
- <p className="mt-2 text-sm text-gray-600">Loading form...</p>
240
- </div>
241
- );
242
- }
243
-
244
- // Build base form schema for SchemaRenderer
245
- const baseFormSchema = {
246
- type: 'form' as const,
247
- layout: 'vertical' as const,
248
- defaultValues: formData,
249
- onSubmit: handleSubmit,
250
- onCancel: handleCancel,
251
- };
252
-
253
- const renderSections = (sections: SplitFormSectionConfig[], showButtons: boolean) => (
254
- <div className="space-y-4 p-4">
255
- {sections.map((section, index) => (
256
- <FormSection
257
- key={section.name || section.label || index}
258
- label={section.label}
259
- description={section.description}
260
- columns={section.columns || 1}
261
- >
262
- <SchemaRenderer
263
- schema={{
264
- ...baseFormSchema,
265
- fields: buildSectionFields(section),
266
- showSubmit: showButtons && schema.showSubmit !== false && schema.mode !== 'view',
267
- showCancel: showButtons && schema.showCancel !== false,
268
- submitLabel: schema.submitText || (schema.mode === 'create' ? 'Create' : 'Update'),
269
- cancelLabel: schema.cancelText,
270
- }}
271
- />
272
- </FormSection>
273
- ))}
274
- </div>
275
- );
276
-
277
- return (
278
- <div className={cn('w-full', className, schema.className)}>
279
- <ResizablePanelGroup orientation={direction as 'horizontal' | 'vertical'} className="min-h-[300px] rounded-lg border">
280
- {/* Left / Top Panel */}
281
- <ResizablePanel defaultSize={panelSize} minSize={20}>
282
- {renderSections(leftSections, rightSections.length === 0)}
283
- </ResizablePanel>
284
-
285
- {rightSections.length > 0 && (
286
- <>
287
- <ResizableHandle withHandle={schema.splitResizable !== false} />
288
-
289
- {/* Right / Bottom Panel */}
290
- <ResizablePanel defaultSize={100 - panelSize} minSize={20}>
291
- {renderSections(rightSections, true)}
292
- </ResizablePanel>
293
- </>
294
- )}
295
- </ResizablePanelGroup>
296
- </div>
297
- );
298
- };
299
-
300
- export default SplitForm;
@@ -1,395 +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
- /**
10
- * TabbedForm Component
11
- *
12
- * A form component that organizes sections into tabs.
13
- * Aligns with @objectstack/spec FormView type: 'tabbed'
14
- */
15
-
16
- import React, { useState, useCallback } from 'react';
17
- import type { FormField, DataSource } from '@object-ui/types';
18
- import { Tabs, TabsContent, TabsList, TabsTrigger, cn } from '@object-ui/components';
19
- import { FormSection } from './FormSection';
20
- import { SchemaRenderer, useSafeFieldLabel } from '@object-ui/react';
21
- import { mapFieldTypeToFormType, buildValidationRules } from '@object-ui/fields';
22
-
23
- export interface FormSectionConfig {
24
- /**
25
- * Section identifier (used as tab value)
26
- */
27
- name?: string;
28
-
29
- /**
30
- * Section label (used as tab trigger text)
31
- */
32
- label?: string;
33
-
34
- /**
35
- * Section description
36
- */
37
- description?: string;
38
-
39
- /**
40
- * Number of columns in the section
41
- * @default 1
42
- */
43
- columns?: 1 | 2 | 3 | 4;
44
-
45
- /**
46
- * Field names or configurations in this section
47
- */
48
- fields: (string | FormField)[];
49
- }
50
-
51
- export interface TabbedFormSchema {
52
- type: 'object-form';
53
- formType: 'tabbed';
54
-
55
- /**
56
- * Object name for ObjectQL schema lookup
57
- */
58
- objectName: string;
59
-
60
- /**
61
- * Form mode
62
- */
63
- mode: 'create' | 'edit' | 'view';
64
-
65
- /**
66
- * Record ID (for edit/view modes)
67
- */
68
- recordId?: string | number;
69
-
70
- /**
71
- * Tab sections configuration
72
- */
73
- sections: FormSectionConfig[];
74
-
75
- /**
76
- * Default active tab (section name)
77
- */
78
- defaultTab?: string;
79
-
80
- /**
81
- * Tab position
82
- * @default 'top'
83
- */
84
- tabPosition?: 'top' | 'bottom' | 'left' | 'right';
85
-
86
- /**
87
- * Show submit button
88
- * @default true
89
- */
90
- showSubmit?: boolean;
91
-
92
- /**
93
- * Submit button text
94
- */
95
- submitText?: string;
96
-
97
- /**
98
- * Show cancel button
99
- * @default true
100
- */
101
- showCancel?: boolean;
102
-
103
- /**
104
- * Cancel button text
105
- */
106
- cancelText?: string;
107
-
108
- /**
109
- * Initial values
110
- */
111
- initialValues?: Record<string, any>;
112
-
113
- /**
114
- * Initial data (alias for initialValues)
115
- */
116
- initialData?: Record<string, any>;
117
-
118
- /**
119
- * Read-only mode
120
- */
121
- readOnly?: boolean;
122
-
123
- /**
124
- * Callbacks
125
- */
126
- onSuccess?: (data: any) => void | Promise<void>;
127
- onError?: (error: Error) => void;
128
- onCancel?: () => void;
129
-
130
- /**
131
- * CSS class
132
- */
133
- className?: string;
134
- }
135
-
136
- export interface TabbedFormProps {
137
- schema: TabbedFormSchema;
138
- dataSource?: DataSource;
139
- className?: string;
140
- }
141
-
142
- /**
143
- * TabbedForm Component
144
- *
145
- * Renders a form with sections organized as tabs.
146
- *
147
- * @example
148
- * ```tsx
149
- * <TabbedForm
150
- * schema={{
151
- * type: 'object-form',
152
- * formType: 'tabbed',
153
- * objectName: 'contacts',
154
- * mode: 'create',
155
- * sections: [
156
- * { label: 'Basic Info', fields: ['firstName', 'lastName', 'email'] },
157
- * { label: 'Address', fields: ['street', 'city', 'country'] },
158
- * ]
159
- * }}
160
- * dataSource={dataSource}
161
- * />
162
- * ```
163
- */
164
- export const TabbedForm: React.FC<TabbedFormProps> = ({
165
- schema,
166
- dataSource,
167
- className,
168
- }) => {
169
- const { fieldLabel } = useSafeFieldLabel();
170
- const [objectSchema, setObjectSchema] = useState<any>(null);
171
- const [formData, setFormData] = useState<Record<string, any>>({});
172
- const [loading, setLoading] = useState(true);
173
- const [error, setError] = useState<Error | null>(null);
174
- const [activeTab, setActiveTab] = useState<string>(
175
- schema.defaultTab || schema.sections[0]?.name || schema.sections[0]?.label || 'tab-0'
176
- );
177
-
178
- // Fetch object schema
179
- React.useEffect(() => {
180
- const fetchSchema = async () => {
181
- if (!dataSource) {
182
- setLoading(false);
183
- return;
184
- }
185
-
186
- try {
187
- const schemaData = await dataSource.getObjectSchema(schema.objectName);
188
- setObjectSchema(schemaData);
189
- } catch (err) {
190
- setError(err as Error);
191
- }
192
- };
193
-
194
- fetchSchema();
195
- }, [schema.objectName, dataSource]);
196
-
197
- // Fetch initial data for edit/view modes
198
- React.useEffect(() => {
199
- const fetchData = async () => {
200
- if (schema.mode === 'create' || !schema.recordId || !dataSource) {
201
- setFormData(schema.initialData || schema.initialValues || {});
202
- setLoading(false);
203
- return;
204
- }
205
-
206
- try {
207
- const data = await dataSource.findOne(schema.objectName, schema.recordId);
208
- setFormData(data || {});
209
- } catch (err) {
210
- setError(err as Error);
211
- } finally {
212
- setLoading(false);
213
- }
214
- };
215
-
216
- if (objectSchema || !dataSource) {
217
- fetchData();
218
- }
219
- }, [objectSchema, schema.mode, schema.recordId, schema.initialData, schema.initialValues, dataSource, schema.objectName]);
220
-
221
- // Build form fields from section config
222
- const buildSectionFields = useCallback((section: FormSectionConfig): FormField[] => {
223
- const fields: FormField[] = [];
224
-
225
- for (const fieldDef of section.fields) {
226
- const fieldName = typeof fieldDef === 'string' ? fieldDef : fieldDef.name;
227
-
228
- if (typeof fieldDef === 'object') {
229
- // Use the field definition directly
230
- fields.push(fieldDef);
231
- } else if (objectSchema?.fields?.[fieldName]) {
232
- // Build from object schema
233
- const field = objectSchema.fields[fieldName];
234
- fields.push({
235
- name: fieldName,
236
- label: fieldLabel(schema.objectName, fieldName, field.label || fieldName),
237
- type: mapFieldTypeToFormType(field.type),
238
- required: field.required || false,
239
- disabled: schema.readOnly || schema.mode === 'view' || field.readonly,
240
- placeholder: field.placeholder,
241
- description: field.help || field.description,
242
- validation: buildValidationRules(field),
243
- field: field,
244
- options: field.options,
245
- multiple: field.multiple,
246
- });
247
- } else {
248
- // Fallback for unknown fields
249
- fields.push({
250
- name: fieldName,
251
- label: fieldName,
252
- type: 'input',
253
- });
254
- }
255
- }
256
-
257
- return fields;
258
- }, [objectSchema, schema.readOnly, schema.mode]);
259
-
260
- // Handle form submission
261
- const handleSubmit = useCallback(async (data: Record<string, any>) => {
262
- if (!dataSource) {
263
- if (schema.onSuccess) {
264
- await schema.onSuccess(data);
265
- }
266
- return data;
267
- }
268
-
269
- try {
270
- let result;
271
-
272
- if (schema.mode === 'create') {
273
- result = await dataSource.create(schema.objectName, data);
274
- } else if (schema.mode === 'edit' && schema.recordId) {
275
- result = await dataSource.update(schema.objectName, schema.recordId, data);
276
- }
277
-
278
- if (schema.onSuccess) {
279
- await schema.onSuccess(result);
280
- }
281
-
282
- return result;
283
- } catch (err) {
284
- if (schema.onError) {
285
- schema.onError(err as Error);
286
- }
287
- throw err;
288
- }
289
- }, [schema, dataSource]);
290
-
291
- // Handle cancel
292
- const handleCancel = useCallback(() => {
293
- if (schema.onCancel) {
294
- schema.onCancel();
295
- }
296
- }, [schema]);
297
-
298
- // Generate tab value
299
- const getTabValue = (section: FormSectionConfig, index: number): string => {
300
- return section.name || section.label || `tab-${index}`;
301
- };
302
-
303
- if (error) {
304
- return (
305
- <div className="p-4 border border-red-300 bg-red-50 rounded-md">
306
- <h3 className="text-red-800 font-semibold">Error loading form</h3>
307
- <p className="text-red-600 text-sm mt-1">{error.message}</p>
308
- </div>
309
- );
310
- }
311
-
312
- if (loading) {
313
- return (
314
- <div className="p-8 text-center">
315
- <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
316
- <p className="mt-2 text-sm text-gray-600">Loading form...</p>
317
- </div>
318
- );
319
- }
320
-
321
- // Collect all fields across all sections for the form
322
- const allFields: FormField[] = schema.sections.flatMap(section => buildSectionFields(section));
323
-
324
- // Build the overall form schema
325
- const formSchema = {
326
- type: 'form' as const,
327
- fields: allFields,
328
- layout: 'vertical' as const,
329
- defaultValues: formData,
330
- submitLabel: schema.submitText || (schema.mode === 'create' ? 'Create' : 'Update'),
331
- cancelLabel: schema.cancelText,
332
- showSubmit: schema.showSubmit !== false && schema.mode !== 'view',
333
- showCancel: schema.showCancel !== false,
334
- onSubmit: handleSubmit,
335
- onCancel: handleCancel,
336
- };
337
-
338
- // Determine orientation based on tabPosition
339
- const isVertical = schema.tabPosition === 'left' || schema.tabPosition === 'right';
340
-
341
- return (
342
- <div className={cn('w-full', className, schema.className)}>
343
- <Tabs
344
- value={activeTab}
345
- onValueChange={setActiveTab}
346
- orientation={isVertical ? 'vertical' : 'horizontal'}
347
- className={cn(isVertical && 'flex gap-4')}
348
- >
349
- <TabsList className={cn(
350
- isVertical ? 'flex-col h-auto' : '',
351
- schema.tabPosition === 'bottom' && 'order-last',
352
- schema.tabPosition === 'right' && 'order-last'
353
- )}>
354
- {schema.sections.map((section, index) => (
355
- <TabsTrigger
356
- key={getTabValue(section, index)}
357
- value={getTabValue(section, index)}
358
- className={isVertical ? 'w-full justify-start' : ''}
359
- >
360
- {section.label || `Tab ${index + 1}`}
361
- </TabsTrigger>
362
- ))}
363
- </TabsList>
364
-
365
- <div className="flex-1">
366
- {schema.sections.map((section, index) => (
367
- <TabsContent
368
- key={getTabValue(section, index)}
369
- value={getTabValue(section, index)}
370
- className="mt-0"
371
- >
372
- <FormSection
373
- description={section.description}
374
- columns={section.columns || 1}
375
- >
376
- {/* Render fields for this section */}
377
- <SchemaRenderer
378
- schema={{
379
- ...formSchema,
380
- fields: buildSectionFields(section),
381
- // Only show buttons on the last tab or always visible
382
- showSubmit: schema.showSubmit !== false && schema.mode !== 'view',
383
- showCancel: schema.showCancel !== false,
384
- }}
385
- />
386
- </FormSection>
387
- </TabsContent>
388
- ))}
389
- </div>
390
- </Tabs>
391
- </div>
392
- );
393
- };
394
-
395
- export default TabbedForm;