@moontra/moonui-pro 2.6.1 → 2.6.2

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.
@@ -0,0 +1,105 @@
1
+ "use client"
2
+
3
+ import React from "react"
4
+ import { cn } from "../../lib/utils"
5
+ import { FormWizardProvider } from "./form-wizard-context"
6
+ import { FormWizardProgress } from "./form-wizard-progress"
7
+ import { FormWizardStep } from "./form-wizard-step"
8
+ import { FormWizardNavigation } from "./form-wizard-navigation"
9
+ import { FormWizardProps } from "./types"
10
+ import { Card, CardContent } from "../ui/card"
11
+ import { Separator } from "../ui/separator"
12
+
13
+ export const FormWizardPro = React.forwardRef<HTMLDivElement, FormWizardProps>(({
14
+ steps,
15
+ currentStep = 0,
16
+ onStepChange,
17
+ onComplete,
18
+ orientation = 'horizontal',
19
+ progressType = 'linear',
20
+ allowStepSkip = false,
21
+ allowBackNavigation = true,
22
+ validateOnStepChange = true,
23
+ animationType = 'slide',
24
+ animationDuration = 0.3,
25
+ showStepNumbers = true,
26
+ showStepTitles = true,
27
+ showProgressBar = true,
28
+ stepIconPosition = 'top',
29
+ completedStepIcon,
30
+ activeStepIcon,
31
+ errorStepIcon,
32
+ className,
33
+ progressClassName,
34
+ navigationClassName,
35
+ contentClassName,
36
+ stepClassName,
37
+ autoSave = false,
38
+ autoSaveDelay = 2000,
39
+ onAutoSave,
40
+ persistData = false,
41
+ storageKey = "form-wizard-data"
42
+ }, ref) => {
43
+ return (
44
+ <FormWizardProvider
45
+ steps={steps}
46
+ initialStep={currentStep}
47
+ onStepChange={onStepChange}
48
+ onComplete={onComplete}
49
+ validateOnStepChange={validateOnStepChange}
50
+ allowBackNavigation={allowBackNavigation}
51
+ allowStepSkip={allowStepSkip}
52
+ autoSave={autoSave}
53
+ autoSaveDelay={autoSaveDelay}
54
+ onAutoSave={onAutoSave}
55
+ persistData={persistData}
56
+ storageKey={storageKey}
57
+ >
58
+ <div ref={ref} className={cn("w-full", className)}>
59
+ {showProgressBar && (
60
+ <>
61
+ <FormWizardProgress
62
+ className={progressClassName}
63
+ progressType={progressType}
64
+ orientation={orientation}
65
+ showStepNumbers={showStepNumbers}
66
+ showStepTitles={showStepTitles}
67
+ stepIconPosition={stepIconPosition}
68
+ completedStepIcon={completedStepIcon}
69
+ activeStepIcon={activeStepIcon}
70
+ errorStepIcon={errorStepIcon}
71
+ />
72
+ <Separator className="my-8" />
73
+ </>
74
+ )}
75
+
76
+ <Card className={cn("border-0 shadow-none", contentClassName)}>
77
+ <CardContent className="p-0">
78
+ <FormWizardStep
79
+ className={stepClassName}
80
+ animationType={animationType}
81
+ animationDuration={animationDuration}
82
+ />
83
+ </CardContent>
84
+ </Card>
85
+
86
+ <Separator className="my-8" />
87
+
88
+ <FormWizardNavigation
89
+ className={navigationClassName}
90
+ />
91
+ </div>
92
+ </FormWizardProvider>
93
+ )
94
+ })
95
+
96
+ FormWizardPro.displayName = "FormWizardPro"
97
+
98
+ // Export types and hooks
99
+ export type { FormWizardProps, WizardStep, WizardStepContentProps } from "./types"
100
+ export { useFormWizard } from "./form-wizard-context"
101
+
102
+ // Export sub-components for advanced usage
103
+ export { FormWizardProgress } from "./form-wizard-progress"
104
+ export { FormWizardStep } from "./form-wizard-step"
105
+ export { FormWizardNavigation } from "./form-wizard-navigation"
@@ -0,0 +1,76 @@
1
+ import { ReactNode } from "react"
2
+
3
+ export interface WizardStep {
4
+ id: string
5
+ title: string
6
+ description?: string
7
+ icon?: ReactNode | ((props: { isActive: boolean; isCompleted: boolean }) => ReactNode)
8
+ content: ReactNode | ((props: WizardStepContentProps) => ReactNode)
9
+ validation?: () => boolean | Promise<boolean>
10
+ isOptional?: boolean
11
+ isDisabled?: boolean | ((currentStep: number, steps: WizardStep[]) => boolean)
12
+ onEnter?: () => void | Promise<void>
13
+ onExit?: () => void | Promise<void>
14
+ }
15
+
16
+ export interface WizardStepContentProps {
17
+ currentStep: number
18
+ totalSteps: number
19
+ goToNext: () => void
20
+ goToPrevious: () => void
21
+ goToStep: (step: number) => void
22
+ isFirstStep: boolean
23
+ isLastStep: boolean
24
+ stepData: any
25
+ updateStepData: (data: any) => void
26
+ }
27
+
28
+ export interface FormWizardProps {
29
+ steps: WizardStep[]
30
+ currentStep?: number
31
+ onStepChange?: (step: number, previousStep: number) => void
32
+ onComplete?: (data: Record<string, any>) => void | Promise<void>
33
+ orientation?: 'horizontal' | 'vertical'
34
+ progressType?: 'linear' | 'circular' | 'dots' | 'custom'
35
+ allowStepSkip?: boolean
36
+ allowBackNavigation?: boolean
37
+ validateOnStepChange?: boolean
38
+ animationType?: 'slide' | 'fade' | 'scale' | 'none'
39
+ animationDuration?: number
40
+ showStepNumbers?: boolean
41
+ showStepTitles?: boolean
42
+ showProgressBar?: boolean
43
+ stepIconPosition?: 'top' | 'left' | 'inside'
44
+ completedStepIcon?: ReactNode
45
+ activeStepIcon?: ReactNode
46
+ errorStepIcon?: ReactNode
47
+ className?: string
48
+ progressClassName?: string
49
+ navigationClassName?: string
50
+ contentClassName?: string
51
+ stepClassName?: string
52
+ autoSave?: boolean
53
+ autoSaveDelay?: number
54
+ onAutoSave?: (stepId: string, data: any) => void | Promise<void>
55
+ persistData?: boolean
56
+ storageKey?: string
57
+ }
58
+
59
+ export interface WizardContextValue {
60
+ steps: WizardStep[]
61
+ currentStep: number
62
+ stepData: Record<string, any>
63
+ isLoading: boolean
64
+ error: string | null
65
+ goToNext: () => void
66
+ goToPrevious: () => void
67
+ goToStep: (step: number) => void
68
+ updateStepData: (stepId: string, data: any) => void
69
+ validateCurrentStep: () => Promise<boolean>
70
+ completeWizard: () => void
71
+ resetWizard: () => void
72
+ isStepCompleted: (stepIndex: number) => boolean
73
+ isStepAccessible: (stepIndex: number) => boolean
74
+ canGoNext: boolean
75
+ canGoPrevious: boolean
76
+ }
@@ -84,4 +84,13 @@ export * from "./file-upload"
84
84
  export * from "./data-table"
85
85
 
86
86
  // Sidebar
87
- export * from "./sidebar"
87
+ export * from "./sidebar"
88
+
89
+ // Form Wizard
90
+ export * from "./form-wizard"
91
+
92
+ // Multi-Step Form
93
+ export * from "./multi-step-form"
94
+
95
+ // Quiz Form
96
+ export * from "./quiz-form"
@@ -0,0 +1,223 @@
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 { FormWizardPro, 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 MultiStepFormPro<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
+ <FormWizardPro
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
+ }