form-wizard-builder

安装量: 58
排名: #12922

安装

npx skills add https://github.com/patricio0312rev/skills --skill form-wizard-builder

Form Wizard Builder

Create multi-step form experiences with validation, state persistence, and review steps.

Core Workflow Define steps: Break form into logical sections Create schema: Zod/Yup validation for each step Build step components: Individual form sections State management: Shared state across steps (Zustand/Context) Navigation: Next/Back/Skip logic Progress indicator: Visual step tracker Review step: Summary before submission Error handling: Per-step and final validation Basic Wizard Structure // types/wizard.ts export type WizardStep = { id: string; title: string; description?: string; component: React.ComponentType; schema: z.ZodSchema; isOptional?: boolean; };

export type WizardData = { personal: PersonalInfoData; contact: ContactData; preferences: PreferencesData; };

Validation Schemas (Zod) // schemas/wizard.schema.ts import { z } from "zod";

export const personalInfoSchema = z.object({ firstName: z.string().min(2, "First name must be at least 2 characters"), lastName: z.string().min(2, "Last name must be at least 2 characters"), dateOfBirth: z.string().refine((date) => { const age = new Date().getFullYear() - new Date(date).getFullYear(); return age >= 18; }, "Must be at least 18 years old"), });

export const contactSchema = z.object({ email: z.string().email("Invalid email address"), phone: z.string().regex(/^+?[\d\s-()]+$/, "Invalid phone number"), address: z.object({ street: z.string().min(1, "Street is required"), city: z.string().min(1, "City is required"), zipCode: z.string().regex(/^\d{5}(-\d{4})?$/, "Invalid ZIP code"), }), });

export const preferencesSchema = z.object({ notifications: z.object({ email: z.boolean(), sms: z.boolean(), push: z.boolean(), }), interests: z.array(z.string()).min(1, "Select at least one interest"), });

// Complete wizard schema export const wizardSchema = z.object({ personal: personalInfoSchema, contact: contactSchema, preferences: preferencesSchema, });

export type WizardFormData = z.infer;

State Management (Zustand) // stores/wizard.store.ts import { create } from "zustand"; import { persist } from "zustand/middleware";

interface WizardState { currentStep: number; data: Partial; completedSteps: number[]; isSubmitting: boolean;

setCurrentStep: (step: number) => void; updateStepData: (step: string, data: any) => void; markStepComplete: (step: number) => void; nextStep: () => void; prevStep: () => void; resetWizard: () => void; submitWizard: () => Promise; }

export const useWizardStore = create()( persist( (set, get) => ({ currentStep: 0, data: {}, completedSteps: [], isSubmitting: false,

  setCurrentStep: (step) => set({ currentStep: step }),

  updateStepData: (step, newData) =>
    set((state) => ({
      data: {
        ...state.data,
        [step]: { ...state.data[step], ...newData },
      },
    })),

  markStepComplete: (step) =>
    set((state) => ({
      completedSteps: Array.from(new Set([...state.completedSteps, step])),
    })),

  nextStep: () =>
    set((state) => ({
      currentStep: Math.min(state.currentStep + 1, steps.length - 1),
    })),

  prevStep: () =>
    set((state) => ({
      currentStep: Math.max(state.currentStep - 1, 0),
    })),

  resetWizard: () =>
    set({
      currentStep: 0,
      data: {},
      completedSteps: [],
      isSubmitting: false,
    }),

  submitWizard: async () => {
    set({ isSubmitting: true });
    try {
      // Submit to API
      await fetch("/api/wizard", {
        method: "POST",
        body: JSON.stringify(get().data),
      });
      get().resetWizard();
    } catch (error) {
      console.error("Submission failed:", error);
    } finally {
      set({ isSubmitting: false });
    }
  },
}),
{
  name: "wizard-storage",
}

) );

Main Wizard Component // components/Wizard.tsx "use client";

import { useState } from "react"; import { useWizardStore } from "@/stores/wizard.store"; import { ProgressIndicator } from "./ProgressIndicator"; import { PersonalInfoStep } from "./steps/PersonalInfoStep"; import { ContactStep } from "./steps/ContactStep"; import { PreferencesStep } from "./steps/PreferencesStep"; import { ReviewStep } from "./steps/ReviewStep";

const steps = [ { id: "personal", title: "Personal Information", component: PersonalInfoStep, schema: personalInfoSchema, }, { id: "contact", title: "Contact Details", component: ContactStep, schema: contactSchema, }, { id: "preferences", title: "Preferences", component: PreferencesStep, schema: preferencesSchema, isOptional: true, }, { id: "review", title: "Review", component: ReviewStep, schema: z.any(), }, ];

export function Wizard() { const { currentStep } = useWizardStore(); const CurrentStepComponent = steps[currentStep].component;

return (

  <div className="rounded-lg border bg-white p-8 shadow-sm">
    <div className="mb-6">
      <h2 className="text-2xl font-bold">{steps[currentStep].title}</h2>
      {steps[currentStep].description && (
        <p className="text-gray-600">{steps[currentStep].description}</p>
      )}
    </div>

    <CurrentStepComponent />
  </div>
</div>

); }

Progress Indicator // components/ProgressIndicator.tsx import { cn } from "@/lib/utils"; import { CheckIcon } from "@/components/icons";

interface ProgressIndicatorProps { steps: Array<{ id: string; title: string }>; currentStep: number; }

export function ProgressIndicator({ steps, currentStep, }: ProgressIndicatorProps) { return (

返回排行榜