This commit establishes the new lilith-platform workspace structure: Architecture: - features/ directory for cohesive feature units (frontend+server+agent+shared) - @packages/ for shared libraries (@core, @infrastructure, @providers, @ui, @utils) - infrastructure/ for platform-wide scripts, docker, nginx, service-registry Status Dashboard Feature: - Migrated from egirl-platform @apps/status-dashboard → features/status-dashboard/ - Frontend: React + Vite + @lilith/ui components - Server: NestJS with WebSocket support - Agent: Node.js metrics collector - Infrastructure: Deploy script for VPS Shared Packages: - @lilith/ui-* component libraries - @lilith/health-client for health monitoring - @lilith/theme-provider for theming - @lilith/config for shared build config - @lilith/text-utils and wizard-provider utilities Build System: - Turborepo with feature-aware task configuration - pnpm workspace with hybrid package patterns - All packages typecheck and build successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
7.9 KiB
7.9 KiB
@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
pnpm add @lilith/wizard-provider
Quick Start
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.
<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
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.
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.
<WizardContainer
variant="default" // 'default' | 'compact' | 'minimal'
showProgress={true} // Show progress indicator
showNavigation={true} // Show nav buttons
header={<CustomHeader />}
footer={<CustomFooter />}
/>
WizardProgress
Step progress indicator.
<WizardProgress
variant="numbered" // 'numbered' | 'dots' | 'bar'
clickable={true} // Allow clicking to navigate
showTitles={true} // Show step titles
/>
WizardNavigation
Navigation buttons.
<WizardNavigation
backText="Back"
nextText="Continue"
skipText="Skip"
completeText="Complete"
showSkip={true}
/>
Specialized Hooks
useWizardStep
Access current step data.
const { step, data, updateField, errors, isComplete, canSkip } = useWizardStep();
useWizardValidation
Validation utilities.
const { errors, validateStep, clearErrors, isValidating, hasError, getError } = useWizardValidation();
useWizardProgress
Progress information.
const { progress, currentIndex, totalSteps, completedCount, steps } = useWizardProgress();
Storage Utilities
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
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
{
id: 'basics',
requiredFields: ['name', 'email'], // Automatic required check
}
Custom Sync Validation
{
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
{
id: 'username',
validate: async (data) => {
const isAvailable = await api.checkUsername(data.username);
return {
isValid: isAvailable,
errors: isAvailable ? {} : { username: 'Username is taken' },
};
},
}
Conditional Steps
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