142 lines
3 KiB
TypeScript
142 lines
3 KiB
TypeScript
import { Loader2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* LoadingSpinnerProps - Propriétés du composant LoadingSpinner
|
|
*
|
|
* @interface LoadingSpinnerProps
|
|
*/
|
|
interface LoadingSpinnerProps {
|
|
/**
|
|
* Taille du spinner
|
|
*
|
|
* - `sm`: Petit (h-4 w-4 en inline, h-4 w-4 en block)
|
|
* - `md`: Moyen (h-5 w-5 en inline, h-8 w-8 en block) - par défaut
|
|
* - `lg`: Grand (h-6 w-6 en inline, h-12 w-12 en block)
|
|
*
|
|
* @default 'md'
|
|
*/
|
|
size?: 'sm' | 'md' | 'lg';
|
|
|
|
/**
|
|
* Classes CSS personnalisées
|
|
*/
|
|
className?: string;
|
|
|
|
/**
|
|
* Texte à afficher sous le spinner (ignoré en mode inline)
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <LoadingSpinner text="Chargement en cours..." />
|
|
* ```
|
|
*/
|
|
text?: string;
|
|
|
|
/**
|
|
* Mode inline pour boutons et éléments compacts.
|
|
* Rend uniquement l'icône sans conteneur.
|
|
*/
|
|
inline?: boolean;
|
|
|
|
/**
|
|
* Variant visuel (uniquement en mode inline)
|
|
*/
|
|
variant?: 'default' | 'muted' | 'white' | 'current';
|
|
|
|
/**
|
|
* Label d'accessibilité
|
|
*/
|
|
'aria-label'?: string;
|
|
}
|
|
|
|
/**
|
|
* LoadingSpinner - Composant de spinner de chargement
|
|
*
|
|
* Composant de spinner animé pour indiquer un état de chargement.
|
|
* Inclut un texte optionnel et supporte plusieurs tailles.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Spinner simple
|
|
* <LoadingSpinner />
|
|
*
|
|
* // Spinner avec texte
|
|
* <LoadingSpinner text="Chargement..." />
|
|
*
|
|
* // Spinner de grande taille
|
|
* <LoadingSpinner size="lg" text="Traitement en cours" />
|
|
* ```
|
|
*
|
|
* @component
|
|
* @param {LoadingSpinnerProps} props - Propriétés du composant
|
|
* @returns {JSX.Element} Spinner animé avec texte optionnel
|
|
*/
|
|
|
|
const variantClasses = {
|
|
default: 'text-primary',
|
|
muted: 'text-muted-foreground',
|
|
white: 'text-foreground',
|
|
current: 'text-current',
|
|
} as const;
|
|
|
|
const blockSizeClasses = {
|
|
sm: 'h-4 w-4',
|
|
md: 'h-8 w-8',
|
|
lg: 'h-12 w-12',
|
|
};
|
|
|
|
const inlineSizeClasses = {
|
|
sm: 'h-4 w-4',
|
|
md: 'h-5 w-5',
|
|
lg: 'h-6 w-6',
|
|
};
|
|
|
|
export function LoadingSpinner({
|
|
size = 'md',
|
|
className,
|
|
text,
|
|
inline = false,
|
|
variant = 'default',
|
|
'aria-label': ariaLabel = 'Chargement en cours',
|
|
}: LoadingSpinnerProps) {
|
|
if (inline) {
|
|
return (
|
|
<>
|
|
<Loader2
|
|
className={cn(
|
|
'animate-spin',
|
|
inlineSizeClasses[size],
|
|
variantClasses[variant],
|
|
className,
|
|
)}
|
|
aria-hidden="true"
|
|
/>
|
|
<span className="sr-only">{ariaLabel}</span>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex flex-col items-center justify-center min-h-48',
|
|
className,
|
|
)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
'animate-spin rounded-full border-2 border-muted border-t-primary',
|
|
blockSizeClasses[size],
|
|
)}
|
|
role="status"
|
|
aria-label={ariaLabel}
|
|
>
|
|
<span className="sr-only">Chargement...</span>
|
|
</div>
|
|
{text && (
|
|
<p className="mt-2 text-sm text-muted-foreground dark:text-muted-foreground">{text}</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|