{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shimmer-button",
  "title": "Shimmer button",
  "description": "A primary button with a slow sheen, and an optional lean towards the pointer.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/manniche/shimmer-button/shimmer-button.tsx",
      "content": "import { useRef, type ButtonHTMLAttributes, type PointerEvent } from 'react'\nimport { useReducedMotion } from '@/registry/manniche/hooks/use-reduced-motion'\nimport { cn } from '@/lib/utils'\n\nexport type ShimmerButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {\n  /** Lean a few pixels towards the pointer. */\n  magnetic?: boolean\n}\n\n/**\n * A primary button with a slow sheen passing over it. Use it once per page, for the main action.\n * With `magnetic` it leans towards a mouse pointer. Both stop under reduced motion.\n */\nexport function ShimmerButton({ magnetic = false, className, children, onPointerMove, onPointerLeave, ...rest }: ShimmerButtonProps) {\n  const reduced = useReducedMotion()\n  const el = useRef<HTMLButtonElement>(null)\n\n  function move(e: PointerEvent<HTMLButtonElement>) {\n    onPointerMove?.(e)\n    if (!magnetic || reduced || e.pointerType !== 'mouse' || !el.current) return\n    const r = el.current.getBoundingClientRect()\n    const x = (e.clientX - r.left - r.width / 2) * 0.15\n    const y = (e.clientY - r.top - r.height / 2) * 0.25\n    el.current.style.transform = `translate(${x}px, ${y}px)`\n  }\n\n  function leave(e: PointerEvent<HTMLButtonElement>) {\n    onPointerLeave?.(e)\n    if (el.current) el.current.style.transform = ''\n  }\n\n  return (\n    <button\n      ref={el}\n      onPointerMove={move}\n      onPointerLeave={leave}\n      className={cn(\n        'relative isolate inline-flex min-h-11 items-center justify-center gap-2 overflow-hidden rounded-full bg-primary px-6 font-medium text-primary-foreground',\n        'transition-transform duration-300 ease-out-quint active:scale-[0.97] disabled:opacity-50',\n        className,\n      )}\n      {...rest}\n    >\n      <style href=\"manniche-shimmer\" precedence=\"default\">\n        {'@keyframes manniche-shimmer { 0% { transform: translateX(-100%) } 60%, 100% { transform: translateX(100%) } }'}\n      </style>\n      <span\n        aria-hidden\n        className=\"pointer-events-none absolute inset-0 -z-10 animate-[manniche-shimmer_2.8s_ease-in-out_infinite] bg-[linear-gradient(110deg,transparent_30%,color-mix(in_oklch,white_28%,transparent)_50%,transparent_70%)] motion-reduce:hidden\"\n      />\n      {children}\n    </button>\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "registry/manniche/hooks/use-reduced-motion.ts",
      "content": "import { useSyncExternalStore } from 'react'\n\nconst QUERY = '(prefers-reduced-motion: reduce)'\n\nfunction subscribe(onChange: () => void) {\n  const mq = window.matchMedia(QUERY)\n  mq.addEventListener('change', onChange)\n  return () => mq.removeEventListener('change', onChange)\n}\n\n/** True when the visitor has asked the system for less motion. */\nexport function useReducedMotion() {\n  return useSyncExternalStore(\n    subscribe,\n    () => window.matchMedia(QUERY).matches,\n    () => false,\n  )\n}\n",
      "type": "registry:hook"
    }
  ],
  "categories": [
    "surfaces"
  ],
  "type": "registry:component"
}