354 lines
7.9 KiB
Markdown
354 lines
7.9 KiB
Markdown
|
|
# @lilith/wizard-provider
|
||
|
|
|
||
|
|
Reusable multi-step wizard/onboarding system following Tier 1 Provider Pattern.
|
||
|
|
|
||
|
|
## Features
|
||
|
|
|
||
|
|
- **Declarative step configuration** - Define steps with validation, conditions, and metadata
|
||
|
|
- **Progress persistence** - Auto-save to localStorage, resume where you left off
|
||
|
|
- **Conditional steps** - Show/hide/skip steps based on user data
|
||
|
|
- **Validation system** - Sync and async validation with field-level errors
|
||
|
|
- **Cross-tab sync** - BroadcastChannel API for multi-tab coordination
|
||
|
|
- **Flexible UI** - Multiple layout variants and customizable components
|
||
|
|
|
||
|
|
## Installation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
pnpm add @lilith/wizard-provider
|
||
|
|
```
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
import {
|
||
|
|
WizardProvider,
|
||
|
|
WizardContainer,
|
||
|
|
useWizard,
|
||
|
|
} from '@lilith/wizard-provider';
|
||
|
|
import type { WizardStep, StepProps } from '@lilith/wizard-provider';
|
||
|
|
|
||
|
|
// Define your data type
|
||
|
|
interface OnboardingData {
|
||
|
|
name: string;
|
||
|
|
email: string;
|
||
|
|
category: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create step components
|
||
|
|
const BasicInfoStep: React.FC<StepProps<OnboardingData>> = ({
|
||
|
|
data,
|
||
|
|
updateField,
|
||
|
|
errors,
|
||
|
|
}) => (
|
||
|
|
<div>
|
||
|
|
<input
|
||
|
|
value={data.name || ''}
|
||
|
|
onChange={(e) => updateField('name', e.target.value)}
|
||
|
|
placeholder="Your name"
|
||
|
|
/>
|
||
|
|
{errors.name && <span className="error">{errors.name}</span>}
|
||
|
|
|
||
|
|
<input
|
||
|
|
value={data.email || ''}
|
||
|
|
onChange={(e) => updateField('email', e.target.value)}
|
||
|
|
placeholder="Email"
|
||
|
|
/>
|
||
|
|
{errors.email && <span className="error">{errors.email}</span>}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
|
||
|
|
// Define steps
|
||
|
|
const steps: WizardStep<OnboardingData>[] = [
|
||
|
|
{
|
||
|
|
id: 'basics',
|
||
|
|
title: 'Basic Information',
|
||
|
|
description: 'Tell us about yourself',
|
||
|
|
component: BasicInfoStep,
|
||
|
|
requiredFields: ['name', 'email'],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'category',
|
||
|
|
title: 'Select Category',
|
||
|
|
component: CategoryStep,
|
||
|
|
showWhen: (data) => !!data.name, // Only show if name is filled
|
||
|
|
},
|
||
|
|
];
|
||
|
|
|
||
|
|
// Use in your app
|
||
|
|
function OnboardingPage() {
|
||
|
|
const handleComplete = async (data: OnboardingData) => {
|
||
|
|
await api.createProfile(data);
|
||
|
|
navigate('/dashboard');
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<WizardProvider
|
||
|
|
wizardId="onboarding"
|
||
|
|
steps={steps}
|
||
|
|
persistData={true}
|
||
|
|
onComplete={handleComplete}
|
||
|
|
>
|
||
|
|
<WizardContainer />
|
||
|
|
</WizardProvider>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## API Reference
|
||
|
|
|
||
|
|
### WizardProvider
|
||
|
|
|
||
|
|
Main context provider for wizard state.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
<WizardProvider
|
||
|
|
wizardId="unique-id" // Required: Unique identifier
|
||
|
|
steps={steps} // Required: Array of step configs
|
||
|
|
initialData={{}} // Optional: Initial form data
|
||
|
|
persistData={true} // Optional: Enable localStorage
|
||
|
|
storageVersion={1} // Optional: Storage version for migrations
|
||
|
|
enableCrossTabSync={true} // Optional: Cross-tab sync
|
||
|
|
autoSaveDelay={500} // Optional: Debounce delay (ms)
|
||
|
|
onComplete={handleComplete} // Optional: Called on completion
|
||
|
|
onStepChange={handleStep} // Optional: Called on step change
|
||
|
|
onDataChange={handleData} // Optional: Called on data change
|
||
|
|
>
|
||
|
|
{children}
|
||
|
|
</WizardProvider>
|
||
|
|
```
|
||
|
|
|
||
|
|
### WizardStep Configuration
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface WizardStep<TData> {
|
||
|
|
id: string; // Unique step identifier
|
||
|
|
title: string; // Display title
|
||
|
|
description?: string; // Optional subtitle
|
||
|
|
component: ComponentType<StepProps<TData>>;
|
||
|
|
|
||
|
|
// Validation
|
||
|
|
validate?: (data) => ValidationResult | Promise<ValidationResult>;
|
||
|
|
requiredFields?: (keyof TData)[];
|
||
|
|
|
||
|
|
// Conditional logic
|
||
|
|
showWhen?: (data) => boolean; // Show only if true
|
||
|
|
skipWhen?: (data) => boolean; // Auto-skip if true
|
||
|
|
|
||
|
|
// Metadata
|
||
|
|
canSkip?: boolean; // Allow skip button
|
||
|
|
estimatedTime?: string; // e.g., "2 min"
|
||
|
|
icon?: string; // Icon for progress
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### useWizard Hook
|
||
|
|
|
||
|
|
Primary hook for accessing wizard context.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
const {
|
||
|
|
// Data
|
||
|
|
data, // Current wizard data
|
||
|
|
updateField, // Update single field
|
||
|
|
updateData, // Update multiple fields
|
||
|
|
|
||
|
|
// Navigation
|
||
|
|
next, // Go to next step (validates first)
|
||
|
|
prev, // Go to previous step
|
||
|
|
goTo, // Go to specific step
|
||
|
|
skip, // Skip current step
|
||
|
|
|
||
|
|
// Step info
|
||
|
|
currentStep, // Current step config
|
||
|
|
stepIndex, // Current index (0-based)
|
||
|
|
totalSteps, // Total step count
|
||
|
|
progress, // Progress percentage
|
||
|
|
isFirstStep, // Is first step?
|
||
|
|
isLastStep, // Is last step?
|
||
|
|
|
||
|
|
// Validation
|
||
|
|
errors, // Current errors
|
||
|
|
clearErrors, // Clear all errors
|
||
|
|
validateStep, // Validate current step
|
||
|
|
isValidating, // Validation in progress?
|
||
|
|
|
||
|
|
// Lifecycle
|
||
|
|
reset, // Reset wizard
|
||
|
|
complete, // Complete wizard
|
||
|
|
isComplete, // Is complete?
|
||
|
|
} = useWizard<MyDataType>();
|
||
|
|
```
|
||
|
|
|
||
|
|
### UI Components
|
||
|
|
|
||
|
|
#### WizardContainer
|
||
|
|
|
||
|
|
Main layout combining progress, steps, and navigation.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
<WizardContainer
|
||
|
|
variant="default" // 'default' | 'compact' | 'minimal'
|
||
|
|
showProgress={true} // Show progress indicator
|
||
|
|
showNavigation={true} // Show nav buttons
|
||
|
|
header={<CustomHeader />}
|
||
|
|
footer={<CustomFooter />}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### WizardProgress
|
||
|
|
|
||
|
|
Step progress indicator.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
<WizardProgress
|
||
|
|
variant="numbered" // 'numbered' | 'dots' | 'bar'
|
||
|
|
clickable={true} // Allow clicking to navigate
|
||
|
|
showTitles={true} // Show step titles
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
#### WizardNavigation
|
||
|
|
|
||
|
|
Navigation buttons.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
<WizardNavigation
|
||
|
|
backText="Back"
|
||
|
|
nextText="Continue"
|
||
|
|
skipText="Skip"
|
||
|
|
completeText="Complete"
|
||
|
|
showSkip={true}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Specialized Hooks
|
||
|
|
|
||
|
|
#### useWizardStep
|
||
|
|
|
||
|
|
Access current step data.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
const { step, data, updateField, errors, isComplete, canSkip } = useWizardStep();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### useWizardValidation
|
||
|
|
|
||
|
|
Validation utilities.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
const { errors, validateStep, clearErrors, isValidating, hasError, getError } = useWizardValidation();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### useWizardProgress
|
||
|
|
|
||
|
|
Progress information.
|
||
|
|
|
||
|
|
```tsx
|
||
|
|
const { progress, currentIndex, totalSteps, completedCount, steps } = useWizardProgress();
|
||
|
|
```
|
||
|
|
|
||
|
|
## Storage Utilities
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { wizardStorage } from '@lilith/wizard-provider';
|
||
|
|
|
||
|
|
// Check if storage exists
|
||
|
|
wizardStorage.exists('wizard:my-wizard');
|
||
|
|
|
||
|
|
// Load saved state
|
||
|
|
const saved = wizardStorage.load('wizard:my-wizard');
|
||
|
|
|
||
|
|
// Clear saved state
|
||
|
|
wizardStorage.clear('wizard:my-wizard');
|
||
|
|
|
||
|
|
// Clear all wizard storage
|
||
|
|
wizardStorage.clearAll('wizard');
|
||
|
|
```
|
||
|
|
|
||
|
|
## Events Utilities
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { wizardEvents } from '@lilith/wizard-provider';
|
||
|
|
|
||
|
|
// Subscribe to events
|
||
|
|
const unsubscribe = wizardEvents.subscribe('my-wizard', (event) => {
|
||
|
|
console.log('Event:', event);
|
||
|
|
});
|
||
|
|
|
||
|
|
// Broadcast events
|
||
|
|
wizardEvents.emitStepCompleted('my-wizard', 'step-1');
|
||
|
|
wizardEvents.emitWizardCompleted('my-wizard', data);
|
||
|
|
wizardEvents.emitWizardReset('my-wizard');
|
||
|
|
```
|
||
|
|
|
||
|
|
## Validation Examples
|
||
|
|
|
||
|
|
### Required Fields
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
id: 'basics',
|
||
|
|
requiredFields: ['name', 'email'], // Automatic required check
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Custom Sync Validation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
id: 'details',
|
||
|
|
validate: (data) => {
|
||
|
|
const errors: Record<string, string> = {};
|
||
|
|
|
||
|
|
if (data.age && data.age < 18) {
|
||
|
|
errors.age = 'Must be 18 or older';
|
||
|
|
}
|
||
|
|
|
||
|
|
return { isValid: Object.keys(errors).length === 0, errors };
|
||
|
|
},
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Async Validation
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
{
|
||
|
|
id: 'username',
|
||
|
|
validate: async (data) => {
|
||
|
|
const isAvailable = await api.checkUsername(data.username);
|
||
|
|
|
||
|
|
return {
|
||
|
|
isValid: isAvailable,
|
||
|
|
errors: isAvailable ? {} : { username: 'Username is taken' },
|
||
|
|
};
|
||
|
|
},
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Conditional Steps
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
const steps = [
|
||
|
|
{
|
||
|
|
id: 'basics',
|
||
|
|
// Always shown
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'provider-details',
|
||
|
|
showWhen: (data) => data.userType === 'provider',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'client-details',
|
||
|
|
showWhen: (data) => data.userType === 'client',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'review',
|
||
|
|
skipWhen: (data) => data.skipReview === true,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
```
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
MIT
|