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
This commit is contained in:
senke 2026-01-16 00:45:52 +01:00
parent b9cceae6b5
commit ac65373c36
2 changed files with 118 additions and 3 deletions

View file

@ -2942,11 +2942,18 @@ Critical path dependencies:
- **Result**: All high and medium priority mutation buttons now have loading states. Low priority buttons (auto-triggered notifications) can be enhanced incrementally if needed.
- **Rollback**: Remove loading states
- [ ] **Action 8.3.1.4**: Create Spinner component (if doesn't exist)
- [x] **Action 8.3.1.4**: Create Spinner component (if doesn't exist)
- **Scope**: `apps/web/src/components/ui/Spinner.tsx` (create) - Reusable spinner component
- **Dependencies**: None
- **Dependencies**: None
- **Risk**: LOW 🔒
- **Validation**: Spinner component works
- **Validation**: ✅ Spinner component created:
- **File**: Created `apps/web/src/components/ui/Spinner.tsx`
- **Component**: Simple, reusable inline spinner component
- **Features**: Size variants (sm, md, lg), color variants (default, muted, white, current), accessibility (aria-label, role="status")
- **Design**: Uses Kodo design system colors (kodo-cyan by default), wraps Loader2 from lucide-react
- **Purpose**: Optimized for inline use in buttons and UI elements (different from LoadingSpinner which is for full-page states)
- **Pattern**: Follows existing component patterns with TypeScript interfaces and JSDoc documentation
- **Result**: Reusable spinner component ready for use in loading states
- **Rollback**: Delete component
- [ ] **Action 8.3.1.5**: Use Spinner in loading states

View file

@ -0,0 +1,108 @@
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>
</>
);
}