- Bulk replace text-white → text-foreground across 116 component files (preserving text-white/ opacity variants) - Remove hover-glow-cyan, shadow-card-glow-cyan, shadow-button-primary-glow classes from all components - Replace --duration-normal/--duration-immersive/--duration-slow with --sumi-duration-normal/--sumi-duration-slow across 130+ files - Replace --ease-out/--ease-in-out with --sumi-ease-out/--sumi-ease-in-out - Replace focus:ring-blue-500 → focus:ring-primary (4 files) - Remove hover:scale-105/110 and hover:-translate-y-1/0.5 transforms (SUMI anti-pattern: no scale on hover) - Clean up stale kodo- references in comments Co-authored-by: Cursor <cursoragent@cursor.com>
179 lines
4.3 KiB
TypeScript
179 lines
4.3 KiB
TypeScript
import * as React from 'react';
|
|
import { Dialog } from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button';
|
|
import { cn } from '@/lib/utils';
|
|
import { CheckCircle, ChevronRight, ChevronLeft } from 'lucide-react';
|
|
|
|
/**
|
|
* OnboardingStep - Single step in the onboarding flow
|
|
*/
|
|
export interface OnboardingStep {
|
|
title: string;
|
|
description: string;
|
|
content?: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* OnboardingProps - Properties for the Onboarding component
|
|
*/
|
|
export interface OnboardingProps {
|
|
/**
|
|
* Whether the onboarding is open
|
|
*/
|
|
open: boolean;
|
|
|
|
/**
|
|
* Callback when onboarding is closed
|
|
*/
|
|
onClose: () => void;
|
|
|
|
/**
|
|
* Steps to display in the onboarding flow
|
|
*/
|
|
steps: OnboardingStep[];
|
|
|
|
/**
|
|
* Optional callback when onboarding is completed
|
|
*/
|
|
onComplete?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Onboarding - Multi-step onboarding flow component
|
|
*
|
|
* Guides new users through key features of the application.
|
|
* Displays steps sequentially with navigation controls.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Onboarding
|
|
* open={showOnboarding}
|
|
* onClose={() => setShowOnboarding(false)}
|
|
* steps={[
|
|
* { title: 'Welcome', description: 'Welcome to Veza!' },
|
|
* { title: 'Library', description: 'Manage your tracks here' },
|
|
* ]}
|
|
* onComplete={() => console.log('Onboarding complete')}
|
|
* />
|
|
* ```
|
|
*/
|
|
export function Onboarding({
|
|
open,
|
|
onClose,
|
|
steps,
|
|
onComplete,
|
|
}: OnboardingProps) {
|
|
const [currentStep, setCurrentStep] = React.useState(0);
|
|
|
|
const handleNext = () => {
|
|
if (currentStep < steps.length - 1) {
|
|
setCurrentStep(currentStep + 1);
|
|
} else {
|
|
handleComplete();
|
|
}
|
|
};
|
|
|
|
const handlePrevious = () => {
|
|
if (currentStep > 0) {
|
|
setCurrentStep(currentStep - 1);
|
|
}
|
|
};
|
|
|
|
const handleSkip = () => {
|
|
handleComplete();
|
|
};
|
|
|
|
const handleComplete = () => {
|
|
onComplete?.();
|
|
onClose();
|
|
// Reset to first step for next time
|
|
setCurrentStep(0);
|
|
};
|
|
|
|
if (!open || steps.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const step = steps[currentStep];
|
|
const isFirstStep = currentStep === 0;
|
|
const isLastStep = currentStep === steps.length - 1;
|
|
|
|
return (
|
|
<Dialog
|
|
open={open}
|
|
onClose={handleComplete}
|
|
title="Bienvenue sur Veza"
|
|
size="lg"
|
|
variant="info"
|
|
showCancel={false}
|
|
footer={
|
|
<div className="flex items-center justify-between w-full">
|
|
{/* Progress indicator */}
|
|
<div className="flex items-center gap-2">
|
|
{steps.map((_, index) => (
|
|
<div
|
|
key={index}
|
|
className={cn(
|
|
'h-2 w-2 rounded-full transition-colors',
|
|
index === currentStep
|
|
? 'bg-primary w-8'
|
|
: index < currentStep
|
|
? 'bg-primary/50'
|
|
: 'bg-muted',
|
|
)}
|
|
/>
|
|
))}
|
|
</div>
|
|
|
|
{/* Navigation buttons */}
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="ghost" onClick={handleSkip} size="sm">
|
|
Passer
|
|
</Button>
|
|
{!isFirstStep && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={handlePrevious}
|
|
size="sm"
|
|
>
|
|
<ChevronLeft className="w-4 h-4 mr-1" />
|
|
Précédent
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="default"
|
|
onClick={handleNext}
|
|
size="sm"
|
|
>
|
|
{isLastStep ? (
|
|
<>
|
|
Terminer
|
|
<CheckCircle className="w-4 h-4 ml-1" />
|
|
</>
|
|
) : (
|
|
<>
|
|
Suivant
|
|
<ChevronRight className="w-4 h-4 ml-1" />
|
|
</>
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<h3 className="text-xl font-semibold text-foreground mb-2">
|
|
{step.title}
|
|
</h3>
|
|
<p className="text-muted-foreground text-sm leading-relaxed">
|
|
{step.description}
|
|
</p>
|
|
</div>
|
|
{step.content && (
|
|
<div className="mt-6">{step.content}</div>
|
|
)}
|
|
</div>
|
|
</Dialog>
|
|
);
|
|
}
|