{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "title": "Button",
  "description": "The action control: primary (green, one per view), secondary, ghost and danger, in three sizes.",
  "dependencies": [
    "@base-ui/react@^1.9.0"
  ],
  "registryDependencies": [
    "https://sproutboat.com/ikada/r/theme.json",
    "https://sproutboat.com/ikada/r/cn.json",
    "https://sproutboat.com/ikada/r/icons.json"
  ],
  "files": [
    {
      "path": "registry/ikada/ui/button.tsx",
      "content": "\"use client\";\n\nimport { Button as BaseButton } from \"@base-ui/react/button\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { coarseTarget, focusRing, pressMotion, tv } from \"@/components/ikada/lib/cn\";\nimport type { VariantProps } from \"@/components/ikada/lib/cn\";\nimport { SpinnerIcon } from \"@/components/ikada/lib/icons\";\n\nexport const buttonStyles = tv({\n  base: [\n    \"relative inline-flex min-w-0 max-w-full shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-md font-semibold\",\n    pressMotion,\n    // Optical alignment: the icon side gets 2px less padding than the text side.\n    \"has-[>svg:first-child:not(:only-child)]:ps-3 has-[>svg:last-child:not(:only-child)]:pe-3\",\n    \"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-disabled:active:translate-y-0 data-disabled:active:scale-100\",\n    \"data-loading:cursor-progress\",\n    focusRing,\n    coarseTarget,\n  ],\n  variants: {\n    variant: {\n      // Hover is colour plus a lift in the cast shadow; a press presses the shadow in.\n      primary:\n        \"bg-brand text-on-brand shadow-primary hover:bg-brand-hover hover:shadow-primary-hover active:bg-brand-hover active:shadow-primary-pressed data-disabled:shadow-primary\",\n      secondary:\n        \"bg-hover text-fg shadow-button hover:bg-[color-mix(in_oklch,var(--sb-hover),var(--sb-fg)_9%)] hover:shadow-button-hover active:shadow-button-pressed data-disabled:shadow-button\",\n      ghost: \"bg-transparent text-fg-muted hover:bg-hover hover:text-fg\",\n      danger: [\n        \"bg-danger text-on-danger hover:bg-danger-hover\",\n        \"shadow-[inset_0_1px_0_oklch(1_0_0/0.2),0_1px_2px_oklch(0.3_0.1_25/0.3),0_6px_14px_-6px_oklch(0.35_0.12_25/0.4)]\",\n        \"hover:shadow-[inset_0_1px_0_oklch(1_0_0/0.24),0_2px_4px_oklch(0.3_0.1_25/0.3),0_10px_20px_-6px_oklch(0.35_0.12_25/0.5)]\",\n        \"active:bg-danger-hover active:shadow-[inset_0_2px_3px_oklch(0.2_0.08_25/0.35)]\",\n      ],\n    },\n    size: {\n      sm: \"h-(--sb-control-h-sm) px-2.5 text-sb-sm\",\n      md: \"h-(--sb-control-h) px-3.5 text-sb-base\",\n      lg: \"h-11 px-4.5 text-sb-base\",\n    },\n  },\n  defaultVariants: { variant: \"secondary\", size: \"md\" },\n});\n\n/** A plain-text label truncates at the button's max width instead of overflowing; icons and nodes are left alone. */\nfunction Label({ children }: { children: ReactNode }) {\n  return typeof children === \"string\" ? <span className=\"min-w-0 truncate\">{children}</span> : children;\n}\n\nexport type ButtonVariants = VariantProps<typeof buttonStyles>;\n\nexport interface ButtonProps extends Omit<ComponentProps<typeof BaseButton>, \"className\">, ButtonVariants {\n  className?: string;\n  /** Shows a spinner, sets aria-busy, ignores clicks, keeps the width and focus. */\n  loading?: boolean;\n}\n\n/**\n * The action control: primary (green, one per view), secondary, ghost and danger, in three sizes.\n * A press settles it 1px down and 3% smaller; the release bobs back once. `loading` keeps the width.\n */\nexport function Button({\n  variant,\n  size,\n  loading = false,\n  disabled,\n  className,\n  children,\n  onClick,\n  ref,\n  ...props\n}: ButtonProps) {\n  // A plain-text label truncates with an ellipsis instead of breaking the layout.\n  const label = typeof children === \"string\" ? <span className=\"min-w-0 truncate\">{children}</span> : children;\n  return (\n    <BaseButton\n      data-slot=\"button\"\n      ref={ref}\n      disabled={disabled || loading}\n      focusableWhenDisabled={loading}\n      aria-busy={loading || undefined}\n      data-loading={loading ? \"\" : undefined}\n      onClick={loading ? undefined : onClick}\n      className={buttonStyles({ variant, size, className })}\n      {...props}\n    >\n      {loading ? (\n        <>\n          <span className=\"inline-flex min-w-0 items-center gap-2 opacity-0\">{label}</span>\n          <SpinnerIcon className=\"absolute\" />\n        </>\n      ) : (\n        label\n      )}\n    </BaseButton>\n  );\n}\n",
      "type": "registry:ui",
      "target": "@components/ikada/button.tsx"
    }
  ],
  "categories": [
    "actions"
  ],
  "type": "registry:ui"
}