- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid - Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px) - Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation - Modified files across all components to ensure consistent 8px grid alignment - Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
import * as React from 'react';
|
|
import { useId } from 'react';
|
|
import { Check } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* CheckboxProps - Propriétés du composant Checkbox
|
|
*
|
|
* @interface CheckboxProps
|
|
* @extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>
|
|
*/
|
|
export interface CheckboxProps
|
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
|
/**
|
|
* Label à afficher à côté de la checkbox
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Checkbox label="J'accepte les conditions" />
|
|
* ```
|
|
*/
|
|
label?: string;
|
|
|
|
/**
|
|
* Fonction appelée lorsque l'état checked change
|
|
* Reçoit la nouvelle valeur booléenne
|
|
*
|
|
* @param {boolean} checked - Nouvel état checked
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* <Checkbox onCheckedChange={(checked) => console.log(checked)} />
|
|
* ```
|
|
*/
|
|
onCheckedChange?: (checked: boolean) => void;
|
|
}
|
|
|
|
/**
|
|
* Checkbox - Composant de case à cocher avec design system Kodo
|
|
*
|
|
* Composant de checkbox avec support pour les labels et les callbacks.
|
|
* Utilise le design system Kodo avec une icône Check animée lors de la sélection.
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* // Checkbox simple
|
|
* <Checkbox label="Option 1" />
|
|
*
|
|
* // Checkbox contrôlée
|
|
* <Checkbox
|
|
* checked={isChecked}
|
|
* onCheckedChange={setIsChecked}
|
|
* label="Accepter"
|
|
* />
|
|
*
|
|
* // Checkbox désactivée
|
|
* <Checkbox label="Option" disabled />
|
|
* ```
|
|
*
|
|
* @component
|
|
* @param {CheckboxProps} props - Propriétés du composant
|
|
* @returns {JSX.Element} Élément label contenant une checkbox stylisée
|
|
*/
|
|
|
|
export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
|
|
({ label, className = '', onCheckedChange, id, ...props }, ref) => {
|
|
// CRITIQUE FIX #37: Utiliser useId() pour générer un ID stable pour l'association label/input
|
|
const generatedId = useId();
|
|
const checkboxId = id || generatedId;
|
|
const labelId = `${checkboxId}-label`;
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (onCheckedChange) {
|
|
onCheckedChange(e.target.checked);
|
|
}
|
|
};
|
|
|
|
// CRITIQUE FIX #37: Si aucun label n'est fourni, s'assurer qu'il y a un aria-label
|
|
// const hasAccessibleLabel = label || props['aria-label'] || props['aria-labelledby'];
|
|
|
|
return (
|
|
<label
|
|
htmlFor={checkboxId}
|
|
id={labelId}
|
|
className={cn(
|
|
`inline-flex items-center gap-4 cursor-pointer group`,
|
|
props.disabled ? 'opacity-50 cursor-not-allowed' : '',
|
|
className,
|
|
)}
|
|
>
|
|
<div className="relative">
|
|
<input
|
|
ref={ref}
|
|
id={checkboxId}
|
|
type="checkbox"
|
|
className="peer sr-only"
|
|
onChange={handleChange}
|
|
// CRITIQUE FIX #37: Ajouter aria-label si aucun label n'est fourni
|
|
aria-label={
|
|
!label && !props['aria-label'] && !props['aria-labelledby']
|
|
? 'Checkbox'
|
|
: undefined
|
|
}
|
|
aria-labelledby={label ? labelId : undefined}
|
|
{...props}
|
|
/>
|
|
<div
|
|
className="
|
|
w-5 h-5 rounded border border-kodo-steel bg-kodo-graphite
|
|
peer-checked:bg-kodo-cyan peer-checked:border-kodo-steel
|
|
peer-focus:ring-2 peer-focus:ring-kodo-steel/30
|
|
transition-all duration-200
|
|
"
|
|
></div>
|
|
<Check
|
|
className="w-3.5 h-3.5 text-black absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 opacity-0 peer-checked:opacity-100 transition-opacity pointer-events-none"
|
|
strokeWidth={3}
|
|
/>
|
|
</div>
|
|
{label && (
|
|
<span className="text-sm text-kodo-text-main group-hover:text-white transition-colors select-none">
|
|
{label}
|
|
</span>
|
|
)}
|
|
</label>
|
|
);
|
|
},
|
|
);
|
|
Checkbox.displayName = 'Checkbox';
|