{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"button","type":"registry:component","title":"Button","description":"Button with a quick press, hairline rings instead of borders, four variants, and a 44px tap area even at small sizes.","author":"Ryan","dependencies":["clsx","motion","tailwind-merge"],"registryDependencies":[],"files":[{"path":"components/motion/button.tsx","type":"registry:component","target":"@components/motion/button.tsx","content":"\"use client\";\n// easeui.dev/components/motion/button\n\nimport { type HTMLMotionProps, motion, useReducedMotion } from \"motion/react\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ButtonVariant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\" | \"icon\";\n\nexport interface ButtonProps extends Omit<HTMLMotionProps<\"button\">, \"children\"> {\n  /** Visual style. Default \"primary\". */\n  variant?: ButtonVariant;\n  /** Height and padding. \"icon\" is a square button for a single icon. Default \"md\". */\n  size?: ButtonSize;\n  children?: ReactNode;\n}\n\n// A quick press confirms the tap before the action finishes.\nconst PRESS = { scale: 0.97 };\nconst PRESS_TRANSITION = { duration: 0.15, ease: [0.23, 1, 0.32, 1] } as const;\n\n// Hairline rings are drawn with box-shadow so they blend with any background.\nconst VARIANT_CLASS: Record<ButtonVariant, string> = {\n  primary: \"bg-foreground text-background hover:bg-foreground/90\",\n  secondary:\n    \"bg-card text-foreground shadow-[0_0_0_1px_var(--border)] hover:bg-muted\",\n  outline:\n    \"text-foreground shadow-[0_0_0_1px_var(--border-strong)] hover:bg-foreground/5\",\n  ghost: \"text-muted-foreground hover:bg-foreground/5 hover:text-foreground\",\n};\n\n// Small sizes grow an invisible hit area so the tap target stays around 44px.\nconst SIZE_CLASS: Record<ButtonSize, string> = {\n  sm: \"h-8 gap-1.5 px-3 text-xs after:absolute after:-inset-1.5\",\n  md: \"h-10 gap-2 px-4 text-sm\",\n  lg: \"h-12 gap-2 px-5 text-base\",\n  icon: \"h-9 w-9 after:absolute after:-inset-1\",\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n  { variant = \"primary\", size = \"md\", type = \"button\", className, children, ...props },\n  ref,\n) {\n  const reduce = useReducedMotion();\n\n  return (\n    <motion.button\n      ref={ref}\n      type={type}\n      whileTap={reduce ? undefined : PRESS}\n      transition={PRESS_TRANSITION}\n      className={cn(\n        \"relative inline-flex shrink-0 touch-manipulation select-none items-center justify-center rounded-full font-medium outline-none\",\n        \"transition-[background-color,color,box-shadow] duration-150 ease-out\",\n        \"focus-visible:ring-2 focus-visible:ring-foreground/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        \"disabled:pointer-events-none disabled:opacity-50\",\n        VARIANT_CLASS[variant],\n        SIZE_CLASS[size],\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </motion.button>\n  );\n});\n"},{"path":"lib/utils.ts","type":"registry:lib","target":"@lib/utils.ts","content":"import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs))\n}\n"}]}