veza/apps/web/src/components/ui/switch.tsx
senke 128a160196 feat(a11y): comprehensive accessibility & view states improvements
Sprint 1 — Quick A11y wins:
- progress.tsx: role=progressbar + aria-value* + aria-label
- switch.tsx: role=switch + aria-checked
- skeleton.tsx: aria-hidden=true
- alert.tsx, Toast.tsx, SelectTrigger.tsx: aria-labels on close buttons
- PostCard.tsx: alt on images + aria-labels on icon buttons
- ProductCard.tsx: aria-labels on play/view buttons
- modal.tsx: role=dialog + aria-modal + aria-labelledby
- input.tsx: error state + aria-invalid + aria-describedby
- FAB.tsx: forward aria-label from label prop

Sprint 2 — Structural A11y + View States:
- tabs/: full ARIA tablist/tab/tabpanel + arrow key navigation
- radio-group.tsx: role=radio + arrow key navigation
- select/: aria-activedescendant + full keyboard navigation
- List.tsx + card.tsx: focus-visible states on interactive elements
- DashboardPage, LibraryPage, LiveView, QueueView: error states
- WishlistView, AdminDashboard, AnalyticsView, SellerDashboard: loading/empty states

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:04:35 +01:00

102 lines
2.7 KiB
TypeScript

import * as React from 'react';
import { cn } from '@/lib/utils';
/**
* SwitchProps - Propriétés du composant Switch
*
* @interface SwitchProps
* @extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>
*/
export interface SwitchProps
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
/**
* État checked du switch
*
* @example
* ```tsx
* <Switch checked={isEnabled} onCheckedChange={setIsEnabled} />
* ```
*/
checked?: boolean;
/**
* Fonction appelée lorsque l'état checked change
*
* @param {boolean} checked - Nouvel état checked
*
* @example
* ```tsx
* <Switch onCheckedChange={(checked) => console.log('Switch:', checked)} />
* ```
*/
onCheckedChange?: (checked: boolean) => void;
}
/**
* Switch - Composant d'interrupteur avec design system Kodo
*
* Composant d'interrupteur (toggle) pour activer/désactiver une option.
* Utilise le design system Kodo avec une animation de transition fluide.
*
* @example
* ```tsx
* // Switch simple
* <Switch />
*
* // Switch contrôlé
* <Switch
* checked={isEnabled}
* onCheckedChange={setIsEnabled}
* />
*
* // Switch désactivé
* <Switch disabled />
* ```
*
* @component
* @param {SwitchProps} props - Propriétés du composant
* @returns {JSX.Element} Élément label contenant un switch stylisé
*/
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ className, checked, onCheckedChange, disabled, ...props }, ref) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (onCheckedChange) {
onCheckedChange(e.target.checked);
}
};
return (
<label
role="switch"
aria-checked={!!checked}
className={cn(
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors',
'focus-within:outline-none focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 focus-within:ring-offset-background',
'disabled:cursor-not-allowed disabled:opacity-50',
checked ? 'bg-primary' : 'bg-muted',
className,
)}
>
<input
ref={ref}
type="checkbox"
className="sr-only"
checked={checked}
onChange={handleChange}
disabled={disabled}
{...props}
/>
<span
className={cn(
'pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform',
checked ? 'translate-x-5' : 'translate-x-0',
)}
/>
</label>
);
},
);
Switch.displayName = 'Switch';
export { Switch };