2025-12-03 21:56:50 +00:00
|
|
|
import * as React from 'react';
|
2026-01-07 09:31:02 +00:00
|
|
|
import { useId } from 'react';
|
2025-12-03 21:56:50 +00:00
|
|
|
import { Check } from 'lucide-react';
|
|
|
|
|
import { cn } from '@/lib/utils';
|
|
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
/**
|
|
|
|
|
* CheckboxProps - Propriétés du composant Checkbox
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @interface CheckboxProps
|
|
|
|
|
* @extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'>
|
|
|
|
|
*/
|
2026-01-13 18:47:57 +00:00
|
|
|
export interface CheckboxProps
|
|
|
|
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
2026-01-07 09:31:02 +00:00
|
|
|
/**
|
|
|
|
|
* Label à afficher à côté de la checkbox
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @example
|
|
|
|
|
* ```tsx
|
|
|
|
|
* <Checkbox label="J'accepte les conditions" />
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
label?: string;
|
2026-01-07 18:39:21 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
/**
|
|
|
|
|
* Fonction appelée lorsque l'état checked change
|
|
|
|
|
* Reçoit la nouvelle valeur booléenne
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @param {boolean} checked - Nouvel état checked
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @example
|
|
|
|
|
* ```tsx
|
|
|
|
|
* <Checkbox onCheckedChange={(checked) => console.log(checked)} />
|
|
|
|
|
* ```
|
|
|
|
|
*/
|
|
|
|
|
onCheckedChange?: (checked: boolean) => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checkbox - Composant de case à cocher avec design system Kodo
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* 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.
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @example
|
|
|
|
|
* ```tsx
|
|
|
|
|
* // Checkbox simple
|
|
|
|
|
* <Checkbox label="Option 1" />
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* // Checkbox contrôlée
|
2026-01-13 18:47:57 +00:00
|
|
|
* <Checkbox
|
|
|
|
|
* checked={isChecked}
|
2026-01-07 09:31:02 +00:00
|
|
|
* onCheckedChange={setIsChecked}
|
|
|
|
|
* label="Accepter"
|
|
|
|
|
* />
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* // Checkbox désactivée
|
|
|
|
|
* <Checkbox label="Option" disabled />
|
|
|
|
|
* ```
|
2026-01-13 18:47:57 +00:00
|
|
|
*
|
2026-01-07 09:31:02 +00:00
|
|
|
* @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>(
|
2026-01-07 18:39:21 +00:00
|
|
|
({ label, className = '', onCheckedChange, id, ...props }, ref) => {
|
2026-01-07 09:31:02 +00:00
|
|
|
// 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
|
2026-01-07 18:39:21 +00:00
|
|
|
// const hasAccessibleLabel = label || props['aria-label'] || props['aria-labelledby'];
|
2025-12-03 21:56:50 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
return (
|
2026-01-07 18:39:21 +00:00
|
|
|
<label
|
2026-01-07 09:31:02 +00:00
|
|
|
htmlFor={checkboxId}
|
|
|
|
|
id={labelId}
|
2026-01-13 18:47:57 +00:00
|
|
|
className={cn(
|
aesthetic-improvements: align spacing to 8px grid (Action 11.2.1.3)
- 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
2026-01-16 10:50:46 +00:00
|
|
|
`inline-flex items-center gap-4 cursor-pointer group`,
|
2026-01-13 18:47:57 +00:00
|
|
|
props.disabled ? 'opacity-50 cursor-not-allowed' : '',
|
|
|
|
|
className,
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
>
|
|
|
|
|
<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
|
2026-01-13 18:47:57 +00:00
|
|
|
aria-label={
|
|
|
|
|
!label && !props['aria-label'] && !props['aria-labelledby']
|
|
|
|
|
? 'Checkbox'
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
aria-labelledby={label ? labelId : undefined}
|
|
|
|
|
{...props}
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<div
|
|
|
|
|
className="
|
refactor: Phase 3a — Global color class migration to SUMI semantics
- Replace all kodo-* color classes across ~100 TSX files:
kodo-void → background, kodo-ink → card, kodo-graphite → muted,
kodo-steel → muted-foreground, kodo-cyan → primary, kodo-magenta → destructive,
kodo-lime → success, kodo-red → destructive, kodo-gold → warning
- Replace cyan-500, magenta-500, lime-500 default Tailwind colors with
semantic equivalents (primary, destructive, success)
- Fix WaveformVisualizer hardcoded hex colors to SUMI values
- Delete global-effects.css (conflicting, redundant with index.css)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 00:51:49 +00:00
|
|
|
w-5 h-5 rounded border border-border bg-muted
|
ui(tokens): migrate kodo-cyan to primary (51 files, 88 instances)
Replace legacy text-kodo-cyan/border-kodo-cyan/bg-kodo-cyan with semantic
text-primary/border-primary/bg-primary across 51 components.
The brand primary color now uses the design system token, enabling proper
theme adaptation. Covers UI primitives, search, dashboard, chat, playlists,
settings, social, marketplace, and auth components.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 23:19:12 +00:00
|
|
|
peer-checked:bg-primary peer-checked:border-border
|
2026-02-09 22:23:09 +00:00
|
|
|
peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-background
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
transition-all duration-[var(--duration-fast)]
|
2026-01-13 18:47:57 +00:00
|
|
|
"
|
|
|
|
|
></div>
|
|
|
|
|
<Check
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
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 duration-[var(--duration-fast)] pointer-events-none"
|
2026-01-13 18:47:57 +00:00
|
|
|
strokeWidth={3}
|
|
|
|
|
/>
|
2026-01-07 09:31:02 +00:00
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
{label && (
|
2026-02-12 01:09:29 +00:00
|
|
|
<span className="text-sm text-foreground group-hover:text-foreground transition-colors duration-[var(--duration-fast)] select-none">
|
2026-01-13 18:47:57 +00:00
|
|
|
{label}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
2026-01-07 09:31:02 +00:00
|
|
|
</label>
|
|
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
},
|
2026-01-07 09:31:02 +00:00
|
|
|
);
|
|
|
|
|
Checkbox.displayName = 'Checkbox';
|