import * as React from 'react'; import { Circle } from 'lucide-react'; import { cn } from '@/lib/utils'; /** * RadioGroupProps - Propriétés du composant RadioGroup * * @interface RadioGroupProps * @extends Omit, 'onChange'> */ export interface RadioGroupProps extends Omit, 'onChange'> { /** * Valeur sélectionnée du groupe de boutons radio * * @example * ```tsx * * * * * ``` */ value?: string; /** * Fonction appelée lorsque la valeur sélectionnée change * * @param {string} value - Nouvelle valeur sélectionnée * * @example * ```tsx * console.log('Selected:', value)}> * ... * * ``` */ onValueChange?: (value: string) => void; /** * Si `true`, désactive tous les boutons radio du groupe * * @default false */ disabled?: boolean; } /** * RadioGroup - Composant de groupe de boutons radio avec design system Kodo * * Composant pour gérer un groupe de boutons radio mutuellement exclusifs. * Utilise le design system Kodo avec des styles cohérents et support pour l'accessibilité. * * @example * ```tsx * // Groupe de boutons radio simple * * * * * * ``` * * @example * ```tsx * // Avec labels * * * * * ``` * * @component * @param {RadioGroupProps} props - Propriétés du composant * @returns {JSX.Element} Élément div avec role="radiogroup" contenant les boutons radio */ const RadioGroup = React.forwardRef( ({ className, value, onValueChange, disabled, children, ...props }, ref) => { // Collect RadioGroupItem values for arrow key navigation const itemValues: string[] = []; React.Children.forEach(children, (child) => { if (React.isValidElement(child) && child.type === RadioGroupItem && !child.props.disabled) { itemValues.push(child.props.value); } }); const handleKeyDown = (e: React.KeyboardEvent) => { if (itemValues.length === 0) return; const currentIndex = value ? itemValues.indexOf(value) : -1; let nextIndex: number | undefined; switch (e.key) { case 'ArrowDown': case 'ArrowRight': nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % itemValues.length; break; case 'ArrowUp': case 'ArrowLeft': nextIndex = currentIndex === -1 ? itemValues.length - 1 : (currentIndex - 1 + itemValues.length) % itemValues.length; break; default: return; } e.preventDefault(); const nextValue = itemValues[nextIndex]; if (nextValue !== undefined) onValueChange?.(nextValue); // Focus the newly selected radio const radios = (e.currentTarget as HTMLElement).querySelectorAll('input[type="radio"]'); radios[nextIndex]?.focus(); }; return (
{React.Children.map(children, (child) => { if (React.isValidElement(child) && child.type === RadioGroupItem) { const isChecked = child.props.value === value; const isFirstItem = child.props.value === itemValues[0]; return React.cloneElement(child, { checked: isChecked, onCheckedChange: () => onValueChange?.(child.props.value), disabled: disabled || child.props.disabled, // Only the checked item (or first if none checked) gets tabIndex 0 tabIndex: isChecked ? 0 : (value === undefined && isFirstItem ? 0 : -1), } as any); } return child; })}
); }, ); RadioGroup.displayName = 'RadioGroup'; /** * RadioGroupItemProps - Propriétés du composant RadioGroupItem * * @interface RadioGroupItemProps * @extends Omit, 'type'> */ export interface RadioGroupItemProps extends Omit, 'type'> { /** * Valeur unique du bouton radio (doit être unique dans le groupe) * * @example * ```tsx * * ``` */ value: string; /** * État checked du bouton radio (géré automatiquement par RadioGroup) * @internal */ checked?: boolean; /** * Fonction appelée lors du clic (gérée automatiquement par RadioGroup) * @internal */ onCheckedChange?: () => void; /** * Tab index for keyboard navigation (managed by RadioGroup) * @internal */ tabIndex?: number; } /** * RadioGroupItem - Bouton radio individuel * * Bouton radio à utiliser à l'intérieur d'un RadioGroup. * L'état checked est géré automatiquement par le RadioGroup parent. * * @component */ const RadioGroupItem = React.forwardRef( ({ className, value, checked, onCheckedChange, disabled, tabIndex, ...props }, ref) => { return ( ); }, ); RadioGroupItem.displayName = 'RadioGroupItem'; export { RadioGroup, RadioGroupItem };