{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "button",
  "title": "Button",
  "description": "Solid button primitive using the same base-rhea styling as the site.",
  "dependencies": [
    "solid-js@^1.9.15",
    "class-variance-authority",
    "cn"
  ],
  "files": [
    {
      "path": "components/ui/button.tsx",
      "content": "import { cva } from \"class-variance-authority\";\nimport type { VariantProps } from \"class-variance-authority\";\n\nimport { createCompatEffect } from \"@/lib/solid/effect\";\nimport type { ButtonDOMProps } from \"@/lib/solid/jsx-types\";\nimport { omitProps } from \"@/lib/solid/props\";\nimport { setRefValue } from \"@/lib/solid/ref\";\nimport type { RefTarget } from \"@/lib/solid/ref\";\nimport { cn } from \"@/lib/utils\";\n\nexport const buttonVariants = cva(\n  \"group/button focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 inline-flex shrink-0 items-center justify-center rounded-2xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:ring-3 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:ring-3 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n  {\n    defaultVariants: {\n      size: \"default\",\n      variant: \"default\",\n    },\n    variants: {\n      size: {\n        default:\n          \"h-8 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5\",\n        icon: \"size-8\",\n        \"icon-lg\": \"size-9\",\n        \"icon-sm\": \"size-7\",\n        \"icon-xs\": \"size-6 [&_svg:not([class*='size-'])]:size-3\",\n        lg: \"h-9 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3\",\n        sm: \"h-7 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2\",\n        xs: \"h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3\",\n      },\n      variant: {\n        default: \"bg-primary text-primary-foreground hover:bg-primary/80\",\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        ghost:\n          \"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50\",\n        link: \"text-primary underline-offset-4 hover:underline\",\n        outline:\n          \"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-input/30 dark:bg-transparent\",\n        secondary:\n          \"bg-secondary text-secondary-foreground aria-expanded:bg-secondary aria-expanded:text-secondary-foreground hover:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]\",\n      },\n    },\n  }\n);\n\nexport type ButtonProps = Omit<\n  ButtonDOMProps,\n  \"children\" | \"class\" | \"className\" | \"onClick\" | \"ref\" | \"tabIndex\" | \"type\"\n> &\n  VariantProps<typeof buttonVariants> & {\n    class?: string;\n    className?: string;\n    children?: ButtonDOMProps[\"children\"];\n    ref?: RefTarget<HTMLButtonElement>;\n    type?: \"button\" | \"submit\" | \"reset\";\n    onClick?: (event: MouseEvent) => void;\n    tabIndex?: number;\n  };\n\nconst OWN_PROPS = [\n  \"class\",\n  \"className\",\n  \"children\",\n  \"ref\",\n  \"size\",\n  \"tabIndex\",\n  \"type\",\n  \"variant\",\n] as const;\n\nexport const Button = (props: ButtonProps) => {\n  const rest = omitProps(props, OWN_PROPS);\n  let element: HTMLButtonElement | undefined;\n\n  createCompatEffect(\n    () => props.tabIndex ?? 0,\n    (tabIndex) => {\n      if (element) {\n        element.tabIndex = tabIndex;\n      }\n    }\n  );\n\n  return (\n    <button\n      class={cn(\n        buttonVariants({\n          className: cn(props.class, props.className),\n          size: props.size ?? \"default\",\n          variant: props.variant ?? \"default\",\n        })\n      )}\n      data-slot=\"button\"\n      ref={(node) => {\n        element = node;\n        node.tabIndex = props.tabIndex ?? 0;\n        setRefValue(props.ref, node);\n      }}\n      type={props.type ?? \"button\"}\n      {...rest}\n    >\n      {props.children}\n    </button>\n  );\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "lib/solid/jsx-types.ts",
      "content": "import type { JSX } from \"solid-js/jsx-runtime\";\n\nexport type ButtonDOMProps = JSX.ButtonHTMLAttributes<HTMLButtonElement>;\n\nexport type DivDOMProps = JSX.HTMLAttributes<HTMLDivElement>;\n\nexport type CanvasDOMProps = JSX.IntrinsicElements[\"canvas\"];\n\nexport type ImageDOMProps = JSX.IntrinsicElements[\"img\"];\n\nexport type ListDOMProps = JSX.IntrinsicElements[\"ul\"];\n\nexport type ListItemDOMProps = JSX.IntrinsicElements[\"li\"];\n\nexport type KbdDOMProps = JSX.IntrinsicElements[\"kbd\"];\n\nexport type LabelDOMProps = JSX.IntrinsicElements[\"label\"];\n\nexport type FieldSetDOMProps = JSX.IntrinsicElements[\"fieldset\"];\n\nexport type LegendDOMProps = JSX.IntrinsicElements[\"legend\"];\n\nexport type GroupDOMProps = JSX.IntrinsicElements[\"g\"];\n\nexport type HeadingDOMProps = JSX.HTMLAttributes<HTMLHeadingElement>;\n\nexport type InputDOMProps = JSX.InputHTMLAttributes<HTMLInputElement>;\n\nexport type LineDOMProps = JSX.IntrinsicElements[\"line\"];\n\nexport type ParagraphDOMProps = JSX.HTMLAttributes<HTMLParagraphElement>;\n\nexport type PathDOMProps = JSX.IntrinsicElements[\"path\"];\n\nexport type SpanDOMProps = JSX.HTMLAttributes<HTMLSpanElement>;\n\nexport type SvgDOMProps = JSX.SvgSVGAttributes<SVGSVGElement>;\n\nexport type JSXElement = JSX.Element;\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/solid/props.ts",
      "content": "/**\n * Copies enumerable string props as live getters without carrying framework\n * symbols across a DOM boundary.\n */\nexport const forwardProps = <Target extends {}, Source extends {}>(\n  target: Target,\n  source: Source,\n  omitted: ReadonlySet<PropertyKey> = new Set()\n): void => {\n  for (const key of Object.keys(source)) {\n    if (omitted.has(key)) {\n      continue;\n    }\n\n    Object.defineProperty(target, key, {\n      configurable: true,\n      enumerable: true,\n      get: () => {\n        // SAFETY: Object.keys only returns own string keys that exist on Source.\n        return source[key as keyof Source];\n      },\n    });\n  }\n};\n\n/**\n * Returns a reactive plain-object view of `props` without the listed keys.\n *\n * Solid 2 marks its props objects with internal symbols. A Proxy forwards\n * those symbols and lets DOM spread unwrap the original props, which leaks\n * component-only fields as attributes. Copying only string keys as getters\n * keeps values live without carrying framework internals across the boundary.\n */\nexport const omitProps = <T extends {}, K extends keyof T>(\n  props: T,\n  keys: readonly K[]\n): Omit<T, K> => {\n  const omitted = new Set<PropertyKey>(keys);\n\n  // SAFETY: forwardProps writes only T keys not present in the omitted set.\n  const rest = {} as Omit<T, K>;\n\n  forwardProps(rest, props, omitted);\n\n  return rest;\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/solid/ref.ts",
      "content": "export interface MutableRef<T> {\n  current: T;\n}\n\nexport type RefTarget<T> =\n  | ((value: T | null) => void)\n  | { current: T | null }\n  | RefTarget<T>[]\n  | null\n  | undefined;\n\n/** Writes a DOM node or imperative handle to the ref shapes audiocn accepts. */\nexport const setRefValue = <T>(ref: RefTarget<T>, value: T | null): void => {\n  if (!ref) {\n    return;\n  }\n\n  if (Array.isArray(ref)) {\n    for (const item of ref) {\n      setRefValue(item, value);\n    }\n\n    return;\n  }\n\n  if (\"current\" in ref) {\n    ref.current = value;\n\n    return;\n  }\n\n  ref(value);\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/solid/effect.ts",
      "content": "import * as Solid from \"solid-js\";\n\ntype Cleanup = void | (() => void);\n\ntype EffectPayload<T> = () => T;\n\nconst hasTwoPhaseEffects = \"onSettled\" in Solid;\n\n/**\n * Runs a tracked compute phase followed by an untracked imperative apply phase.\n *\n * Both Solid versions accept this two-argument call. Solid 1 treats the second\n * function as the initial previous value, while Solid 2 treats it as the apply\n * phase. Solid 1 therefore applies inside the tracked callback after compute;\n * Solid 2 applies through its native second phase.\n */\nexport const createCompatEffect = <T>(\n  compute: () => T,\n  apply: (value: T) => Cleanup\n): void => {\n  const computePayload = (): EffectPayload<T> => {\n    const value = compute();\n\n    if (!hasTwoPhaseEffects) {\n      const cleanup = Solid.untrack(() => apply(value));\n\n      if (cleanup) {\n        Solid.onCleanup(cleanup);\n      }\n    }\n\n    return () => value;\n  };\n\n  const applyPayload = (payload: EffectPayload<T>): Cleanup =>\n    Solid.untrack(() => apply(payload()));\n\n  Solid.createEffect(computePayload, applyPayload);\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/utils.ts",
      "content": "export { cn } from \"cn\";\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/audiocn-license.txt",
      "content": "Audio component source adapted from https://github.com/audiocn/ui.\nMIT License — Copyright (c) 2026 OrcDev\n\nRegular SVG icon geometry adapted from @phosphor-icons/react 2.1.10.\nMIT License — Copyright (c) 2020 Phosphor Icons\n\nThe following permission and warranty terms apply to both notices.\nRetain this file with copies or substantial portions of the source.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
      "type": "registry:file",
      "target": "@lib/audiocn-license.txt"
    }
  ],
  "type": "registry:ui"
}
