{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "dependencies": [
    "@base-ui/react",
    "class-variance-authority",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://sdk-components.thesqd.com/r/dropdown-menu.json"
  ],
  "files": [
    {
      "path": "src/components/ui/button.tsx",
      "content": "import * as React from \"react\"\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\nimport { ChevronDownIcon } from \"lucide-react\"\n\nimport { cn } from \"@/lib/utils\"\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuGroup,\n  DropdownMenuItem,\n  DropdownMenuRadioGroup,\n  DropdownMenuRadioItem,\n  DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\nconst buttonVariants = cva(\n  \"group/button inline-flex shrink-0 items-center justify-center rounded-full border border-transparent text-sm leading-none font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    variants: {\n      variant: {\n        default:\n          \"bg-[#513DE5] text-white shadow-sm hover:bg-[#513DE5]/90 hover:shadow-md [a]:hover:bg-[#513DE5]/90 dark:bg-[#513DE5] dark:hover:bg-[#513DE5]/90\",\n        // Glossy pill — a top-lit linear gradient with a soft white inset\n        // highlight and drop shadow; presses in on active. Color-agnostic\n        // here; the actual gradient/border/bg color comes from the `color`\n        // prop via `fancyColors` below (defaults to brand). Ported from\n        // ds.asanshay.com's `default` variant.\n        // Glossy pill — a top-lit vertical gradient with a refined triple\n        // inset/drop shadow (top white highlight + bottom inner shadow + soft\n        // drop), a hairline ring, and a subtle press-scale. Color-agnostic\n        // here; the gradient stops / ring / bg color come from the `color`\n        // prop via `fancyColors` below (defaults to brand). Gloss treatment\n        // ported from cubby-ui's FancyButton.\n        fancy:\n          \"ring-1 bg-linear-to-b text-primary-foreground shadow-[inset_0_1px_1px_oklch(1_0_0_/_0.4),0_1px_2px_0_oklch(0.1_0_0_/_0.07),inset_0_-0.5px_1px_oklch(0_0_0_/_0.1)] dark:shadow-[inset_0_1px_1px_oklch(1_0_0_/_0.2),0_1px_2px_0_oklch(0.1_0_0_/_0.07),inset_0_-1px_1px_oklch(0_0_0_/_0.1)] hover:opacity-90 active:scale-[0.98]\",\n        outline:\n          \"border-input bg-white shadow-sm hover:bg-accent hover:text-accent-foreground hover:shadow-md aria-expanded:bg-accent aria-expanded:text-accent-foreground dark:border-[#242428] dark:bg-transparent dark:hover:bg-[#242428] dark:hover:border-[#242428] dark:hover:text-foreground\",\n        // The `outline` surface with the `fancy` gloss — same neutral surface\n        // (white / dark transparent, hover accent) but with a HARD, crisp\n        // border and only a faint white inset top-highlight, a deeper shadow,\n        // and a press-in on active.\n        \"fancy-outline\":\n          \"border-black/25 bg-black/[0.04] text-foreground shadow-md inset-shadow-[0px_1px_0px_1px] inset-shadow-white/60 hover:bg-black/[0.07] active:translate-y-px active:inset-shadow-black/10 aria-expanded:bg-black/[0.07] dark:border-white/30 dark:bg-white/5 dark:inset-shadow-white/15 dark:hover:bg-white/10 dark:aria-expanded:bg-white/10\",\n        secondary:\n          \"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80 hover:shadow-md aria-expanded:bg-secondary aria-expanded:text-secondary-foreground\",\n        ghost:\n          \"hover:bg-primary/10 hover:text-primary aria-expanded:bg-primary/10 aria-expanded:text-primary dark:hover:bg-primary/15\",\n        destructive:\n          \"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40\",\n        // `not-prose` + `!important` keep the link button clean inside a\n        // Tailwind Typography `prose` container, whose `.prose a` rule\n        // (text-decoration: underline) has the same specificity as\n        // `no-underline` and would otherwise win by source order. The offset\n        // is forced too so prose's tighter link underline can't bleed through.\n        link: \"not-prose text-primary !no-underline !underline-offset-4 !decoration-1 hover:!underline [&_span]:[text-box:normal]\",\n        soft:\n          \"bg-primary/10 text-primary hover:bg-primary/15 dark:bg-primary/20 dark:hover:bg-primary/25 aria-expanded:bg-primary/15 aria-expanded:text-primary\",\n        // Inherits the surrounding text color via `currentColor`, so the\n        // button visually melts into whatever tonal surface it sits on\n        // (e.g. the warning Alert in the action-button demo). Use this\n        // when the parent already establishes the tone.\n        tonal:\n          \"bg-current/10 text-current hover:bg-current/20 aria-expanded:bg-current/20\",\n        // Squad coral pop — solid pill in CMS coral with darkest-purple text.\n        // Use as the headline CTA on dark surfaces (e.g. DashboardHero).\n        coral:\n          \"bg-[#FBA09C] text-[#341756] shadow-sm hover:bg-[#FBA09C]/90 hover:text-[#341756] hover:shadow-md dark:bg-[#FBA09C] dark:hover:bg-[#FBA09C]/90\",\n        // Transparent pill that borrows the surrounding text color via\n        // currentColor — works on any background. Pair with `coral` as a\n        // secondary action on dark heroes.\n        transparent:\n          \"border border-current/30 bg-transparent text-current hover:border-current/60 hover:bg-current/10 aria-expanded:bg-current/10\",\n      },\n      size: {\n        xs: \"h-6 gap-1 rounded-full px-2 text-xs leading-none in-data-[slot=button-group]:rounded-full has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3\",\n        sm: \"h-8 gap-1.5 rounded-full px-3 text-[0.8rem] leading-none in-data-[slot=button-group]:rounded-full has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5\",\n        xl: \"h-12 gap-2 px-6 text-base leading-none has-data-[icon=inline-end]:pr-5 has-data-[icon=inline-start]:pl-5 [&_svg:not([class*='size-'])]:size-5\",\n        default:\n          \"h-10 gap-2 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        wide: \"h-10 w-full gap-2 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        icon: \"size-10\",\n        \"icon-xs\":\n          \"size-6 rounded-full in-data-[slot=button-group]:rounded-full [&_svg:not([class*='size-'])]:size-3\",\n        \"icon-sm\":\n          \"size-8 rounded-full in-data-[slot=button-group]:rounded-full\",\n        \"icon-xl\": \"size-12 [&_svg:not([class*='size-'])]:size-5\",\n      },\n    },\n    defaultVariants: {\n      variant: \"default\",\n      size: \"default\",\n    },\n  }\n)\n\n// Color tokens for `variant=\"fancy\"`. The `fancy` base is color-agnostic\n// (gloss, inset highlight, shadow, press); these supply the gradient stops /\n// border / bg per color. `coral` + `warning` flip to dark text for contrast.\n// Pass `color` to pick one (defaults to `brand`); `className` still overrides.\nexport type FancyButtonColor =\n  | \"brand\"\n  | \"coral\"\n  | \"destructive\"\n  | \"warning\"\n  | \"success\"\n  | \"ghost\"\n\n// Per-color tokens for `variant=\"fancy\"`. Each supplies a `bg`, a subtle\n// top→bottom gradient (full color → 12% darker), and a darker hairline `ring`.\n// `coral` + `warning` flip to dark text for contrast. `ghost` is the neutral\n// dark glossy button (cubby-ui's \"Default\"): a near-black surface that stays\n// dark in both light and dark mode, so it can't map to a theme token.\nconst fancyColors: Record<FancyButtonColor, string> = {\n  brand:\n    \"bg-primary from-primary to-[color-mix(in_oklch,var(--primary),black_12%)] ring-[color-mix(in_oklch,var(--primary),black_18%)] text-white\",\n  coral:\n    \"bg-[#FBA09C] from-[#FBA09C] to-[color-mix(in_oklch,#FBA09C,black_12%)] ring-[color-mix(in_oklch,#FBA09C,black_18%)] text-[#341756]\",\n  destructive:\n    \"bg-destructive from-destructive to-[color-mix(in_oklch,var(--destructive),black_12%)] ring-[color-mix(in_oklch,var(--destructive),black_18%)] text-white\",\n  warning:\n    \"bg-amber-500 from-amber-500 to-[color-mix(in_oklch,var(--color-amber-500),black_12%)] ring-[color-mix(in_oklch,var(--color-amber-500),black_18%)] text-amber-950\",\n  success:\n    \"bg-green-600 from-green-600 to-[color-mix(in_oklch,var(--color-green-600),black_12%)] ring-[color-mix(in_oklch,var(--color-green-600),black_18%)] text-white\",\n  ghost:\n    \"bg-[oklch(0.3_0_0)] from-[oklch(0.32_0_0)] to-[oklch(0.28_0_0)] ring-[oklch(0.15_0_0)] text-white hover:bg-[oklch(0.35_0_0)] dark:bg-[oklch(0.2_0_0)] dark:from-[oklch(0.22_0_0)] dark:to-[oklch(0.18_0_0)] dark:ring-[oklch(0.15_0_0)] dark:text-secondary-foreground dark:hover:bg-[oklch(0.22_0_0)]\",\n}\n\n// Inter's line box is taller than its cap-height and sits asymmetrically\n// around the glyph, so `items-center` centers the box (with its invisible\n// ascender/descender slack) rather than the visible text — labels read a hair\n// high, worst at small sizes. Trimming the line box to cap/alphabetic edges on\n// the text node (only) lets flex center the actual glyphs while leaving icons\n// untouched. Browsers without `text-box-trim` ignore it and fall back cleanly.\nfunction renderButtonChildren(children: React.ReactNode) {\n  return React.Children.map(children, (child) => {\n    if (\n      (typeof child === \"string\" && child.trim() !== \"\") ||\n      typeof child === \"number\"\n    ) {\n      return (\n        <span className=\"squad-ui [text-box:trim-both_cap_alphabetic]\">\n          {child}\n        </span>\n      )\n    }\n    return child\n  })\n}\n\nfunction Button({\n  className,\n  variant = \"default\",\n  size = \"default\",\n  color,\n  render,\n  nativeButton,\n  children,\n  ...props\n}: Omit<ButtonPrimitive.Props, \"color\"> &\n  VariantProps<typeof buttonVariants> & {\n    /**\n     * Color for `variant=\"fancy\"` (ignored by other variants). Defaults to\n     * `brand`. Pass `className` to override beyond these presets. The\n     * `string & {}` keeps literal autocomplete while still accepting the\n     * native `color` attribute that spreads (e.g. react-day-picker) carry.\n     */\n    color?: FancyButtonColor | (string & {})\n  }) {\n  return (\n    <ButtonPrimitive\n      data-slot=\"button\"\n      className={cn(\n        \"squad-ui\",\n        buttonVariants({ variant, size }),\n        variant === \"fancy\"\n          ? fancyColors[(color as FancyButtonColor) ?? \"brand\"]\n          : undefined,\n        className,\n      )}\n      render={render}\n      nativeButton={nativeButton ?? render === undefined}\n      {...props}\n    >\n      {renderButtonChildren(children)}\n    </ButtonPrimitive>\n  )\n}\n\n// ---------------------------------------------------------------------------\n// ToggleButton — a Button that flips its own pressed state on click.\n// ---------------------------------------------------------------------------\n//\n// Uncontrolled:  <ToggleButton defaultPressed>Mentions</ToggleButton>\n// Controlled:    <ToggleButton pressed={on} onPressedChange={setOn}>…</ToggleButton>\n//\n// `activeVariant` (default \"default\"/primary) renders when pressed;\n// `inactiveVariant` (default \"outline\") renders when not. `aria-pressed` is\n// wired automatically.\n\ntype ButtonVariantName = NonNullable<VariantProps<typeof buttonVariants>[\"variant\"]>\n\nexport interface ToggleButtonProps\n  extends Omit<ButtonPrimitive.Props, \"variant\"> {\n  /** Controlled pressed state. Pair with `onPressedChange`. */\n  pressed?: boolean\n  /** Uncontrolled initial pressed state. */\n  defaultPressed?: boolean\n  /** Fires with the next pressed state on click. */\n  onPressedChange?: (pressed: boolean) => void\n  /** Variant rendered while pressed. Defaults to `default` (primary). */\n  activeVariant?: ButtonVariantName\n  /** Variant rendered while not pressed. Defaults to `outline`. */\n  inactiveVariant?: ButtonVariantName\n  size?: VariantProps<typeof buttonVariants>[\"size\"]\n}\n\nfunction ToggleButton({\n  pressed: pressedProp,\n  defaultPressed = false,\n  onPressedChange,\n  activeVariant = \"default\",\n  inactiveVariant = \"outline\",\n  onClick,\n  ...props\n}: ToggleButtonProps) {\n  const isControlled = pressedProp !== undefined\n  const [internal, setInternal] = React.useState(defaultPressed)\n  const pressed = isControlled ? pressedProp : internal\n\n  return (\n    <Button\n      variant={pressed ? activeVariant : inactiveVariant}\n      aria-pressed={pressed}\n      onClick={(e) => {\n        if (!isControlled) setInternal((p) => !p)\n        onPressedChange?.(!pressed)\n        onClick?.(e)\n      }}\n      {...props}\n    />\n  )\n}\n\n// ---------------------------------------------------------------------------\n// SplitButton — flat prop-driven Button + chevron dropdown.\n// ---------------------------------------------------------------------------\n//\n// Two modes:\n//\n//   1) Actions menu:\n//      <SplitButton\n//        label=\"Save\"\n//        onClick={save}\n//        items={[\n//          { label: \"Duplicate\", icon: CopyIcon, onClick },\n//          { label: \"Archive\",   icon: ArchiveIcon, onClick },\n//          { label: \"Delete\",    icon: TrashIcon, destructive: true, onClick },\n//        ]}\n//      />\n//\n//   2) Selectable (radio):\n//      <SplitButton\n//        options={[{ label, description? }, …]}\n//        defaultValue={0}\n//        onAction={(opt, i) => …}\n//      />\n\ntype LucideIconComponent = React.ComponentType<{ className?: string }>\n\nexport type SplitButtonItem = {\n  label: React.ReactNode\n  icon?: LucideIconComponent\n  onClick?: () => void\n  disabled?: boolean\n  /** Renders the item with destructive tone. */\n  destructive?: boolean\n  key?: React.Key\n}\n\nexport type SplitButtonOption = {\n  label: React.ReactNode\n  description?: React.ReactNode\n  disabled?: boolean\n}\n\ntype ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>[\"variant\"]>\ntype ButtonSize = NonNullable<VariantProps<typeof buttonVariants>[\"size\"]>\n\nexport interface SplitButtonProps {\n  /** Primary button label. Ignored when `options` is set. */\n  label?: React.ReactNode\n  /** Primary button click handler. */\n  onClick?: () => void\n  /** Action items shown in the chevron dropdown (actions-menu mode). */\n  items?: SplitButtonItem[]\n  /** Radio options. When set, the primary button's label is the active option. */\n  options?: SplitButtonOption[]\n  /** Initial selected index in selectable mode. */\n  defaultValue?: number\n  /** Controlled selected index. */\n  value?: number\n  /** Fires when the selected index changes (selectable mode). */\n  onValueChange?: (index: number) => void\n  /** Fires when the primary button is activated (selectable mode). */\n  onAction?: (option: SplitButtonOption, index: number) => void\n  variant?: ButtonVariant\n  size?: ButtonSize\n  /** Accessible label for the chevron trigger. */\n  chevronLabel?: string\n  disabled?: boolean\n  className?: string\n}\n\nfunction SplitButton({\n  label,\n  onClick,\n  items,\n  options,\n  defaultValue = 0,\n  value,\n  onValueChange,\n  onAction,\n  variant,\n  size,\n  chevronLabel = \"More actions\",\n  disabled,\n  className,\n}: SplitButtonProps) {\n  const [internal, setInternal] = React.useState<number>(defaultValue)\n  const selectedIndex = value ?? internal\n\n  const selectable = !!options\n  const activeOption =\n    selectable && options\n      ? options[Math.min(Math.max(selectedIndex, 0), options.length - 1)]\n      : undefined\n\n  const handleSelectChange = (next: string) => {\n    const i = Number(next)\n    if (value === undefined) setInternal(i)\n    onValueChange?.(i)\n  }\n\n  const handlePrimary = () => {\n    if (selectable && activeOption) onAction?.(activeOption, selectedIndex)\n    onClick?.()\n  }\n\n  return (\n    <div\n      data-slot=\"split-button\"\n      className={cn(\n        \"squad-ui inline-flex w-fit overflow-hidden rounded-full shadow-xs\",\n        className,\n      )}\n    >\n      <Button\n        type=\"button\"\n        variant={variant}\n        size={size}\n        disabled={disabled}\n        onClick={handlePrimary}\n        className=\"squad-ui rounded-none rounded-l-full border-0 focus-visible:z-10\"\n      >\n        {selectable ? activeOption?.label : label}\n      </Button>\n      <DropdownMenu>\n        <DropdownMenuTrigger\n          render={\n            <Button\n              type=\"button\"\n              variant={variant}\n              size={size ?? \"icon\"}\n              aria-label={chevronLabel}\n              disabled={disabled}\n              className=\"squad-ui rounded-none rounded-r-full border-0 shadow-[inset_1px_0_0_rgba(255,255,255,0.28)] focus-visible:z-10\"\n            >\n              <ChevronDownIcon />\n            </Button>\n          }\n        />\n        <DropdownMenuContent align=\"end\">\n          {selectable && options ? (\n            <DropdownMenuRadioGroup\n              value={String(selectedIndex)}\n              onValueChange={handleSelectChange}\n            >\n              {options.map((opt, i) => (\n                <DropdownMenuRadioItem\n                  key={i}\n                  value={String(i)}\n                  disabled={opt.disabled}\n                >\n                  <div className=\"squad-ui flex flex-col gap-0.5\">\n                    <span className=\"squad-ui font-medium\">{opt.label}</span>\n                    {opt.description && (\n                      <span className=\"squad-ui text-xs text-muted-foreground\">\n                        {opt.description}\n                      </span>\n                    )}\n                  </div>\n                </DropdownMenuRadioItem>\n              ))}\n            </DropdownMenuRadioGroup>\n          ) : (\n            <DropdownMenuGroup>\n              {(items ?? []).map((it, i) => {\n                const Icon = it.icon\n                return (\n                  <DropdownMenuItem\n                    key={it.key ?? i}\n                    onClick={it.onClick}\n                    disabled={it.disabled}\n                    variant={it.destructive ? \"destructive\" : undefined}\n                  >\n                    {Icon && <Icon />}\n                    {it.label}\n                  </DropdownMenuItem>\n                )\n              })}\n            </DropdownMenuGroup>\n          )}\n        </DropdownMenuContent>\n      </DropdownMenu>\n    </div>\n  )\n}\n\nexport { Button, ToggleButton, SplitButton, buttonVariants }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}