JavaScript 56.7%
TypeScript 42.6%
1"use client";23import { motion } from "framer-motion";4import { Loader2 } from "lucide-react";5import { ButtonHTMLAttributes, forwardRef } from "react";67interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {8 variant?: "primary" | "secondary" | "danger" | "ghost" | "outline" | "accent";9 size?: "xs" | "sm" | "md" | "lg" | "icon";10 loading?: boolean;11}1213const variants: Record<NonNullable<ButtonProps["variant"]>, string> = {14 // Noir/blanc net — action principale neutre15 primary:16 "bg-gray-900 text-white hover:bg-gray-800 active:bg-gray-700 shadow-card dark:bg-white dark:text-gray-950 dark:hover:bg-gray-100 dark:active:bg-gray-200",17 // Accent dégradé — action « héro » (Upload, Créer…)18 accent:19 "text-white bg-accent-gradient shadow-[0_1px_2px_rgb(0_0_0/.15),inset_0_1px_0_rgb(255_255_255/.18)] hover:brightness-110 active:brightness-95 hover:shadow-glow",20 secondary:21 "bg-gray-100 text-gray-800 hover:bg-gray-200 active:bg-gray-300 border border-gray-200/80 dark:bg-white/[0.07] dark:text-white dark:hover:bg-white/[0.12] dark:active:bg-white/[0.16] dark:border-white/10",22 outline:23 "bg-transparent text-gray-700 border border-gray-300 hover:bg-gray-50 hover:border-gray-400 dark:text-gray-200 dark:border-white/15 dark:hover:bg-white/5 dark:hover:border-white/25",24 danger:25 "bg-rose-50 text-rose-600 hover:bg-rose-100 active:bg-rose-200 border border-rose-200 dark:bg-rose-500/10 dark:text-rose-400 dark:hover:bg-rose-500/20 dark:active:bg-rose-500/30 dark:border-rose-500/20",26 ghost:27 "text-gray-600 hover:text-gray-900 hover:bg-gray-100 active:bg-gray-200 dark:text-gray-400 dark:hover:text-white dark:hover:bg-white/[0.07] dark:active:bg-white/10",28};2930// Cibles tactiles ≥ 40px sur mobile31const sizes: Record<NonNullable<ButtonProps["size"]>, string> = {32 xs: "px-2.5 py-1.5 text-xs min-h-[32px] rounded-lg gap-1.5",33 sm: "px-3 py-2 text-[13px] min-h-[38px] sm:min-h-[34px] rounded-xl gap-1.5",34 md: "px-4 py-2.5 text-sm min-h-[44px] sm:min-h-[40px] rounded-xl gap-2",35 lg: "px-6 py-3.5 text-base min-h-[48px] rounded-2xl gap-2",36 icon: "w-10 h-10 min-w-[40px] min-h-[40px] rounded-xl p-0",37};3839const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(40 { variant = "primary", size = "md", loading, children, className = "", disabled, ...props },41 ref42) {43 return (44 <motion.button45 ref={ref}46 whileTap={{ scale: 0.97 }}47 className={`inline-flex items-center justify-center font-medium transition-all duration-200 ease-spring disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:shadow-none select-none whitespace-nowrap ${variants[variant]} ${sizes[size]} ${className}`}48 disabled={disabled || loading}49 {...(props as Record<string, unknown>)}50 >51 {loading && <Loader2 className="w-4 h-4 animate-spin" />}52 {children}53 </motion.button>54 );55});5657export default Button;58