@object-ui/plugin-form 0.5.0 → 2.0.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/.turbo/turbo-build.log +8 -6
- package/CHANGELOG.md +15 -0
- package/dist/index.js +1388 -283
- package/dist/index.umd.cjs +2 -2
- package/dist/packages/plugin-form/src/DrawerForm.d.ts +61 -0
- package/dist/packages/plugin-form/src/FormSection.d.ts +49 -0
- package/dist/packages/plugin-form/src/FormVariants.test.d.ts +0 -0
- package/dist/packages/plugin-form/src/ModalForm.d.ts +60 -0
- package/dist/packages/plugin-form/src/SplitForm.d.ts +50 -0
- package/dist/packages/plugin-form/src/TabbedForm.d.ts +123 -0
- package/dist/packages/plugin-form/src/WizardForm.d.ts +112 -0
- package/dist/packages/plugin-form/src/__tests__/NewVariants.test.d.ts +8 -0
- package/dist/packages/plugin-form/src/index.d.ts +12 -0
- package/package.json +8 -8
- package/src/DrawerForm.tsx +385 -0
- package/src/FormSection.tsx +144 -0
- package/src/FormVariants.test.tsx +219 -0
- package/src/ModalForm.tsx +379 -0
- package/src/ObjectForm.msw.test.tsx +29 -2
- package/src/ObjectForm.tsx +204 -6
- package/src/SplitForm.tsx +299 -0
- package/src/TabbedForm.tsx +394 -0
- package/src/WizardForm.tsx +501 -0
- package/src/__tests__/NewVariants.test.tsx +488 -0
- package/src/index.tsx +60 -3
- package/vitest.config.ts +12 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,501 @@
|
|
|
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
|
+
* WizardForm Component
|
|
11
|
+
*
|
|
12
|
+
* A multi-step wizard form that guides users through sections step by step.
|
|
13
|
+
* Aligns with @objectstack/spec FormView type: 'wizard'
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useState, useCallback, useMemo } from 'react';
|
|
17
|
+
import type { FormField, DataSource } from '@object-ui/types';
|
|
18
|
+
import { Button, cn } from '@object-ui/components';
|
|
19
|
+
import { Check, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
20
|
+
import { FormSection } from './FormSection';
|
|
21
|
+
import { SchemaRenderer } from '@object-ui/react';
|
|
22
|
+
import { mapFieldTypeToFormType, buildValidationRules } from '@object-ui/fields';
|
|
23
|
+
import type { FormSectionConfig } from './TabbedForm';
|
|
24
|
+
|
|
25
|
+
export interface WizardFormSchema {
|
|
26
|
+
type: 'object-form';
|
|
27
|
+
formType: 'wizard';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Object name for ObjectQL schema lookup
|
|
31
|
+
*/
|
|
32
|
+
objectName: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Form mode
|
|
36
|
+
*/
|
|
37
|
+
mode: 'create' | 'edit' | 'view';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Record ID (for edit/view modes)
|
|
41
|
+
*/
|
|
42
|
+
recordId?: string | number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wizard step sections
|
|
46
|
+
*/
|
|
47
|
+
sections: FormSectionConfig[];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Allow navigation to any step (not just sequential)
|
|
51
|
+
* @default false
|
|
52
|
+
*/
|
|
53
|
+
allowSkip?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Show step indicators
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
showStepIndicator?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Text for Next button
|
|
63
|
+
* @default 'Next'
|
|
64
|
+
*/
|
|
65
|
+
nextText?: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Text for Previous button
|
|
69
|
+
* @default 'Back'
|
|
70
|
+
*/
|
|
71
|
+
prevText?: string;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Submit button text (shown on last step)
|
|
75
|
+
*/
|
|
76
|
+
submitText?: string;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Show cancel button
|
|
80
|
+
* @default true
|
|
81
|
+
*/
|
|
82
|
+
showCancel?: boolean;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Cancel button text
|
|
86
|
+
*/
|
|
87
|
+
cancelText?: string;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initial values
|
|
91
|
+
*/
|
|
92
|
+
initialValues?: Record<string, any>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Initial data (alias)
|
|
96
|
+
*/
|
|
97
|
+
initialData?: Record<string, any>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Read-only mode
|
|
101
|
+
*/
|
|
102
|
+
readOnly?: boolean;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Callbacks
|
|
106
|
+
*/
|
|
107
|
+
onSuccess?: (data: any) => void | Promise<void>;
|
|
108
|
+
onError?: (error: Error) => void;
|
|
109
|
+
onCancel?: () => void;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Called when step changes
|
|
113
|
+
*/
|
|
114
|
+
onStepChange?: (step: number) => void;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* CSS class
|
|
118
|
+
*/
|
|
119
|
+
className?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface WizardFormProps {
|
|
123
|
+
schema: WizardFormSchema;
|
|
124
|
+
dataSource?: DataSource;
|
|
125
|
+
className?: string;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* WizardForm Component
|
|
130
|
+
*
|
|
131
|
+
* Renders a multi-step wizard form with step indicators and navigation.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```tsx
|
|
135
|
+
* <WizardForm
|
|
136
|
+
* schema={{
|
|
137
|
+
* type: 'object-form',
|
|
138
|
+
* formType: 'wizard',
|
|
139
|
+
* objectName: 'users',
|
|
140
|
+
* mode: 'create',
|
|
141
|
+
* sections: [
|
|
142
|
+
* { label: 'Step 1: Personal', fields: ['firstName', 'lastName'] },
|
|
143
|
+
* { label: 'Step 2: Contact', fields: ['email', 'phone'] },
|
|
144
|
+
* { label: 'Step 3: Review', fields: [] },
|
|
145
|
+
* ]
|
|
146
|
+
* }}
|
|
147
|
+
* dataSource={dataSource}
|
|
148
|
+
* />
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export const WizardForm: React.FC<WizardFormProps> = ({
|
|
152
|
+
schema,
|
|
153
|
+
dataSource,
|
|
154
|
+
className,
|
|
155
|
+
}) => {
|
|
156
|
+
const [objectSchema, setObjectSchema] = useState<any>(null);
|
|
157
|
+
const [formData, setFormData] = useState<Record<string, any>>({});
|
|
158
|
+
const [loading, setLoading] = useState(true);
|
|
159
|
+
const [error, setError] = useState<Error | null>(null);
|
|
160
|
+
const [currentStep, setCurrentStep] = useState(0);
|
|
161
|
+
const [completedSteps, setCompletedSteps] = useState<Set<number>>(new Set());
|
|
162
|
+
const [submitting, setSubmitting] = useState(false);
|
|
163
|
+
|
|
164
|
+
const totalSteps = schema.sections.length;
|
|
165
|
+
const isFirstStep = currentStep === 0;
|
|
166
|
+
const isLastStep = currentStep === totalSteps - 1;
|
|
167
|
+
|
|
168
|
+
// Fetch object schema
|
|
169
|
+
React.useEffect(() => {
|
|
170
|
+
const fetchSchema = async () => {
|
|
171
|
+
if (!dataSource) {
|
|
172
|
+
setLoading(false);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const schemaData = await dataSource.getObjectSchema(schema.objectName);
|
|
178
|
+
setObjectSchema(schemaData);
|
|
179
|
+
} catch (err) {
|
|
180
|
+
setError(err as Error);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
fetchSchema();
|
|
185
|
+
}, [schema.objectName, dataSource]);
|
|
186
|
+
|
|
187
|
+
// Fetch initial data
|
|
188
|
+
React.useEffect(() => {
|
|
189
|
+
const fetchData = async () => {
|
|
190
|
+
if (schema.mode === 'create' || !schema.recordId || !dataSource) {
|
|
191
|
+
setFormData(schema.initialData || schema.initialValues || {});
|
|
192
|
+
setLoading(false);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const data = await dataSource.findOne(schema.objectName, schema.recordId);
|
|
198
|
+
setFormData(data || {});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
setError(err as Error);
|
|
201
|
+
} finally {
|
|
202
|
+
setLoading(false);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (objectSchema || !dataSource) {
|
|
207
|
+
fetchData();
|
|
208
|
+
}
|
|
209
|
+
}, [objectSchema, schema.mode, schema.recordId, schema.initialData, schema.initialValues, dataSource, schema.objectName]);
|
|
210
|
+
|
|
211
|
+
// Build section fields from object schema
|
|
212
|
+
const buildSectionFields = useCallback((section: FormSectionConfig): FormField[] => {
|
|
213
|
+
const fields: FormField[] = [];
|
|
214
|
+
|
|
215
|
+
for (const fieldDef of section.fields) {
|
|
216
|
+
const fieldName = typeof fieldDef === 'string' ? fieldDef : fieldDef.name;
|
|
217
|
+
|
|
218
|
+
if (typeof fieldDef === 'object') {
|
|
219
|
+
fields.push(fieldDef);
|
|
220
|
+
} else if (objectSchema?.fields?.[fieldName]) {
|
|
221
|
+
const field = objectSchema.fields[fieldName];
|
|
222
|
+
fields.push({
|
|
223
|
+
name: fieldName,
|
|
224
|
+
label: field.label || fieldName,
|
|
225
|
+
type: mapFieldTypeToFormType(field.type),
|
|
226
|
+
required: field.required || false,
|
|
227
|
+
disabled: schema.readOnly || schema.mode === 'view' || field.readonly,
|
|
228
|
+
placeholder: field.placeholder,
|
|
229
|
+
description: field.help || field.description,
|
|
230
|
+
validation: buildValidationRules(field),
|
|
231
|
+
field: field,
|
|
232
|
+
options: field.options,
|
|
233
|
+
multiple: field.multiple,
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
fields.push({
|
|
237
|
+
name: fieldName,
|
|
238
|
+
label: fieldName,
|
|
239
|
+
type: 'input',
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return fields;
|
|
245
|
+
}, [objectSchema, schema.readOnly, schema.mode]);
|
|
246
|
+
|
|
247
|
+
// Current section fields
|
|
248
|
+
const currentSectionFields = useMemo(() => {
|
|
249
|
+
if (currentStep >= 0 && currentStep < totalSteps) {
|
|
250
|
+
return buildSectionFields(schema.sections[currentStep]);
|
|
251
|
+
}
|
|
252
|
+
return [];
|
|
253
|
+
}, [currentStep, totalSteps, schema.sections, buildSectionFields]);
|
|
254
|
+
|
|
255
|
+
// Handle step data collection (merge partial data into formData)
|
|
256
|
+
const handleStepSubmit = useCallback(async (stepData: Record<string, any>) => {
|
|
257
|
+
const mergedData = { ...formData, ...stepData };
|
|
258
|
+
setFormData(mergedData);
|
|
259
|
+
|
|
260
|
+
// Mark step as completed
|
|
261
|
+
setCompletedSteps(prev => new Set(prev).add(currentStep));
|
|
262
|
+
|
|
263
|
+
if (isLastStep) {
|
|
264
|
+
// Final submission
|
|
265
|
+
setSubmitting(true);
|
|
266
|
+
try {
|
|
267
|
+
if (!dataSource) {
|
|
268
|
+
if (schema.onSuccess) {
|
|
269
|
+
await schema.onSuccess(mergedData);
|
|
270
|
+
}
|
|
271
|
+
return mergedData;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let result;
|
|
275
|
+
if (schema.mode === 'create') {
|
|
276
|
+
result = await dataSource.create(schema.objectName, mergedData);
|
|
277
|
+
} else if (schema.mode === 'edit' && schema.recordId) {
|
|
278
|
+
result = await dataSource.update(schema.objectName, schema.recordId, mergedData);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (schema.onSuccess) {
|
|
282
|
+
await schema.onSuccess(result);
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (schema.onError) {
|
|
287
|
+
schema.onError(err as Error);
|
|
288
|
+
}
|
|
289
|
+
throw err;
|
|
290
|
+
} finally {
|
|
291
|
+
setSubmitting(false);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// Move to next step
|
|
295
|
+
goToStep(currentStep + 1);
|
|
296
|
+
}
|
|
297
|
+
}, [formData, currentStep, isLastStep, schema, dataSource]);
|
|
298
|
+
|
|
299
|
+
// Navigation
|
|
300
|
+
const goToStep = useCallback((step: number) => {
|
|
301
|
+
if (step >= 0 && step < totalSteps) {
|
|
302
|
+
setCurrentStep(step);
|
|
303
|
+
if (schema.onStepChange) {
|
|
304
|
+
schema.onStepChange(step);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}, [totalSteps, schema]);
|
|
308
|
+
|
|
309
|
+
const handlePrev = useCallback(() => {
|
|
310
|
+
goToStep(currentStep - 1);
|
|
311
|
+
}, [currentStep, goToStep]);
|
|
312
|
+
|
|
313
|
+
const handleCancel = useCallback(() => {
|
|
314
|
+
if (schema.onCancel) {
|
|
315
|
+
schema.onCancel();
|
|
316
|
+
}
|
|
317
|
+
}, [schema]);
|
|
318
|
+
|
|
319
|
+
const handleStepClick = useCallback((step: number) => {
|
|
320
|
+
if (schema.allowSkip || completedSteps.has(step) || step <= currentStep) {
|
|
321
|
+
goToStep(step);
|
|
322
|
+
}
|
|
323
|
+
}, [schema.allowSkip, completedSteps, currentStep, goToStep]);
|
|
324
|
+
|
|
325
|
+
if (error) {
|
|
326
|
+
return (
|
|
327
|
+
<div className="p-4 border border-red-300 bg-red-50 rounded-md">
|
|
328
|
+
<h3 className="text-red-800 font-semibold">Error loading form</h3>
|
|
329
|
+
<p className="text-red-600 text-sm mt-1">{error.message}</p>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (loading) {
|
|
335
|
+
return (
|
|
336
|
+
<div className="p-8 text-center">
|
|
337
|
+
<div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
|
|
338
|
+
<p className="mt-2 text-sm text-gray-600">Loading form...</p>
|
|
339
|
+
</div>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const currentSection = schema.sections[currentStep];
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<div className={cn('w-full', className, schema.className)}>
|
|
347
|
+
{/* Step Indicator */}
|
|
348
|
+
{schema.showStepIndicator !== false && (
|
|
349
|
+
<nav aria-label="Progress" className="mb-8">
|
|
350
|
+
<ol className="flex items-center">
|
|
351
|
+
{schema.sections.map((section, index) => {
|
|
352
|
+
const isActive = index === currentStep;
|
|
353
|
+
const isCompleted = completedSteps.has(index);
|
|
354
|
+
const isClickable = schema.allowSkip || isCompleted || index <= currentStep;
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<li
|
|
358
|
+
key={index}
|
|
359
|
+
className={cn(
|
|
360
|
+
'relative flex-1',
|
|
361
|
+
index !== totalSteps - 1 && 'pr-8 sm:pr-12'
|
|
362
|
+
)}
|
|
363
|
+
>
|
|
364
|
+
{/* Connector line */}
|
|
365
|
+
{index !== totalSteps - 1 && (
|
|
366
|
+
<div
|
|
367
|
+
className="absolute top-4 left-8 -right-4 sm:left-10 sm:-right-2 h-0.5"
|
|
368
|
+
aria-hidden="true"
|
|
369
|
+
>
|
|
370
|
+
<div
|
|
371
|
+
className={cn(
|
|
372
|
+
'h-full',
|
|
373
|
+
isCompleted ? 'bg-primary' : 'bg-muted'
|
|
374
|
+
)}
|
|
375
|
+
/>
|
|
376
|
+
</div>
|
|
377
|
+
)}
|
|
378
|
+
|
|
379
|
+
<button
|
|
380
|
+
type="button"
|
|
381
|
+
className={cn(
|
|
382
|
+
'group relative flex items-center',
|
|
383
|
+
isClickable ? 'cursor-pointer' : 'cursor-not-allowed'
|
|
384
|
+
)}
|
|
385
|
+
onClick={() => handleStepClick(index)}
|
|
386
|
+
disabled={!isClickable}
|
|
387
|
+
>
|
|
388
|
+
{/* Step circle */}
|
|
389
|
+
<span
|
|
390
|
+
className={cn(
|
|
391
|
+
'flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium transition-colors',
|
|
392
|
+
isCompleted && 'bg-primary text-primary-foreground',
|
|
393
|
+
isActive && !isCompleted && 'border-2 border-primary bg-background text-primary',
|
|
394
|
+
!isActive && !isCompleted && 'border-2 border-muted bg-background text-muted-foreground'
|
|
395
|
+
)}
|
|
396
|
+
>
|
|
397
|
+
{isCompleted ? (
|
|
398
|
+
<Check className="h-4 w-4" />
|
|
399
|
+
) : (
|
|
400
|
+
index + 1
|
|
401
|
+
)}
|
|
402
|
+
</span>
|
|
403
|
+
|
|
404
|
+
{/* Step label */}
|
|
405
|
+
<span className="ml-3 text-sm font-medium hidden sm:block">
|
|
406
|
+
<span
|
|
407
|
+
className={cn(
|
|
408
|
+
isActive ? 'text-foreground' : 'text-muted-foreground'
|
|
409
|
+
)}
|
|
410
|
+
>
|
|
411
|
+
{section.label || `Step ${index + 1}`}
|
|
412
|
+
</span>
|
|
413
|
+
</span>
|
|
414
|
+
</button>
|
|
415
|
+
</li>
|
|
416
|
+
);
|
|
417
|
+
})}
|
|
418
|
+
</ol>
|
|
419
|
+
</nav>
|
|
420
|
+
)}
|
|
421
|
+
|
|
422
|
+
{/* Current Step Content */}
|
|
423
|
+
<div className="min-h-[200px]">
|
|
424
|
+
{currentSection && (
|
|
425
|
+
<FormSection
|
|
426
|
+
label={currentSection.label}
|
|
427
|
+
description={currentSection.description}
|
|
428
|
+
columns={currentSection.columns || 1}
|
|
429
|
+
>
|
|
430
|
+
{currentSectionFields.length > 0 ? (
|
|
431
|
+
<SchemaRenderer
|
|
432
|
+
schema={{
|
|
433
|
+
type: 'form' as const,
|
|
434
|
+
fields: currentSectionFields,
|
|
435
|
+
layout: 'vertical' as const,
|
|
436
|
+
defaultValues: formData,
|
|
437
|
+
showSubmit: false,
|
|
438
|
+
showCancel: false,
|
|
439
|
+
onSubmit: handleStepSubmit,
|
|
440
|
+
}}
|
|
441
|
+
/>
|
|
442
|
+
) : (
|
|
443
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
444
|
+
No fields configured for this step
|
|
445
|
+
</div>
|
|
446
|
+
)}
|
|
447
|
+
</FormSection>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
{/* Navigation Buttons */}
|
|
452
|
+
<div className="flex items-center justify-between mt-6 pt-4 border-t">
|
|
453
|
+
<div>
|
|
454
|
+
{schema.showCancel !== false && (
|
|
455
|
+
<Button
|
|
456
|
+
variant="ghost"
|
|
457
|
+
onClick={handleCancel}
|
|
458
|
+
>
|
|
459
|
+
{schema.cancelText || 'Cancel'}
|
|
460
|
+
</Button>
|
|
461
|
+
)}
|
|
462
|
+
</div>
|
|
463
|
+
|
|
464
|
+
<div className="flex items-center gap-2">
|
|
465
|
+
{/* Step counter */}
|
|
466
|
+
<span className="text-sm text-muted-foreground mr-2">
|
|
467
|
+
Step {currentStep + 1} of {totalSteps}
|
|
468
|
+
</span>
|
|
469
|
+
|
|
470
|
+
{!isFirstStep && (
|
|
471
|
+
<Button
|
|
472
|
+
variant="outline"
|
|
473
|
+
onClick={handlePrev}
|
|
474
|
+
>
|
|
475
|
+
<ChevronLeft className="h-4 w-4 mr-1" />
|
|
476
|
+
{schema.prevText || 'Back'}
|
|
477
|
+
</Button>
|
|
478
|
+
)}
|
|
479
|
+
|
|
480
|
+
{isLastStep ? (
|
|
481
|
+
<Button
|
|
482
|
+
onClick={() => handleStepSubmit(formData)}
|
|
483
|
+
disabled={submitting || schema.mode === 'view'}
|
|
484
|
+
>
|
|
485
|
+
{submitting ? 'Submitting...' : (schema.submitText || (schema.mode === 'create' ? 'Create' : 'Update'))}
|
|
486
|
+
</Button>
|
|
487
|
+
) : (
|
|
488
|
+
<Button
|
|
489
|
+
onClick={() => handleStepSubmit(formData)}
|
|
490
|
+
>
|
|
491
|
+
{schema.nextText || 'Next'}
|
|
492
|
+
<ChevronRight className="h-4 w-4 ml-1" />
|
|
493
|
+
</Button>
|
|
494
|
+
)}
|
|
495
|
+
</div>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
export default WizardForm;
|