veza/apps/web/src/components/ui/Spinner.tsx
senke ac65373c36 ui: create Spinner component for inline loading states (Action 8.3.1.4)
- Created Spinner.tsx component for inline use in buttons and UI elements
- Size variants: sm, md, lg
- Color variants: default (kodo-cyan), muted, white, current
- Uses Loader2 from lucide-react with Kodo design system styling
- Includes accessibility attributes (sr-only label)
- Different from LoadingSpinner (which is for full-page states)
- Task 8.3.1.4 complete
2026-01-16 00:46:12 +01:00

108 lines
2.5 KiB
TypeScript

import { Loader2 } from 'lucide-react';
import { cn } from '@/lib/utils';
/**
* SpinnerProps - Propriétés du composant Spinner
*
* @interface SpinnerProps
*/
export interface SpinnerProps {
/**
* Taille du spinner
*
* - `sm`: Petit (h-4 w-4) - pour les boutons et éléments compacts
* - `md`: Moyen (h-5 w-5) - par défaut
* - `lg`: Grand (h-6 w-6) - pour les éléments plus grands
*
* @default 'md'
*/
size?: 'sm' | 'md' | 'lg';
/**
* Couleur du spinner
*
* - `default`: Couleur par défaut (kodo-cyan pour le thème Kodo)
* - `muted`: Couleur atténuée (text-muted-foreground)
* - `white`: Blanc
* - `current`: Utilise la couleur du texte parent
*
* @default 'default'
*/
variant?: 'default' | 'muted' | 'white' | 'current';
/**
* Classes CSS personnalisées
*/
className?: string;
/**
* Label d'accessibilité
*
* @default 'Chargement en cours'
*/
'aria-label'?: string;
}
/**
* Spinner - Composant de spinner réutilisable pour les états de chargement inline
*
* Composant de spinner optimisé pour l'utilisation inline dans les boutons,
* formulaires et autres éléments UI. Utilise le design system Kodo.
*
* Différences avec LoadingSpinner:
* - LoadingSpinner: Pour les états de chargement de page complète avec conteneur
* - Spinner: Pour les spinners inline dans les boutons et éléments UI
*
* @example
* ```tsx
* // Spinner simple dans un bouton
* <Button disabled={isLoading}>
* {isLoading && <Spinner size="sm" className="mr-2" />}
* Enregistrer
* </Button>
*
* // Spinner avec couleur personnalisée
* <Spinner size="lg" variant="muted" />
*
* // Spinner avec classes personnalisées
* <Spinner className="text-kodo-cyan" />
* ```
*
* @component
* @param {SpinnerProps} props - Propriétés du composant
* @returns {JSX.Element} Spinner animé
*/
export function Spinner({
size = 'md',
variant = 'default',
className,
'aria-label': ariaLabel = 'Chargement en cours',
}: SpinnerProps) {
const sizeClasses = {
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-6 w-6',
};
const variantClasses = {
default: 'text-kodo-cyan',
muted: 'text-muted-foreground',
white: 'text-white',
current: 'text-current',
};
return (
<>
<Loader2
className={cn(
'animate-spin',
sizeClasses[size],
variantClasses[variant],
className,
)}
aria-hidden="true"
/>
<span className="sr-only">{ariaLabel}</span>
</>
);
}