{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "type": "registry:ui",
  "title": "Button",
  "description": "Button built on Base UI with seam depth motion.",
  "dependencies": [
    "@base-ui/react",
    "motion",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "https://seamui.dev/r/utils.json",
    "https://seamui.dev/r/motion.json",
    "https://seamui.dev/r/haptics.json"
  ],
  "files": [
    {
      "path": "registry/seam/ui/button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Button as BaseButton } from \"@base-ui/react/button\"\nimport { motion } from \"motion/react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { springs, fades, depth, reduced, useReducedMotion } from \"@/lib/motion\"\nimport { useHaptics, type HapticPreset } from \"@/lib/haptics\"\n\nconst buttonVariants = cva(\n  // base — no CSS transition classes; motion.dev owns transform/shadow.\n  \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lg squircle text-sm font-medium outline-none select-none focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-primary text-primary-foreground shadow-resting hover:bg-primary/90\",\n        destructive:\n          \"bg-destructive text-white shadow-resting hover:bg-destructive/90\",\n        outline:\n          \"border border-border/60 bg-card shadow-resting hover:bg-secondary\",\n        // raised white \"key\" resting on the canvas (see seamui design language).\n        secondary:\n          \"bg-secondary text-secondary-foreground shadow-resting hover:shadow-raised\",\n        ghost: \"hover:bg-accent hover:text-accent-foreground\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n      },\n      size: {\n        default: \"h-10 px-5 py-2\",\n        sm: \"h-9 rounded-md px-3.5 text-xs\",\n        lg: \"h-11 rounded-lg px-7\",\n        icon: \"size-10\",\n      },\n    },\n    defaultVariants: { variant: \"default\", size: \"default\" },\n  }\n)\n\n/** Variants with no resting shadow to compress: they press by dimming\n *  (movement-free) rather than receding in z, so a scale-down doesn't read. */\nconst FLAT_VARIANTS = new Set([\"ghost\", \"link\"])\n\n// motion.create() per element type, cached so re-renders reuse components.\nconst motionTagCache = new Map<string, React.ElementType>()\nconst motionTypeCache = new WeakMap<object, React.ElementType>()\nfunction asMotion(type: React.ElementType): React.ElementType {\n  const cache = typeof type === \"string\" ? motionTagCache : motionTypeCache\n  let cached = cache.get(type as never)\n  if (!cached) {\n    cached = motion.create(type as React.ComponentType) as React.ElementType\n    cache.set(type as never, cached as never)\n  }\n  return cached\n}\n\nexport interface ButtonProps\n  extends React.ComponentProps<typeof BaseButton>,\n    VariantProps<typeof buttonVariants> {\n  /** Haptic on press when a HapticsProvider is mounted: `true` = \"tap\",\n   *  a preset name to override, `false` to opt out. */\n  haptic?: boolean | HapticPreset\n}\n\nfunction Button({\n  className,\n  variant,\n  size,\n  disabled,\n  render,\n  haptic = true,\n  onPointerDown,\n  ...props\n}: ButtonProps) {\n  const reduceMotion = useReducedMotion()\n  const { trigger } = useHaptics()\n  const flat = FLAT_VARIANTS.has(variant ?? \"default\")\n\n  // Base UI keeps native semantics (real <button>, or the caller's render\n  // element, e.g. a <Link>); the motion wrapper supplies seam press feedback:\n  // recede into the surface, spring back on release. Under reduced motion\n  // the press dims instead of moving.\n  const motionProps = {\n    // Flat variants (ghost/link) still react to press — they just dim instead\n    // of receding, since there's no shadow to compress. Every control gives\n    // press feedback (§1); only movement is dropped, never the feedback.\n    whileTap: disabled\n      ? undefined\n      : flat || reduceMotion\n        ? reduced.pressed\n        : depth.pressed,\n    transition: reduceMotion ? fades.fast : springs.press,\n  }\n\n  let motionRender: React.ComponentProps<typeof BaseButton>[\"render\"]\n  if (render === undefined) {\n    motionRender = <motion.button {...motionProps} />\n  } else if (React.isValidElement(render)) {\n    const MotionEl = asMotion(render.type as React.ElementType)\n    motionRender = <MotionEl {...(render.props as object)} {...motionProps} />\n  } else {\n    // function-form render: pass through untouched (no press motion).\n    motionRender = render\n  }\n\n  // Tell Base UI when the rendered element is not a real <button> (e.g. a\n  // link) so it applies role/keyboard semantics instead of warning.\n  const nativeButton =\n    render === undefined ||\n    (React.isValidElement(render) && render.type === \"button\")\n\n  return (\n    <BaseButton\n      data-slot=\"button\"\n      className={cn(buttonVariants({ variant, size, className }))}\n      disabled={disabled}\n      nativeButton={nativeButton}\n      render={motionRender}\n      // seam touch feedback, tactile half: a haptic tap the moment the\n      // pointer lands (primary button/touch only). No-op without a provider.\n      onPointerDown={(e) => {\n        onPointerDown?.(e)\n        if (haptic && !disabled && e.button === 0) {\n          trigger(haptic === true ? \"tap\" : haptic)\n        }\n      }}\n      {...props}\n    />\n  )\n}\n\nexport { Button, buttonVariants }\n",
      "type": "registry:ui",
      "target": "components/ui/button.tsx"
    }
  ]
}