{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "title": "Button",
  "description": "Presses spring back, the label lifts away for a spinner while pending, and an arrow nudges on hover.",
  "dependencies": [
    "@ui-sdk/core@0.1.0-beta.2",
    "@ui-sdk/react@0.1.0-beta.2"
  ],
  "registryDependencies": [
    "@keyframery/motion"
  ],
  "files": [
    {
      "path": "registry/components/keyframery/primitives/button.tsx",
      "content": "\"use client\"\n\n/* Animated drop-in for `@ui-sdk/react/button`: same part, same props. Swap the import and keep your code.\n   This file adds motion only. Colours, radii, shadows and spacing stay yours.\n   - Press: the button gives under the pointer and springs back with a small pop on release.\n   - Direction: while its action runs, the label rises out and a spinner rises in from below.\n   - Nudge: a `ButtonArrow` inside slides forward and grows its stem on hover. */\n\nimport { Button as ButtonPart } from \"@ui-sdk/react/button\"\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport * from \"@ui-sdk/react/button\"\n\n/* Old content leaves fast and flat; new content arrives on a spring. */\nconst swap =\n  \"transition-[opacity,translate,filter] duration-[calc(460ms*var(--kf-speed,1))] ease-spring motion-reduce:transition-none!\"\n\nfunction Spinner() {\n  return (\n    <svg\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      className=\"size-4 animate-spin [animation-duration:calc(700ms*var(--kf-speed,1))] [animation-play-state:paused] group-data-pending/kf-button:[animation-play-state:running] motion-reduce:[animation-duration:calc(1600ms*var(--kf-speed,1))]\"\n    >\n      <circle cx=\"8\" cy=\"8\" r=\"5.75\" opacity=\"0.28\" />\n      <path d=\"M8 2.25A5.75 5.75 0 0 1 13.75 8\" />\n    </svg>\n  )\n}\n\nfunction Button({\n  className,\n  children,\n  pendingIndicator,\n  ...props\n}: React.ComponentProps<typeof ButtonPart> & {\n  /** What rises in while the button is pending. Defaults to a spinner; pass `null` for none. */\n  pendingIndicator?: React.ReactNode\n}) {\n  return (\n    <ButtonPart\n      className={cn(\n        \"group/kf-button relative\",\n        /* Colours and shadows ease as the pointer arrives; the press springs back with a pop. */\n        \"[transition:color_calc(150ms*var(--kf-speed,1))_var(--ease-house),background-color_calc(150ms*var(--kf-speed,1))_var(--ease-house),box-shadow_calc(150ms*var(--kf-speed,1))_var(--ease-house),opacity_calc(150ms*var(--kf-speed,1))_var(--ease-house),scale_calc(260ms*var(--kf-speed,1))_var(--ease-pop)]\",\n        \"not-data-disabled:not-data-pending:active:scale-[0.96] not-data-disabled:not-data-pending:active:[transition-duration:calc(150ms*var(--kf-speed,1)),calc(150ms*var(--kf-speed,1)),calc(150ms*var(--kf-speed,1)),calc(150ms*var(--kf-speed,1)),calc(90ms*var(--kf-speed,1))]\",\n        \"not-data-disabled:hover:[--kf-nudge:1] not-data-disabled:data-hovered:[--kf-nudge:1] focus-visible:[--kf-nudge:1]\",\n        \"motion-reduce:[transition:none]!\",\n        className,\n      )}\n      {...props}\n    >\n      {(state) => (\n        <>\n          <span\n            data-slot=\"button-label\"\n            className={cn(\n              \"inline-flex flex-1 items-center [justify-content:inherit] [gap:inherit]\",\n              swap,\n              \"group-data-pending/kf-button:-translate-y-4 group-data-pending/kf-button:opacity-0 group-data-pending/kf-button:blur-[3px]\",\n              \"group-data-pending/kf-button:duration-[calc(240ms*var(--kf-speed,1))] group-data-pending/kf-button:ease-house\",\n            )}\n          >\n            {typeof children === \"function\" ? children(state) : children}\n          </span>\n          {pendingIndicator !== null && (\n            <span\n              aria-hidden\n              data-slot=\"button-pending\"\n              className={cn(\n                \"pointer-events-none absolute inset-0 grid place-items-center\",\n                swap,\n                \"translate-y-4 opacity-0 blur-[3px] duration-[calc(200ms*var(--kf-speed,1))] ease-house\",\n                \"group-data-pending/kf-button:translate-y-0 group-data-pending/kf-button:opacity-100 group-data-pending/kf-button:blur-none\",\n                \"group-data-pending/kf-button:duration-[calc(460ms*var(--kf-speed,1))] group-data-pending/kf-button:ease-spring\",\n              )}\n            >\n              {pendingIndicator ?? <Spinner />}\n            </span>\n          )}\n        </>\n      )}\n    </ButtonPart>\n  )\n}\n\n/* A trailing arrow for \"go on\" buttons. At rest it is a chevron; on hover (or keyboard focus)\n   it slides forward and its stem draws in behind it. */\nfunction ButtonArrow({ className, ...props }: React.ComponentProps<\"svg\">) {\n  return (\n    <svg\n      data-slot=\"button-arrow\"\n      viewBox=\"0 0 16 16\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"1.5\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden\n      className={cn(\"overflow-visible\", className)}\n      {...props}\n    >\n      <path\n        d=\"M3.5 8h8\"\n        pathLength={1}\n        className=\"[stroke-dasharray:1] [stroke-dashoffset:calc(1-var(--kf-nudge,0))] opacity-(--kf-nudge,0) [transition:stroke-dashoffset_calc(320ms*var(--kf-speed,1))_var(--ease-spring),opacity_calc(120ms*var(--kf-speed,1))_var(--ease-house)] motion-reduce:[transition:none]!\"\n      />\n      <path\n        d=\"M8 4.5 11.5 8 8 11.5\"\n        className=\"[translate:calc(var(--kf-nudge,0)*3px-2.5px)_0] [transition:translate_calc(320ms*var(--kf-speed,1))_var(--ease-spring)] motion-reduce:[transition:none]!\"\n      />\n    </svg>\n  )\n}\n\nexport { Button, ButtonArrow }\n",
      "type": "registry:component",
      "target": "components/keyframery/primitives/button.tsx"
    },
    {
      "path": "registry/components/keyframery/button.tsx",
      "content": "\"use client\"\n\n/* Styled button: the animated primitive plus the Keyframery look. */\n\nimport * as React from \"react\"\n\nimport { ButtonArrow, Button as ButtonPart } from \"@/components/keyframery/primitives/button\"\nimport { cn } from \"@/lib/utils\"\n\ntype ButtonVariant = \"default\" | \"primary\" | \"ghost\" | \"destructive\"\ntype ButtonSize = \"sm\" | \"md\" | \"lg\" | \"icon\" | \"icon-sm\" | \"icon-lg\"\n\n/* The white key. Shared by the default and destructive variants. */\nconst key = [\n  \"bg-background dark:bg-accent\",\n  \"shadow-[0_0_0_1px_rgb(0_0_0/0.06),0_1px_1px_rgb(0_0_0/0.05),0_3px_6px_-2px_rgb(0_0_0/0.1)]\",\n  \"dark:shadow-[0_0_0_1px_rgb(255_255_255/0.07),inset_0_1px_0_rgb(255_255_255/0.08),0_2px_6px_-1px_rgb(0_0_0/0.6)]\",\n  \"not-data-disabled:hover:shadow-[0_0_0_1px_rgb(0_0_0/0.1),0_1px_1px_rgb(0_0_0/0.05),0_4px_8px_-2px_rgb(0_0_0/0.12)]\",\n  \"dark:not-data-disabled:hover:shadow-[0_0_0_1px_rgb(255_255_255/0.12),inset_0_1px_0_rgb(255_255_255/0.1),0_2px_6px_-1px_rgb(0_0_0/0.6)]\",\n  \"not-data-disabled:not-data-pending:active:shadow-[0_0_0_1px_rgb(0_0_0/0.1),0_1px_1px_rgb(0_0_0/0.04),0_1px_2px_-1px_rgb(0_0_0/0.06)]\",\n  \"dark:not-data-disabled:not-data-pending:active:shadow-[0_0_0_1px_rgb(255_255_255/0.09),inset_0_1px_0_rgb(255_255_255/0.04),0_1px_2px_-1px_rgb(0_0_0/0.6)]\",\n]\n\nconst variants: Record<ButtonVariant, (string | string[])[]> = {\n  default: [\n    key,\n    \"text-foreground\",\n    \"not-data-disabled:hover:bg-[color-mix(in_oklab,var(--background),var(--foreground)_3%)]\",\n    \"dark:not-data-disabled:hover:bg-[color-mix(in_oklab,var(--accent),var(--foreground)_7%)]\",\n  ],\n  primary: [\n    \"bg-primary text-primary-foreground\",\n    \"shadow-[inset_0_1px_0_rgb(255_255_255/0.14),0_1px_2px_rgb(0_0_0/0.25)]\",\n    \"dark:shadow-[inset_0_1px_0_rgb(255_255_255/0.7),0_1px_2px_rgb(0_0_0/0.5)]\",\n    \"not-data-disabled:hover:bg-[color-mix(in_oklab,var(--primary),var(--background)_10%)]\",\n    \"not-data-disabled:hover:shadow-[inset_0_1px_0_rgb(255_255_255/0.18),0_2px_5px_rgb(0_0_0/0.25)]\",\n    \"dark:not-data-disabled:hover:shadow-[inset_0_1px_0_rgb(255_255_255/0.7),0_2px_5px_rgb(0_0_0/0.5)]\",\n    \"not-data-disabled:not-data-pending:active:shadow-[inset_0_1px_0_rgb(255_255_255/0.08),0_0_1px_rgb(0_0_0/0.3)]\",\n    \"dark:not-data-disabled:not-data-pending:active:shadow-[inset_0_1px_0_rgb(255_255_255/0.4),0_0_1px_rgb(0_0_0/0.5)]\",\n  ],\n  ghost: [\n    \"text-foreground\",\n    \"not-data-disabled:hover:bg-accent\",\n    \"not-data-disabled:not-data-pending:active:bg-[color-mix(in_oklab,var(--accent),var(--foreground)_6%)]\",\n  ],\n  destructive: [\n    key,\n    \"text-destructive\",\n    \"not-data-disabled:hover:bg-[color-mix(in_oklab,var(--background),var(--destructive)_7%)]\",\n    \"dark:not-data-disabled:hover:bg-[color-mix(in_oklab,var(--accent),var(--destructive)_14%)]\",\n  ],\n}\n\nconst sizes: Record<ButtonSize, string> = {\n  sm: \"h-7 gap-1.5 rounded-[8px] px-2.5 text-[12px] [&_svg]:size-3.5\",\n  md: \"h-8 gap-1.5 rounded-[9px] px-3 text-[13px] [&_svg]:size-4\",\n  lg: \"h-9 gap-2 rounded-[10px] px-4 text-[14px] [&_svg]:size-4\",\n  \"icon-sm\": \"size-7 rounded-[8px] [&_svg]:size-3.5\",\n  icon: \"size-8 rounded-[9px] [&_svg]:size-4\",\n  \"icon-lg\": \"size-9 rounded-[10px] [&_svg]:size-4\",\n}\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"md\",\n  ...props\n}: React.ComponentProps<typeof ButtonPart> & {\n  variant?: ButtonVariant\n  size?: ButtonSize\n}) {\n  return (\n    <ButtonPart\n      data-variant={variant}\n      data-size={size}\n      className={cn(\n        \"group/button relative inline-flex shrink-0 cursor-pointer items-center justify-center leading-none font-medium whitespace-nowrap outline-none select-none\",\n        \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n        \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n        \"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-pending:cursor-progress\",\n        /* UI SDK's convention for content that belongs to one state only. */\n        \"[&:not([data-pending])_[data-show=pending]]:hidden [&[data-pending]_[data-show=idle]]:hidden\",\n        variants[variant],\n        sizes[size],\n        className,\n      )}\n      {...props}\n    />\n  )\n}\n\n/* Holds a leading icon. While the button is pending it rises out with the label,\n   and the primitive's spinner takes the middle. */\nfunction ButtonIcon({ className, ...props }: React.ComponentProps<\"span\">) {\n  return (\n    <span\n      data-slot=\"button-icon\"\n      aria-hidden\n      className={cn(\"inline-grid shrink-0 place-items-center\", className)}\n      {...props}\n    />\n  )\n}\n\nexport { Button, ButtonArrow, ButtonIcon }\n",
      "type": "registry:component",
      "target": "components/keyframery/button.tsx"
    }
  ],
  "type": "registry:component"
}