@moontra/moonui-pro 2.8.1 → 2.8.3
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/dist/index.d.ts +1 -43
- package/dist/index.mjs +2284 -13881
- package/package.json +1 -1
- package/src/components/form-wizard/form-wizard-context.tsx +63 -4
- package/src/components/form-wizard/form-wizard-progress.tsx +55 -54
- package/src/components/form-wizard/form-wizard-step.tsx +13 -2
- package/src/components/index.ts +0 -2
- package/src/components/multi-step-form/index.tsx +0 -223
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -95,17 +95,76 @@ export const FormWizardProvider: React.FC<FormWizardProviderProps> = ({
|
|
|
95
95
|
|
|
96
96
|
const validateCurrentStep = useCallback(async (): Promise<boolean> => {
|
|
97
97
|
const step = steps[currentStep]
|
|
98
|
+
|
|
99
|
+
// First check HTML5 validation for required fields
|
|
100
|
+
if (typeof window !== 'undefined') {
|
|
101
|
+
const stepElement = document.querySelector('[data-wizard-step-content]')
|
|
102
|
+
if (stepElement) {
|
|
103
|
+
const requiredInputs = stepElement.querySelectorAll('input[required], select[required], textarea[required]')
|
|
104
|
+
const emptyFields: string[] = []
|
|
105
|
+
|
|
106
|
+
requiredInputs.forEach((input: Element) => {
|
|
107
|
+
const htmlInput = input as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
108
|
+
if (!htmlInput.value || htmlInput.value.trim() === '') {
|
|
109
|
+
const label = document.querySelector(`label[for="${htmlInput.id}"]`)
|
|
110
|
+
const fieldName = label ? (label.textContent?.replace(' *', '') || 'Field') : htmlInput.name || htmlInput.id || 'Field'
|
|
111
|
+
emptyFields.push(fieldName)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Check for required checkboxes
|
|
116
|
+
const requiredCheckboxes = stepElement.querySelectorAll('input[type="checkbox"][required]')
|
|
117
|
+
requiredCheckboxes.forEach((checkbox: Element) => {
|
|
118
|
+
const htmlCheckbox = checkbox as HTMLInputElement
|
|
119
|
+
if (!htmlCheckbox.checked) {
|
|
120
|
+
const label = document.querySelector(`label[for="${htmlCheckbox.id}"]`)
|
|
121
|
+
const fieldName = label ? (label.textContent?.replace(' *', '') || 'Checkbox') : htmlCheckbox.name || htmlCheckbox.id || 'Checkbox'
|
|
122
|
+
emptyFields.push(fieldName)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
if (emptyFields.length > 0) {
|
|
127
|
+
setError(`Please fill in the following required fields: ${emptyFields.join(', ')}`)
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Then run custom validation if provided
|
|
98
134
|
if (!step.validation) return true
|
|
99
135
|
|
|
100
136
|
setIsLoading(true)
|
|
101
137
|
setError(null)
|
|
102
138
|
|
|
103
139
|
try {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
140
|
+
const result = await step.validation()
|
|
141
|
+
|
|
142
|
+
// Handle boolean return
|
|
143
|
+
if (typeof result === 'boolean') {
|
|
144
|
+
if (!result) {
|
|
145
|
+
setError("Please complete all required fields before proceeding")
|
|
146
|
+
}
|
|
147
|
+
return result
|
|
107
148
|
}
|
|
108
|
-
|
|
149
|
+
|
|
150
|
+
// Handle object return {isValid: boolean, error?: string, errors?: string[]}
|
|
151
|
+
if (typeof result === 'object' && result !== null && 'isValid' in result) {
|
|
152
|
+
const validationResult = result as { isValid: boolean; error?: string; errors?: string[] }
|
|
153
|
+
if (!validationResult.isValid) {
|
|
154
|
+
if (validationResult.error) {
|
|
155
|
+
setError(validationResult.error)
|
|
156
|
+
} else if (validationResult.errors && Array.isArray(validationResult.errors)) {
|
|
157
|
+
setError(validationResult.errors.join(', '))
|
|
158
|
+
} else {
|
|
159
|
+
setError("Please complete all required fields before proceeding")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return validationResult.isValid
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Invalid return type
|
|
166
|
+
setError("Invalid validation response")
|
|
167
|
+
return false
|
|
109
168
|
} catch (err) {
|
|
110
169
|
setError(err instanceof Error ? err.message : "Validation failed")
|
|
111
170
|
return false
|
|
@@ -41,31 +41,7 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
|
|
|
41
41
|
)}>
|
|
42
42
|
{orientation === 'horizontal' ? (
|
|
43
43
|
<div className="relative w-full">
|
|
44
|
-
{/*
|
|
45
|
-
<div
|
|
46
|
-
className="absolute left-6 right-6 h-[2px] bg-gray-200 dark:bg-gray-700"
|
|
47
|
-
style={{
|
|
48
|
-
top: showStepTitles ? '80px' : '24px'
|
|
49
|
-
}}
|
|
50
|
-
/>
|
|
51
|
-
|
|
52
|
-
{/* Progress Line Fill */}
|
|
53
|
-
<motion.div
|
|
54
|
-
className="absolute left-6 h-[2px] bg-primary"
|
|
55
|
-
style={{
|
|
56
|
-
top: showStepTitles ? '80px' : '24px',
|
|
57
|
-
right: `${100 - ((currentStep / (steps.length - 1)) * 100)}%`
|
|
58
|
-
}}
|
|
59
|
-
initial={{ right: '100%' }}
|
|
60
|
-
animate={{
|
|
61
|
-
right: steps.length > 1
|
|
62
|
-
? `${100 - ((currentStep / (steps.length - 1)) * 100)}%`
|
|
63
|
-
: '100%'
|
|
64
|
-
}}
|
|
65
|
-
transition={{ duration: 0.5, ease: "easeInOut" }}
|
|
66
|
-
/>
|
|
67
|
-
|
|
68
|
-
{/* Steps Container */}
|
|
44
|
+
{/* Steps Container with connecting lines */}
|
|
69
45
|
<div className="relative flex items-start justify-between w-full">
|
|
70
46
|
{steps.map((step, index) => {
|
|
71
47
|
const isActive = index === currentStep
|
|
@@ -78,7 +54,7 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
|
|
|
78
54
|
: step.icon
|
|
79
55
|
|
|
80
56
|
return (
|
|
81
|
-
<div key={step.id} className="relative flex flex-col items-center">
|
|
57
|
+
<div key={step.id} className="relative flex-1 flex flex-col items-center">
|
|
82
58
|
{/* Step Title & Description - Above the circle */}
|
|
83
59
|
{showStepTitles && (
|
|
84
60
|
<div className="text-center mb-3 min-h-[40px] px-2">
|
|
@@ -98,35 +74,60 @@ export const FormWizardProgress: React.FC<FormWizardProgressProps> = ({
|
|
|
98
74
|
</div>
|
|
99
75
|
)}
|
|
100
76
|
|
|
101
|
-
{/* Step Circle */}
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
|
|
124
|
-
) : showStepNumbers ? (
|
|
125
|
-
<span className="text-sm font-semibold">{index + 1}</span>
|
|
126
|
-
) : (
|
|
127
|
-
<Circle className="w-4 h-4" />
|
|
77
|
+
{/* Step Circle Container with line */}
|
|
78
|
+
<div className="relative flex items-center w-full">
|
|
79
|
+
{/* Connecting Line - Positioned after each circle except the last */}
|
|
80
|
+
{index < steps.length - 1 && (
|
|
81
|
+
<div
|
|
82
|
+
className="absolute left-1/2 w-full h-[2px]"
|
|
83
|
+
style={{
|
|
84
|
+
width: 'calc(100% + 24px)',
|
|
85
|
+
marginLeft: '24px'
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<div className="h-full bg-gray-200 dark:bg-gray-700" />
|
|
89
|
+
{index < currentStep && (
|
|
90
|
+
<motion.div
|
|
91
|
+
className="absolute inset-0 bg-primary"
|
|
92
|
+
initial={{ scaleX: 0 }}
|
|
93
|
+
animate={{ scaleX: 1 }}
|
|
94
|
+
transition={{ duration: 0.5, delay: index * 0.1 }}
|
|
95
|
+
style={{ transformOrigin: 'left' }}
|
|
96
|
+
/>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
128
99
|
)}
|
|
129
|
-
|
|
100
|
+
|
|
101
|
+
{/* Step Circle */}
|
|
102
|
+
<motion.div
|
|
103
|
+
initial={{ scale: 0.8 }}
|
|
104
|
+
animate={{ scale: isActive ? 1.05 : 1 }}
|
|
105
|
+
transition={{ duration: 0.2 }}
|
|
106
|
+
className={cn(
|
|
107
|
+
"relative z-10 flex items-center justify-center rounded-full transition-all duration-300 mx-auto",
|
|
108
|
+
"w-12 h-12 border-2",
|
|
109
|
+
isActive && "border-primary bg-primary text-primary-foreground shadow-lg shadow-primary/20",
|
|
110
|
+
isCompleted && !isActive && "border-primary bg-primary text-primary-foreground",
|
|
111
|
+
!isActive && !isCompleted && isAccessible && "border-gray-300 bg-background text-gray-500 hover:border-gray-400 dark:border-gray-600 dark:text-gray-400",
|
|
112
|
+
!isAccessible && "border-gray-200 bg-gray-50 text-gray-400 cursor-not-allowed dark:border-gray-700 dark:bg-gray-900 dark:text-gray-600",
|
|
113
|
+
hasError && "border-destructive bg-destructive text-destructive-foreground"
|
|
114
|
+
)}
|
|
115
|
+
>
|
|
116
|
+
{hasError ? (
|
|
117
|
+
errorStepIcon
|
|
118
|
+
) : isCompleted && !isActive ? (
|
|
119
|
+
completedStepIcon
|
|
120
|
+
) : isActive && activeStepIcon ? (
|
|
121
|
+
activeStepIcon
|
|
122
|
+
) : StepIcon ? (
|
|
123
|
+
<span className="w-5 h-5 flex items-center justify-center">{StepIcon}</span>
|
|
124
|
+
) : showStepNumbers ? (
|
|
125
|
+
<span className="text-sm font-semibold">{index + 1}</span>
|
|
126
|
+
) : (
|
|
127
|
+
<Circle className="w-4 h-4" />
|
|
128
|
+
)}
|
|
129
|
+
</motion.div>
|
|
130
|
+
</div>
|
|
130
131
|
|
|
131
132
|
{/* Step Label Below Circle */}
|
|
132
133
|
<div className="mt-3">
|
|
@@ -5,6 +5,8 @@ import { motion, AnimatePresence } from "framer-motion"
|
|
|
5
5
|
import { cn } from "../../lib/utils"
|
|
6
6
|
import { useFormWizard } from "./form-wizard-context"
|
|
7
7
|
import { FormWizardProps } from "./types"
|
|
8
|
+
import { Alert, AlertDescription } from "../ui/alert"
|
|
9
|
+
import { AlertCircle } from "lucide-react"
|
|
8
10
|
|
|
9
11
|
interface FormWizardStepProps {
|
|
10
12
|
className?: string
|
|
@@ -49,7 +51,7 @@ export const FormWizardStep: React.FC<FormWizardStepProps> = ({
|
|
|
49
51
|
animationType = 'slide',
|
|
50
52
|
animationDuration = 0.3
|
|
51
53
|
}) => {
|
|
52
|
-
const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData } = useFormWizard()
|
|
54
|
+
const { steps, currentStep, goToNext, goToPrevious, goToStep, updateStepData, stepData, error } = useFormWizard()
|
|
53
55
|
const [direction, setDirection] = React.useState(0)
|
|
54
56
|
const previousStep = React.useRef(currentStep)
|
|
55
57
|
|
|
@@ -92,8 +94,17 @@ export const FormWizardStep: React.FC<FormWizardStepProps> = ({
|
|
|
92
94
|
ease: "easeInOut"
|
|
93
95
|
}}
|
|
94
96
|
className={cn("w-full", className)}
|
|
97
|
+
data-wizard-step-content
|
|
95
98
|
>
|
|
96
|
-
|
|
99
|
+
<div className="space-y-4">
|
|
100
|
+
{error && (
|
|
101
|
+
<Alert variant="error">
|
|
102
|
+
<AlertCircle className="h-4 w-4" />
|
|
103
|
+
<AlertDescription>{error}</AlertDescription>
|
|
104
|
+
</Alert>
|
|
105
|
+
)}
|
|
106
|
+
{content}
|
|
107
|
+
</div>
|
|
97
108
|
</motion.div>
|
|
98
109
|
</AnimatePresence>
|
|
99
110
|
)
|
package/src/components/index.ts
CHANGED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
|
|
3
|
-
import React from "react"
|
|
4
|
-
import { useForm, FormProvider, UseFormReturn, FieldValues, DefaultValues } from "react-hook-form"
|
|
5
|
-
import { zodResolver } from "@hookform/resolvers/zod"
|
|
6
|
-
import { z } from "zod"
|
|
7
|
-
import { MoonUIFormWizardPro, FormWizardProps, WizardStep } from "../form-wizard"
|
|
8
|
-
import { Alert, AlertDescription } from "../ui/alert"
|
|
9
|
-
import { AlertCircle } from "lucide-react"
|
|
10
|
-
|
|
11
|
-
export interface MultiStepFormField {
|
|
12
|
-
name: string
|
|
13
|
-
label: string
|
|
14
|
-
type: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'textarea' | 'select' | 'checkbox' | 'radio' | 'switch' | 'file' | 'date' | 'time' | 'datetime'
|
|
15
|
-
placeholder?: string
|
|
16
|
-
description?: string
|
|
17
|
-
required?: boolean
|
|
18
|
-
validation?: z.ZodSchema<any>
|
|
19
|
-
options?: Array<{ value: string; label: string }>
|
|
20
|
-
defaultValue?: any
|
|
21
|
-
disabled?: boolean
|
|
22
|
-
autoComplete?: string
|
|
23
|
-
min?: number | string
|
|
24
|
-
max?: number | string
|
|
25
|
-
step?: number | string
|
|
26
|
-
pattern?: string
|
|
27
|
-
multiple?: boolean
|
|
28
|
-
accept?: string
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface MultiStepFormStep extends Omit<WizardStep, 'content' | 'validation'> {
|
|
32
|
-
fields: MultiStepFormField[]
|
|
33
|
-
schema?: z.ZodSchema<any>
|
|
34
|
-
submitLabel?: string
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface MultiStepFormProps<TFormData extends FieldValues = FieldValues> extends Omit<FormWizardProps, 'steps'> {
|
|
38
|
-
steps: MultiStepFormStep[]
|
|
39
|
-
onSubmit: (data: TFormData) => void | Promise<void>
|
|
40
|
-
defaultValues?: DefaultValues<TFormData>
|
|
41
|
-
showErrorSummary?: boolean
|
|
42
|
-
fieldClassName?: string
|
|
43
|
-
labelClassName?: string
|
|
44
|
-
errorClassName?: string
|
|
45
|
-
descriptionClassName?: string
|
|
46
|
-
renderField?: (field: MultiStepFormField, form: UseFormReturn<TFormData>) => React.ReactNode
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function createStepSchema(fields: MultiStepFormField[]): z.ZodSchema<any> {
|
|
50
|
-
const shape: Record<string, z.ZodSchema<any>> = {}
|
|
51
|
-
|
|
52
|
-
fields.forEach(field => {
|
|
53
|
-
if (field.validation) {
|
|
54
|
-
shape[field.name] = field.validation
|
|
55
|
-
} else {
|
|
56
|
-
let schema: z.ZodSchema<any> = z.any()
|
|
57
|
-
|
|
58
|
-
switch (field.type) {
|
|
59
|
-
case 'email':
|
|
60
|
-
schema = z.string().email('Invalid email address')
|
|
61
|
-
break
|
|
62
|
-
case 'number':
|
|
63
|
-
schema = z.number()
|
|
64
|
-
if (field.min !== undefined) schema = (schema as z.ZodNumber).min(Number(field.min))
|
|
65
|
-
if (field.max !== undefined) schema = (schema as z.ZodNumber).max(Number(field.max))
|
|
66
|
-
break
|
|
67
|
-
case 'url':
|
|
68
|
-
schema = z.string().url('Invalid URL')
|
|
69
|
-
break
|
|
70
|
-
case 'tel':
|
|
71
|
-
schema = z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number')
|
|
72
|
-
break
|
|
73
|
-
case 'checkbox':
|
|
74
|
-
case 'switch':
|
|
75
|
-
schema = z.boolean()
|
|
76
|
-
break
|
|
77
|
-
default:
|
|
78
|
-
schema = z.string()
|
|
79
|
-
if (field.min !== undefined) schema = (schema as z.ZodString).min(Number(field.min))
|
|
80
|
-
if (field.max !== undefined) schema = (schema as z.ZodString).max(Number(field.max))
|
|
81
|
-
if (field.pattern) schema = (schema as z.ZodString).regex(new RegExp(field.pattern))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!field.required) {
|
|
85
|
-
schema = schema.optional()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
shape[field.name] = schema
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
return z.object(shape)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function MoonUIMultiStepFormPro<TFormData extends FieldValues = FieldValues>({
|
|
96
|
-
steps,
|
|
97
|
-
onSubmit,
|
|
98
|
-
defaultValues,
|
|
99
|
-
showErrorSummary = true,
|
|
100
|
-
fieldClassName,
|
|
101
|
-
labelClassName,
|
|
102
|
-
errorClassName,
|
|
103
|
-
descriptionClassName,
|
|
104
|
-
renderField,
|
|
105
|
-
...wizardProps
|
|
106
|
-
}: MultiStepFormProps<TFormData>) {
|
|
107
|
-
// Create combined schema from all steps
|
|
108
|
-
const combinedSchema = React.useMemo(() => {
|
|
109
|
-
const shape: Record<string, z.ZodSchema<any>> = {}
|
|
110
|
-
|
|
111
|
-
steps.forEach(step => {
|
|
112
|
-
const stepSchema = step.schema || createStepSchema(step.fields)
|
|
113
|
-
if (stepSchema instanceof z.ZodObject) {
|
|
114
|
-
Object.assign(shape, stepSchema.shape)
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
return z.object(shape)
|
|
119
|
-
}, [steps])
|
|
120
|
-
|
|
121
|
-
const form = useForm<TFormData>({
|
|
122
|
-
resolver: zodResolver(combinedSchema) as any,
|
|
123
|
-
defaultValues,
|
|
124
|
-
mode: 'onChange'
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
const wizardSteps: WizardStep[] = steps.map((step, stepIndex) => ({
|
|
128
|
-
...step,
|
|
129
|
-
validation: async () => {
|
|
130
|
-
// Validate only the fields in the current step
|
|
131
|
-
const currentStepFields = step.fields.map(f => f.name)
|
|
132
|
-
const result = await form.trigger(currentStepFields as any)
|
|
133
|
-
return result
|
|
134
|
-
},
|
|
135
|
-
content: ({ updateStepData }) => {
|
|
136
|
-
const errors = form.formState.errors
|
|
137
|
-
const stepErrors = step.fields
|
|
138
|
-
.map(field => errors[field.name])
|
|
139
|
-
.filter(Boolean)
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<FormProvider {...form}>
|
|
143
|
-
<form className="space-y-6">
|
|
144
|
-
{showErrorSummary && stepErrors.length > 0 && (
|
|
145
|
-
<Alert variant="error">
|
|
146
|
-
<AlertCircle className="h-4 w-4" />
|
|
147
|
-
<AlertDescription>
|
|
148
|
-
Please fix the errors below before proceeding.
|
|
149
|
-
</AlertDescription>
|
|
150
|
-
</Alert>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{step.fields.map(field => {
|
|
154
|
-
if (renderField) {
|
|
155
|
-
return <div key={field.name}>{renderField(field, form)}</div>
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Default field rendering
|
|
159
|
-
return (
|
|
160
|
-
<div key={field.name} className={fieldClassName}>
|
|
161
|
-
<label
|
|
162
|
-
htmlFor={field.name}
|
|
163
|
-
className={cn(
|
|
164
|
-
"block text-sm font-medium",
|
|
165
|
-
labelClassName
|
|
166
|
-
)}
|
|
167
|
-
>
|
|
168
|
-
{field.label}
|
|
169
|
-
{field.required && <span className="text-destructive ml-1">*</span>}
|
|
170
|
-
</label>
|
|
171
|
-
|
|
172
|
-
{field.description && (
|
|
173
|
-
<p className={cn("text-sm text-muted-foreground mt-1", descriptionClassName)}>
|
|
174
|
-
{field.description}
|
|
175
|
-
</p>
|
|
176
|
-
)}
|
|
177
|
-
|
|
178
|
-
<div className="mt-2">
|
|
179
|
-
{/* Field rendering would go here - simplified for brevity */}
|
|
180
|
-
<input
|
|
181
|
-
{...form.register(field.name as any)}
|
|
182
|
-
id={field.name}
|
|
183
|
-
type={field.type}
|
|
184
|
-
placeholder={field.placeholder}
|
|
185
|
-
disabled={field.disabled}
|
|
186
|
-
className="w-full px-3 py-2 border rounded-md"
|
|
187
|
-
/>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{errors[field.name] && (
|
|
191
|
-
<p className={cn("text-sm text-destructive mt-1", errorClassName)}>
|
|
192
|
-
{errors[field.name]?.message as string}
|
|
193
|
-
</p>
|
|
194
|
-
)}
|
|
195
|
-
</div>
|
|
196
|
-
)
|
|
197
|
-
})}
|
|
198
|
-
</form>
|
|
199
|
-
</FormProvider>
|
|
200
|
-
)
|
|
201
|
-
}
|
|
202
|
-
}))
|
|
203
|
-
|
|
204
|
-
const handleComplete = async (stepData: Record<string, any>) => {
|
|
205
|
-
const isValid = await form.trigger()
|
|
206
|
-
if (isValid) {
|
|
207
|
-
await onSubmit(form.getValues())
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return (
|
|
212
|
-
<MoonUIFormWizardPro
|
|
213
|
-
{...wizardProps}
|
|
214
|
-
steps={wizardSteps}
|
|
215
|
-
onComplete={handleComplete}
|
|
216
|
-
/>
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Utility function for better imports
|
|
221
|
-
function cn(...classes: (string | undefined | false)[]) {
|
|
222
|
-
return classes.filter(Boolean).join(' ')
|
|
223
|
-
}
|