{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox",
  "title": "Checkbox",
  "description": "A yes/no choice for forms, with a checked, unchecked and indeterminate state and a springy tick.",
  "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/checkbox.tsx",
      "content": "\"use client\";\n\nimport { Checkbox as BaseCheckbox } from \"@base-ui/react/checkbox\";\nimport { useId, useState } from \"react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { cn, focusRing } from \"@/components/ikada/lib/cn\";\nimport { CheckIcon, MinusIcon } from \"@/components/ikada/lib/icons\";\n\nexport interface CheckboxProps extends Omit<ComponentProps<typeof BaseCheckbox.Root>, \"className\" | \"children\"> {\n  className?: string;\n  /** Visible label. Without one, pass aria-label. */\n  label?: ReactNode;\n  /** Muted line under the label, linked to the checkbox as its description. */\n  description?: ReactNode;\n  /** Marks the checkbox invalid: danger ring and aria-invalid. */\n  invalid?: boolean;\n}\n\n/**\n * A yes/no choice in a form. The box bobs once as it fills (only on a change, never on mount) and\n * the checked fill is solid green, 3:1 against the unchecked box and the surface in both themes.\n */\nexport function Checkbox({\n  label,\n  description,\n  invalid,\n  className,\n  id,\n  ref,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const autoId = useId();\n  const controlId = id ?? autoId;\n  const [bob, setBob] = useState(false);\n  const box = (\n    <BaseCheckbox.Root\n      data-slot=\"checkbox\"\n      ref={ref}\n      id={controlId}\n      // A <label> does not name a role=\"checkbox\" span, so name it by reference.\n      aria-labelledby={label ? `${controlId}-label` : undefined}\n      aria-describedby={label && description ? `${controlId}-description` : undefined}\n      aria-invalid={invalid || undefined}\n      data-sb-bob={bob ? \"\" : undefined}\n      onCheckedChange={(checked, details) => {\n        if (checked) setBob(true);\n        onCheckedChange?.(checked, details);\n      }}\n      onAnimationEnd={(event) => {\n        if (event.animationName === \"sb-bob\") setBob(false);\n      }}\n      className={cn(\n        \"mt-px inline-flex size-4.5 shrink-0 items-center justify-center rounded-xs bg-well text-on-brand\",\n        \"data-sb-bob:animate-[sb-bob_var(--sb-duration-spring-bob)_var(--sb-spring-bob)]\",\n        \"shadow-[inset_0_0_0_1.5px_var(--sb-control-edge)]\",\n        \"transition-[background-color,box-shadow] duration-(--sb-duration-hover) ease-hover\",\n        \"hover:shadow-[inset_0_0_0_1.5px_var(--sb-fg-muted)]\",\n        \"data-checked:bg-brand data-checked:shadow-none data-indeterminate:bg-brand data-indeterminate:shadow-none\",\n        \"data-disabled:cursor-not-allowed data-disabled:opacity-50\",\n        \"aria-invalid:shadow-[inset_0_0_0_1.5px_var(--sb-danger-dot)]\",\n        \"relative before:absolute before:-inset-3 before:content-[''] pointer-fine:before:hidden\",\n        focusRing,\n        // Keyboard toggling runs the bob and the focus wake together.\n        \"data-sb-bob:focus-visible:[animation:sb-bob_var(--sb-duration-spring-bob)_var(--sb-spring-bob),sb-wake_var(--sb-duration-wake)_var(--ease-out)]\",\n        !label && className,\n      )}\n      {...props}\n    >\n      <BaseCheckbox.Indicator\n        className={cn(\n          \"flex [transition:opacity_var(--sb-duration-hover)_var(--ease-out),scale_var(--sb-duration-spring-bob)_var(--sb-spring-bob)]\",\n          \"data-ending-style:[transition:opacity_var(--sb-duration-hover)_var(--ease-out),scale_var(--sb-duration-hover)_var(--ease-out)]\",\n          \"data-starting-style:scale-(--sb-scale-icon) data-starting-style:opacity-0\",\n          \"data-ending-style:scale-(--sb-scale-icon) data-ending-style:opacity-0\",\n        )}\n        keepMounted={false}\n      >\n        <IndicatorIcon indeterminate={props.indeterminate} />\n      </BaseCheckbox.Indicator>\n    </BaseCheckbox.Root>\n  );\n  if (!label) return box;\n  return (\n    <label\n      htmlFor={controlId}\n      className={cn(\"inline-flex max-w-full items-start gap-2.5 text-sb-base text-fg\", className)}\n    >\n      {box}\n      <span className=\"flex min-w-0 flex-col [overflow-wrap:anywhere]\">\n        <span id={`${controlId}-label`}>{label}</span>\n        {description ? (\n          <span id={`${controlId}-description`} className=\"text-pretty text-sb-sm text-fg-muted\">\n            {description}\n          </span>\n        ) : null}\n      </span>\n    </label>\n  );\n}\n\nfunction IndicatorIcon({ indeterminate }: { indeterminate?: boolean }) {\n  return indeterminate ? <MinusIcon className=\"size-3.5\" /> : <CheckIcon className=\"size-3.5\" />;\n}\n",
      "type": "registry:ui",
      "target": "@components/ikada/checkbox.tsx"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}