import * as React from 'react'; import { Check, Circle } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Dropdown } from './dropdown'; // Pure Kodo DropdownMenu implementation - No Radix UI dependency // Uses the existing Dropdown component as base /** * DropdownMenuProps - Propriétés du composant DropdownMenu * * @interface DropdownMenuProps */ export interface DropdownMenuProps { /** * Si `true`, le menu est ouvert (mode contrôlé) * Si non fourni, le menu gère son propre état (mode non-contrôlé) * * @example * ```tsx * * Menu * ... * * ``` */ open?: boolean; /** * Fonction appelée lorsque l'état ouvert change * * @param {boolean} open - Nouvel état ouvert */ onOpenChange?: (open: boolean) => void; /** * Enfants du composant (DropdownMenuTrigger et DropdownMenuContent) */ children: React.ReactNode; } /** * DropdownMenu - Composant de menu déroulant avec design system Kodo * * Composant de menu déroulant pour afficher des actions ou des options. * Implémentation pure Kodo sans dépendance Radix UI. * * @example * ```tsx * // Menu déroulant simple * * * * * * Éditer * Supprimer * * * ``` * * @component * @param {DropdownMenuProps} props - Propriétés du composant * @returns {JSX.Element} Menu déroulant avec trigger et contenu */ const DropdownMenu: React.FC = ({ open, onOpenChange, children }) => { const [_internalOpen, setInternalOpen] = React.useState(false); const isControlled = open !== undefined; const handleOpenChange = (newOpen: boolean) => { if (!isControlled) { setInternalOpen(newOpen); } onOpenChange?.(newOpen); }; // Extract trigger and content from children const trigger = React.Children.toArray(children).find( (child) => React.isValidElement(child) && child.type === DropdownMenuTrigger ); const content = React.Children.toArray(children).find( (child) => React.isValidElement(child) && child.type === DropdownMenuContent ); if (!trigger || !content) { return <>{children}; } return ( {React.isValidElement(content) ? content.props.children : content} ); }; export interface DropdownMenuTriggerProps extends React.HTMLAttributes { asChild?: boolean; } const DropdownMenuTrigger = React.forwardRef( ({ className, children, asChild, ...props }, ref) => { if (asChild && React.isValidElement(children)) { return React.cloneElement(children, { ref, className: cn(className, children.props.className), ...props, } as any); } return ( ); } ); DropdownMenuTrigger.displayName = 'DropdownMenuTrigger'; export interface DropdownMenuContentProps extends React.HTMLAttributes { align?: 'start' | 'end' | 'center'; sideOffset?: number; } const DropdownMenuContent = React.forwardRef( ({ className, align = 'start', sideOffset = 4, children, ...props }, ref) => { return (
{children}
); } ); DropdownMenuContent.displayName = 'DropdownMenuContent'; export interface DropdownMenuItemProps extends React.ButtonHTMLAttributes { inset?: boolean; } const DropdownMenuItem = React.forwardRef( ({ className, inset, onKeyDown, onClick, ...props }, ref) => { // CRITIQUE FIX #47: Gestion complète du clavier pour l'accessibilité const handleKeyDown = (e: React.KeyboardEvent) => { // Gérer Enter et Space pour activer l'item if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if (onClick && !props.disabled) { onClick(e as any); } } // Gérer Escape pour fermer le menu (géré par le composant parent Dropdown) // Les flèches sont gérées par le composant Dropdown parent // Appeler le handler personnalisé s'il existe if (onKeyDown) { onKeyDown(e); } }; return ( ) ); DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem'; export interface DropdownMenuRadioItemProps extends React.ButtonHTMLAttributes { value: string; checked?: boolean; } const DropdownMenuRadioItem = React.forwardRef( ({ className, children, checked, ...props }, ref) => ( ) ); DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem'; export interface DropdownMenuLabelProps extends React.HTMLAttributes { inset?: boolean; } const DropdownMenuLabel = React.forwardRef( ({ className, inset, ...props }, ref) => (
) ); DropdownMenuLabel.displayName = 'DropdownMenuLabel'; export interface DropdownMenuSeparatorProps extends React.HTMLAttributes {} const DropdownMenuSeparator = React.forwardRef( ({ className, ...props }, ref) => (
) ); DropdownMenuSeparator.displayName = 'DropdownMenuSeparator'; const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => { return ( ); }; DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; // Placeholder components for compatibility (not fully implemented but exported) const DropdownMenuGroup: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; const DropdownMenuPortal: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; const DropdownMenuSub: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; const DropdownMenuSubContent: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; const DropdownMenuSubTrigger: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; const DropdownMenuRadioGroup: React.FC<{ children: React.ReactNode }> = ({ children }) => <>{children}; export { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuGroup, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuRadioGroup, };