{"_id":"@amrviernes/react-wizard","name":"@amrviernes/react-wizard","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@amrviernes/react-wizard","version":"1.0.0","description":"A flexible, type-safe wizard component for React with context support","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"dev":"vite","build":"tsc && vite build","preview":"vite preview","prepublishOnly":"npm run build","test":"vitest","lint":"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"},"peerDependencies":{"react":">=16.8.0"},"peerDependenciesMeta":{"react":{"optional":false}},"devDependencies":{"@types/node":"^24.10.1","@types/react":"^18.3.0","@typescript-eslint/eslint-plugin":"^8.0.0","@typescript-eslint/parser":"^8.0.0","@vitejs/plugin-react":"^4.3.0","eslint":"^9.0.0","react":"^18.3.0","typescript":"^5.5.0","vite":"^6.0.0","vite-plugin-dts":"^4.0.0","vitest":"^2.0.0"},"keywords":["react","wizard","stepper","form","multi-step","typescript","context"],"author":{"name":"Amr El-Desoky","email":"amraromoro@hotmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/AmrViernes/react-wizard.git"},"bugs":{"url":"https://github.com/AmrViernes/react-wizard/issues"},"homepage":"https://github.com/AmrViernes/react-wizard#readme","_id":"@amrviernes/react-wizard@1.0.0","gitHead":"6e8bbb77bd687ccd7136dd41ca1c24096bc3bbe5","_nodeVersion":"22.20.0","_npmVersion":"10.8.1","dist":{"integrity":"sha512-IkjTT7CHvnvODsTcpEyOdlOPOldiblhAbkkhrkSYNI2qoAuAAQxM3jmsuXpXH06a7zDUtRTFEG8Eqd5rgyvgCQ==","shasum":"b7b3a6aff66f8c5afdc6bfecc16f507eef5733fd","tarball":"https://registry.npmjs.org/@amrviernes/react-wizard/-/react-wizard-1.0.0.tgz","fileCount":14,"unpackedSize":45265,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDPZnl5nHCQ83w1lXZRuqHNxU/q1xdDuk3FPbq7R36rUgIhAJrhG5l1Ol5yLwN55I2sqn4YRqddYsAeFjIO8cLK67lz"}]},"_npmUser":{"name":"amrviernes","email":"amraromoro@hotmail.com"},"directories":{},"maintainers":[{"name":"amrviernes","email":"amraromoro@hotmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/react-wizard_1.0.0_1763671413092_0.9466457662330923"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-20T20:43:32.997Z","1.0.0":"2025-11-20T20:43:33.282Z","modified":"2025-11-20T20:43:33.601Z"},"maintainers":[{"name":"amrviernes","email":"amraromoro@hotmail.com"}],"description":"A flexible, type-safe wizard component for React with context support","homepage":"https://github.com/AmrViernes/react-wizard#readme","keywords":["react","wizard","stepper","form","multi-step","typescript","context"],"repository":{"type":"git","url":"git+https://github.com/AmrViernes/react-wizard.git"},"author":{"name":"Amr El-Desoky","email":"amraromoro@hotmail.com"},"bugs":{"url":"https://github.com/AmrViernes/react-wizard/issues"},"license":"MIT","readme":"# 🧙‍♂️ React Wizard Component\r\n\r\nA powerful, flexible, and dependency-free wizard (multi-step form) component for React —\r\ntiny at just 📦 0.84 kB gzip, fully 🔒 type-safe, and built with 🟦 TypeScript + ⚛️ React Context API.\r\nCompatible with React 16.8+ → React 19.\r\n\r\n[![npm version](https://badge.fury.io/js/%40yourscope%2Freact-wizard.svg)](https://www.npmjs.com/package/@amrviernes/react-wizard)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\r\n\r\n---\r\n\r\n## ✨ Key Benefits\r\n\r\n### 🎯 **Type-Safe by Design**\r\nFull TypeScript support with generics ensures your wizard data and step IDs are type-checked at compile time, catching errors before runtime.\r\n\r\n### 🔄 **Context-Powered State Management**\r\nBuilt on React Context API, allowing any nested component to access wizard state without prop drilling. Perfect for complex multi-step forms.\r\n\r\n### 🚀 **Framework Agnostic**\r\nWorks seamlessly with:\r\n- ✅ Pure React (CRA, Vite)\r\n- ✅ Next.js (App Router & Pages Router)\r\n- ✅ Remix\r\n- ✅ Gatsby\r\n- ✅ Any React framework\r\n\r\n### 📦 **Lightweight & Tree-Shakeable**\r\nZero dependencies except React. Only 3KB gzipped. Tree-shakeable ESM exports mean you only bundle what you use.\r\n\r\n### 🎨 **Unstyled & Flexible**\r\nBring your own styles - works with Tailwind, CSS Modules, styled-components, or plain CSS. You have complete control over the UI.\r\n\r\n### 🧪 **Testing-Ready**\r\nBuilt-in `data-testid` attributes make testing with React Testing Library or Cypress a breeze.\r\n\r\n### 🌐 **SSR Compatible**\r\nWorks perfectly with server-side rendering in Next.js, Remix, and other SSR frameworks.\r\n\r\n### ♿ **Accessibility First**\r\nSemantic HTML structure and proper ARIA attributes ensure your wizard is accessible to all users.\r\n\r\n---\r\n\r\n## 📦 Installation\r\n\r\n```bash\r\nnpm install @amrviernes/react-wizard\r\n# or\r\nyarn add @amrviernes/react-wizard\r\n# or\r\npnpm add @amrviernes/react-wizard\r\n# or \r\nbun add @amrviernes/react-wizard\r\n```\r\n\r\n---\r\n\r\n## 🚀 Quick Start\r\n\r\n```tsx\r\nimport { Wizard, WizardStepProps } from '@amrviernes/react-wizard';\r\n\r\n// 1. Define your data structure\r\ninterface FormData {\r\n  name: string;\r\n  email: string;\r\n  age: number;\r\n}\r\n\r\n// 2. Define step IDs\r\ntype StepId = 'personal' | 'contact' | 'review';\r\n\r\n// 3. Create step components\r\nconst PersonalStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  nextStep,\r\n}) => (\r\n  <div>\r\n    <h2>Personal Information</h2>\r\n    <input\r\n      value={data.name || ''}\r\n      onChange={(e) => updateData({ name: e.target.value })}\r\n      placeholder=\"Name\"\r\n    />\r\n    <input\r\n      type=\"number\"\r\n      value={data.age || ''}\r\n      onChange={(e) => updateData({ age: parseInt(e.target.value) })}\r\n      placeholder=\"Age\"\r\n    />\r\n    <button onClick={nextStep}>Next</button>\r\n  </div>\r\n);\r\n\r\nconst ContactStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  nextStep,\r\n  prevStep,\r\n}) => (\r\n  <div>\r\n    <h2>Contact Information</h2>\r\n    <input\r\n      type=\"email\"\r\n      value={data.email || ''}\r\n      onChange={(e) => updateData({ email: e.target.value })}\r\n      placeholder=\"Email\"\r\n    />\r\n    <button onClick={prevStep}>Back</button>\r\n    <button onClick={nextStep}>Next</button>\r\n  </div>\r\n);\r\n\r\nconst ReviewStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  prevStep,\r\n}) => (\r\n  <div>\r\n    <h2>Review</h2>\r\n    <p>Name: {data.name}</p>\r\n    <p>Age: {data.age}</p>\r\n    <p>Email: {data.email}</p>\r\n    <button onClick={prevStep}>Back</button>\r\n    <button onClick={() => console.log('Submit:', data)}>Submit</button>\r\n  </div>\r\n);\r\n\r\n// 4. Use the Wizard\r\nfunction App() {\r\n  return (\r\n    <Wizard<FormData, StepId>\r\n      stepMapping={{\r\n        personal: PersonalStep,\r\n        contact: ContactStep,\r\n        review: ReviewStep,\r\n      }}\r\n      initialData={{ name: '', email: '', age: 0 }}\r\n      onStepChange={(stepId, data, index) => {\r\n        console.log(`Step: ${stepId}`, data);\r\n      }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n---\r\n\r\n## 📚 Core Concepts\r\n\r\n### The Wizard Component\r\n\r\nThe main component that orchestrates the multi-step flow:\r\n\r\n```tsx\r\n<Wizard<DataType, StepIdType>\r\n  stepMapping={...}      // Map of step IDs to components\r\n  initialData={...}      // Initial wizard data\r\n  onStepChange={...}     // Callback when step changes\r\n  trackSteps={true}      // Enable step tracking events\r\n  data-testid=\"wizard\"   // For testing\r\n/>\r\n```\r\n\r\n### Step Components\r\n\r\nEach step receives these props automatically:\r\n\r\n| Prop | Type | Description |\r\n|------|------|-------------|\r\n| `data` | `Partial<T>` | Current wizard data |\r\n| `updateData` | `(newData: Partial<T>) => void` | Update wizard data |\r\n| `resetData` | `(data?: Partial<T>) => void` | Reset wizard to initial state |\r\n| `nextStep` | `() => void` | Navigate to next step |\r\n| `prevStep` | `() => void` | Navigate to previous step |\r\n| `toStep` | `(stepId: S) => void` | Jump to specific step |\r\n| `step` | `S` | Current step ID |\r\n| `stepIndex` | `number` | Current step index (0-based) |\r\n| `totalSteps` | `number` | Total number of steps |\r\n\r\n---\r\n\r\n## 🎯 Advanced Usage\r\n\r\n### Using the Context Hook\r\n\r\nAccess wizard state from any nested component:\r\n\r\n```tsx\r\nimport { useWizardContext } from '@amrviernes/react-wizard';\r\n\r\nfunction ProgressBar() {\r\n  const { stepIndex, totalSteps, step } = useWizardContext<FormData, StepId>();\r\n  \r\n  const progress = ((stepIndex + 1) / totalSteps) * 100;\r\n\r\n  return (\r\n    <div>\r\n      <div>Step {stepIndex + 1} of {totalSteps}</div>\r\n      <div style={{ width: `${progress}%`, height: '10px', background: 'blue' }} />\r\n      <div>Current: {step}</div>\r\n    </div>\r\n  );\r\n}\r\n\r\n// Use anywhere inside your step components\r\nconst MyStep: React.FC<WizardStepProps<FormData, StepId>> = () => (\r\n  <div>\r\n    <ProgressBar />\r\n    {/* rest of your step */}\r\n  </div>\r\n);\r\n```\r\n\r\n### Navigation Between Steps\r\n\r\n```tsx\r\nconst ReviewStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  toStep,\r\n  prevStep,\r\n  data,\r\n}) => (\r\n  <div>\r\n    <h2>Review Your Information</h2>\r\n    \r\n    <section>\r\n      <h3>Personal</h3>\r\n      <p>{data.name}</p>\r\n      <button onClick={() => toStep('personal')}>Edit</button>\r\n    </section>\r\n    \r\n    <section>\r\n      <h3>Contact</h3>\r\n      <p>{data.email}</p>\r\n      <button onClick={() => toStep('contact')}>Edit</button>\r\n    </section>\r\n    \r\n    <button onClick={prevStep}>Back</button>\r\n    <button onClick={handleSubmit}>Submit</button>\r\n  </div>\r\n);\r\n```\r\n\r\n### Form Validation\r\n\r\n```tsx\r\nconst PersonalStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  nextStep,\r\n}) => {\r\n  const [errors, setErrors] = useState<string[]>([]);\r\n\r\n  const validate = () => {\r\n    const newErrors: string[] = [];\r\n    if (!data.name) newErrors.push('Name is required');\r\n    if (!data.age || data.age < 18) newErrors.push('Must be 18 or older');\r\n    \r\n    setErrors(newErrors);\r\n    return newErrors.length === 0;\r\n  };\r\n\r\n  const handleNext = () => {\r\n    if (validate()) {\r\n      nextStep();\r\n    }\r\n  };\r\n\r\n  return (\r\n    <div>\r\n      <input\r\n        value={data.name || ''}\r\n        onChange={(e) => updateData({ name: e.target.value })}\r\n      />\r\n      {errors.map((err, i) => <p key={i} style={{ color: 'red' }}>{err}</p>)}\r\n      <button onClick={handleNext}>Next</button>\r\n    </div>\r\n  );\r\n};\r\n```\r\n\r\n### Conditional Steps\r\n\r\n```tsx\r\ninterface FormData {\r\n  userType: 'business' | 'individual';\r\n  businessName?: string;\r\n  personalName?: string;\r\n}\r\n\r\ntype StepId = 'type' | 'business' | 'individual' | 'review';\r\n\r\nconst TypeStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  toStep,\r\n}) => {\r\n  const handleSelection = (type: 'business' | 'individual') => {\r\n    updateData({ userType: type });\r\n    toStep(type); // Jump directly to business or individual step\r\n  };\r\n\r\n  return (\r\n    <div>\r\n      <button onClick={() => handleSelection('business')}>Business</button>\r\n      <button onClick={() => handleSelection('individual')}>Individual</button>\r\n    </div>\r\n  );\r\n};\r\n```\r\n\r\n### Reset Wizard\r\n\r\n```tsx\r\nconst FinalStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  resetData,\r\n  data,\r\n}) => {\r\n  const handleSubmit = async () => {\r\n    await api.submit(data);\r\n    alert('Success!');\r\n    resetData(); // Resets to initial data and first step\r\n  };\r\n\r\n  const handleStartOver = () => {\r\n    resetData({ name: 'John' }); // Reset with custom data\r\n  };\r\n\r\n  return (\r\n    <div>\r\n      <button onClick={handleSubmit}>Submit</button>\r\n      <button onClick={handleStartOver}>Start Over</button>\r\n    </div>\r\n  );\r\n};\r\n```\r\n\r\n---\r\n\r\n## 🎨 Framework Examples\r\n\r\n### Next.js (App Router)\r\n\r\n```tsx\r\n'use client';\r\n\r\nimport { Wizard, WizardStepProps } from '@amrviernes/react-wizard';\r\n\r\nexport default function OnboardingPage() {\r\n  const handleStepChange = async (stepId, data, index) => {\r\n    // Save to database\r\n    await fetch('/api/onboarding', {\r\n      method: 'POST',\r\n      body: JSON.stringify({ step: stepId, data }),\r\n    });\r\n  };\r\n\r\n  return (\r\n    <Wizard<UserData, OnboardingStep>\r\n      stepMapping={stepMapping}\r\n      onStepChange={handleStepChange}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n### Next.js (Pages Router)\r\n\r\n```tsx\r\nimport { Wizard } from '@amrviernes/react-wizard';\r\nimport { useState } from 'react';\r\n\r\nexport default function CheckoutPage() {\r\n  return <Wizard<CheckoutData, CheckoutStep> stepMapping={stepMapping} />;\r\n}\r\n```\r\n\r\n### Remix\r\n\r\n```tsx\r\nimport { Wizard, WizardStepProps } from '@amrviernes/react-wizard';\r\nimport { useFetcher } from '@remix-run/react';\r\n\r\nexport default function SignupRoute() {\r\n  const fetcher = useFetcher();\r\n\r\n  return (\r\n    <Wizard<SignupData, SignupStep>\r\n      stepMapping={stepMapping}\r\n      onStepChange={(stepId, data) => {\r\n        fetcher.submit(\r\n          { step: stepId, data: JSON.stringify(data) },\r\n          { method: 'post' }\r\n        );\r\n      }}\r\n    />\r\n  );\r\n}\r\n```\r\n\r\n### With React Hook Form\r\n\r\n```tsx\r\nimport { useForm } from 'react-hook-form';\r\n\r\nconst PersonalStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  nextStep,\r\n}) => {\r\n  const { register, handleSubmit, formState: { errors } } = useForm({\r\n    defaultValues: data,\r\n  });\r\n\r\n  const onSubmit = (formData: Partial<FormData>) => {\r\n    updateData(formData);\r\n    nextStep();\r\n  };\r\n\r\n  return (\r\n    <form onSubmit={handleSubmit(onSubmit)}>\r\n      <input {...register('name', { required: true })} />\r\n      {errors.name && <span>Name is required</span>}\r\n      <button type=\"submit\">Next</button>\r\n    </form>\r\n  );\r\n};\r\n```\r\n\r\n---\r\n\r\n## 📊 Tracking & Analytics\r\n\r\nThe wizard emits custom events when `trackSteps={true}`:\r\n\r\n```tsx\r\n// Listen to step changes\r\nuseEffect(() => {\r\n  const handleStepChange = (e: CustomEvent) => {\r\n    const { step, stepIndex } = e.detail;\r\n    \r\n    // Google Analytics\r\n    gtag('event', 'wizard_step_change', {\r\n      step_name: step,\r\n      step_index: stepIndex,\r\n    });\r\n    \r\n    // Mixpanel\r\n    mixpanel.track('Wizard Step', {\r\n      step,\r\n      stepIndex,\r\n    });\r\n  };\r\n\r\n  window.addEventListener('wizard:step-change', handleStepChange as EventListener);\r\n  \r\n  return () => {\r\n    window.removeEventListener('wizard:step-change', handleStepChange as EventListener);\r\n  };\r\n}, []);\r\n\r\n// Enable tracking\r\n<Wizard trackSteps={true} {...props} />\r\n```\r\n\r\nOr use the callback:\r\n\r\n```tsx\r\n<Wizard\r\n  onStepChange={(stepId, data, index) => {\r\n    analytics.track('Wizard Step Changed', {\r\n      stepId,\r\n      stepIndex: index,\r\n      dataSnapshot: data,\r\n    });\r\n  }}\r\n/>\r\n```\r\n\r\n---\r\n\r\n## 🧪 Testing\r\n\r\n```tsx\r\nimport { render, screen, fireEvent } from '@testing-library/react';\r\nimport { Wizard } from '@amrviernes/react-wizard';\r\n\r\ntest('wizard navigates between steps', () => {\r\n  render(\r\n    <Wizard\r\n      stepMapping={stepMapping}\r\n      data-testid=\"checkout-wizard\"\r\n    />\r\n  );\r\n\r\n  // Check first step renders\r\n  expect(screen.getByTestId('checkout-wizard-step-personal')).toBeInTheDocument();\r\n\r\n  // Click next\r\n  fireEvent.click(screen.getByText('Next'));\r\n\r\n  // Check second step renders\r\n  expect(screen.getByTestId('checkout-wizard-step-contact')).toBeInTheDocument();\r\n});\r\n\r\ntest('wizard updates data', () => {\r\n  const onStepChange = jest.fn();\r\n  \r\n  render(\r\n    <Wizard\r\n      stepMapping={stepMapping}\r\n      onStepChange={onStepChange}\r\n    />\r\n  );\r\n\r\n  // Update input\r\n  const input = screen.getByPlaceholderText('Name');\r\n  fireEvent.change(input, { target: { value: 'John' } });\r\n\r\n  // Navigate\r\n  fireEvent.click(screen.getByText('Next'));\r\n\r\n  // Check callback was called with data\r\n  expect(onStepChange).toHaveBeenCalledWith(\r\n    'contact',\r\n    expect.objectContaining({ name: 'John' }),\r\n    1\r\n  );\r\n});\r\n```\r\n\r\n---\r\n\r\n## 🎨 Styling Examples\r\n\r\n### With Tailwind CSS\r\n\r\n```tsx\r\nconst PersonalStep: React.FC<WizardStepProps<FormData, StepId>> = ({\r\n  data,\r\n  updateData,\r\n  nextStep,\r\n  stepIndex,\r\n  totalSteps,\r\n}) => (\r\n  <div className=\"max-w-2xl mx-auto p-6\">\r\n    <div className=\"mb-4 text-sm text-gray-600\">\r\n      Step {stepIndex + 1} of {totalSteps}\r\n    </div>\r\n    \r\n    <h2 className=\"text-2xl font-bold mb-6\">Personal Information</h2>\r\n    \r\n    <input\r\n      className=\"w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500\"\r\n      value={data.name || ''}\r\n      onChange={(e) => updateData({ name: e.target.value })}\r\n      placeholder=\"Full Name\"\r\n    />\r\n    \r\n    <button\r\n      onClick={nextStep}\r\n      className=\"mt-6 px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700\"\r\n    >\r\n      Continue\r\n    </button>\r\n  </div>\r\n);\r\n```\r\n\r\n### With CSS Modules\r\n\r\n```tsx\r\nimport styles from './Wizard.module.css';\r\n\r\nconst PersonalStep: React.FC<WizardStepProps<FormData, StepId>> = (props) => (\r\n  <div className={styles.stepContainer}>\r\n    <div className={styles.progress}>\r\n      Step {props.stepIndex + 1} of {props.totalSteps}\r\n    </div>\r\n    {/* ... */}\r\n  </div>\r\n);\r\n```\r\n\r\n---\r\n\r\n## 🔧 API Reference\r\n\r\n### `<Wizard>` Component Props\r\n\r\n| Prop | Type | Required | Default | Description |\r\n|------|------|----------|---------|-------------|\r\n| `stepMapping` | `Record<S, React.FC<WizardStepProps<T, S>>>` | ✅ Yes | - | Map of step IDs to step components |\r\n| `initialData` | `Partial<T>` | ❌ No | `{}` | Initial data for the wizard |\r\n| `onStepChange` | `(stepId: S, data: Partial<T>, stepIndex: number) => void` | ❌ No | - | Callback fired when step changes |\r\n| `trackSteps` | `boolean` | ❌ No | `false` | Enable step tracking via custom events |\r\n| `data-testid` | `string` | ❌ No | `'wizard'` | Test ID for testing purposes |\r\n\r\n### `useWizardContext<T, S>()` Hook\r\n\r\nReturns an object with:\r\n\r\n```typescript\r\n{\r\n  data: Partial<T>;           // Current wizard data\r\n  step: S;                    // Current step ID\r\n  stepIndex: number;          // Current step index (0-based)\r\n  totalSteps: number;         // Total number of steps\r\n  nextStep: () => void;       // Navigate to next step\r\n  prevStep: () => void;       // Navigate to previous step\r\n  toStep: (stepId: S) => void; // Navigate to specific step\r\n  updateData: (newData: Partial<T>) => void; // Update wizard data\r\n  resetData: (data?: Partial<T>) => void;    // Reset wizard\r\n  trackSteps?: boolean;       // Whether tracking is enabled\r\n}\r\n```\r\n\r\n---\r\n\r\n## 💡 Best Practices\r\n\r\n### 1. **Define Clear Types**\r\n```tsx\r\n// ✅ Good\r\ninterface UserData {\r\n  email: string;\r\n  password: string;\r\n  profile: {\r\n    firstName: string;\r\n    lastName: string;\r\n  };\r\n}\r\ntype Steps = 'account' | 'profile' | 'review';\r\n\r\n// ❌ Avoid\r\nconst data: any = {};\r\n```\r\n\r\n### 2. **Validate Before Navigation**\r\n```tsx\r\nconst handleNext = () => {\r\n  if (isValid(data)) {\r\n    nextStep();\r\n  } else {\r\n    showErrors();\r\n  }\r\n};\r\n```\r\n\r\n### 3. **Persist Data**\r\n```tsx\r\nconst handleStepChange = (stepId, data, index) => {\r\n  // Save to localStorage\r\n  localStorage.setItem('wizardData', JSON.stringify(data));\r\n  \r\n  // Or save to backend\r\n  await saveProgress(data, stepId);\r\n};\r\n```\r\n\r\n### 4. **Use Semantic Step IDs**\r\n```tsx\r\n// ✅ Good\r\ntype StepId = 'personal-info' | 'payment-method' | 'review-order';\r\n\r\n// ❌ Avoid\r\ntype StepId = 'step1' | 'step2' | 'step3';\r\n```\r\n\r\n### 5. **Handle Loading States**\r\n```tsx\r\nconst [isSubmitting, setIsSubmitting] = useState(false);\r\n\r\nconst handleNext = async () => {\r\n  setIsSubmitting(true);\r\n  await saveData(data);\r\n  setIsSubmitting(false);\r\n  nextStep();\r\n};\r\n```\r\n\r\n---\r\n\r\n## 🤔 FAQ\r\n\r\n**Q: Can I use this with React 16?**  \r\nA: Yes! It works with React 16.8+ (requires Hooks support).\r\n\r\n**Q: Does it work with TypeScript?**  \r\nA: Absolutely! It's written in TypeScript with full type definitions.\r\n\r\n**Q: Can I style the wizard?**  \r\nA: Yes! The wizard is completely unstyled. Bring your own CSS, Tailwind, styled-components, etc.\r\n\r\n**Q: Does it support server-side rendering?**  \r\nA: Yes! It works perfectly with Next.js, Remix, and other SSR frameworks.\r\n\r\n**Q: Can I save progress and resume later?**  \r\nA: Yes! Use the `onStepChange` callback to save data to localStorage, database, etc.\r\n\r\n**Q: How do I handle async operations?**  \r\nA: Make your step component handlers async and use loading states.\r\n\r\n**Q: Can I skip steps conditionally?**  \r\nA: Yes! Use the `toStep()` function to jump to any step based on your logic.\r\n\r\n---\r\n\r\n## 📄 License\r\n\r\nMIT © [Amr El-Desoky]\r\n\r\n---\r\n\r\n## 🤝 Contributing\r\n\r\nContributions are welcome! Please feel free to submit a Pull Request.\r\n\r\n1. Fork the repository\r\n2. Create your feature branch (`git checkout -b feature/AmazingFeature`)\r\n3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)\r\n4. Push to the branch (`git push origin feature/AmazingFeature`)\r\n5. Open a Pull Request\r\n\r\n---\r\n\r\n## 🐛 Issues\r\n\r\nFound a bug? Please [open an issue](https://github.com/amrviernes/react-wizard/issues) with:\r\n- Description of the bug\r\n- Steps to reproduce\r\n- Expected behavior\r\n- Screenshots (if applicable)\r\n\r\n---\r\n\r\n## 💖 Support\r\n\r\nIf you find this package helpful, please:\r\n- ⭐ Star the repository\r\n- 🐦 Share on Twitter\r\n- 📝 Write a blog post about it\r\n\r\n---\r\n\r\n## 📞 Get in Touch\r\n\r\n- GitHub: [@AmrViernes](https://github.com/AmrViernes)\r\n- Email: amraromoro@hotmail.com\r\n\r\n---\r\n\r\n**Built with ❤️ for the React community**","readmeFilename":"readme.md","_rev":"1-1ec0a6f5ae59a236f9c29ad78d784914"}