{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox",
  "title": "CheckboxField",
  "description": "Checkbox, checkbox-group, and tristate checkbox field for BuzzForm v2.",
  "dependencies": [
    "@base-ui/react"
  ],
  "registryDependencies": [
    "@buzzform/init",
    "checkbox",
    "field"
  ],
  "files": [
    {
      "path": "registry/shadcn/fields/checkbox.tsx",
      "content": "\"use client\";\n\nimport type { CheckboxField as CheckboxFieldDef } from \"@buildnbuzz/form-react\";\nimport { useDataField } from \"@buildnbuzz/form-react\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel,\n} from \"@/components/ui/field\";\n\ninterface CheckboxUi {\n  autoFocus?: boolean;\n  className?: string;\n  width?: string | number;\n  asterisk?: boolean;\n}\n\nexport function CheckboxField() {\n  const {\n    fieldApi,\n    field,\n    isDisabled,\n    isReadOnly,\n    isRequired,\n    label,\n    description,\n    errors,\n    isInvalid,\n    descriptionId,\n    errorId,\n    ariaDescribedBy,\n    handleChange,\n    handleBlur,\n  } = useDataField<CheckboxFieldDef>();\n\n  // Delegate to group renderer if hasMany\n  if (\"hasMany\" in field && field.hasMany) {\n    return <CheckboxGroupField />;\n  }\n\n  // Delegate to tristate renderer if tristate\n  if (\"tristate\" in field && field.tristate) {\n    return <TristateCheckboxField />;\n  }\n\n  const value = (fieldApi.state.value as boolean) ?? false;\n  const ui = field.ui as CheckboxUi | undefined;\n\n  const width = ui?.width;\n  const widthStyle = width\n    ? {\n        width: typeof width === \"number\" ? `${width}px` : width,\n        flex: \"0 1 auto\",\n      }\n    : undefined;\n\n  return (\n    <FieldGroup\n      data-field={fieldApi.name}\n      className={ui?.className}\n      style={widthStyle}\n    >\n      <Field\n        orientation=\"horizontal\"\n        data-invalid={isInvalid}\n        data-disabled={isDisabled}\n      >\n        <Checkbox\n          id={fieldApi.name}\n          checked={value}\n          onCheckedChange={(checked) => {\n            if (isReadOnly) return;\n            handleChange(!!checked);\n            handleBlur();\n          }}\n          disabled={isDisabled}\n          aria-invalid={isInvalid}\n          aria-describedby={ariaDescribedBy}\n          aria-readonly={isReadOnly}\n          autoFocus={ui?.autoFocus}\n        />\n\n        <FieldContent>\n          {label && (\n            <FieldLabel\n              htmlFor={fieldApi.name}\n              className=\"cursor-pointer m-0 font-normal inline\"\n            >\n              {label}\n              {isRequired && ui?.asterisk !== false && (\n                <span className=\"text-destructive\">&nbsp;*</span>\n              )}\n            </FieldLabel>\n          )}\n\n          {description && !isInvalid && (\n            <FieldDescription id={descriptionId}>\n              {description}\n            </FieldDescription>\n          )}\n\n          {isInvalid && <FieldError id={errorId} errors={errors} />}\n        </FieldContent>\n      </Field>\n    </FieldGroup>\n  );\n}\n\nimport { CheckboxGroupField } from \"./checkbox-group\";\nimport { TristateCheckboxField } from \"./tristate-checkbox\";\n",
      "type": "registry:component",
      "target": "components/buzzform/fields/checkbox.tsx"
    },
    {
      "path": "registry/shadcn/fields/checkbox-group.tsx",
      "content": "\"use client\";\n\nimport type { CheckboxGroupField as CheckboxGroupFieldDef } from \"@buildnbuzz/form-react\";\nimport { useDataField, useFieldOptions } from \"@buildnbuzz/form-react\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n  Field,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel,\n} from \"@/components/ui/field\";\nimport { cn } from \"@/lib/utils\";\n\ntype OptionGroupVariant = \"default\" | \"card\";\ntype OptionGroupDirection = \"vertical\" | \"horizontal\";\ntype OptionGroupColumns = 1 | 2 | 3 | 4 | string | number | undefined;\n\ninterface OptionUi {\n  description?: string;\n}\n\ninterface CheckboxGroupUi {\n  variant?: OptionGroupVariant;\n  direction?: OptionGroupDirection;\n  columns?: OptionGroupColumns;\n  card?: {\n    size?: \"sm\" | \"md\" | \"lg\";\n    bordered?: boolean;\n  };\n  autoFocus?: boolean;\n  className?: string;\n  width?: string | number;\n  asterisk?: boolean;\n}\n\nconst cardSizeClasses = {\n  sm: \"p-2.5 sm:p-3\",\n  md: \"p-3 sm:p-4\",\n  lg: \"p-4 sm:p-5\",\n} as const;\n\nfunction getGridColumnsClass(columns: OptionGroupColumns | undefined) {\n  if (columns === 2) return \"sm:grid-cols-2\";\n  if (columns === 3) return \"sm:grid-cols-2 md:grid-cols-3\";\n  if (columns === 4) return \"sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4\";\n  return undefined;\n}\n\nfunction getLayoutClass({\n  variant = \"default\",\n  direction = \"vertical\",\n  columns,\n}: {\n  variant?: OptionGroupVariant;\n  direction?: OptionGroupDirection;\n  columns?: OptionGroupColumns;\n}) {\n  const isCard = variant === \"card\";\n  const isHorizontal = direction === \"horizontal\";\n\n  if (isHorizontal && !columns)\n    return \"!flex flex-row flex-wrap gap-x-4 gap-y-2\";\n\n  const effectiveColumns =\n    isCard || isHorizontal ? (columns ?? (isCard ? 2 : undefined)) : undefined;\n\n  if (!effectiveColumns || effectiveColumns === 1) return \"flex flex-col gap-2\";\n\n  return cn(\"grid gap-2\", getGridColumnsClass(effectiveColumns));\n}\n\nexport function CheckboxGroupField() {\n  const {\n    fieldApi,\n    field,\n    isDisabled,\n    isReadOnly,\n    isRequired,\n    label,\n    description,\n    errors,\n    isInvalid,\n    descriptionId,\n    errorId,\n    ariaDescribedBy,\n    handleBlur,\n  } = useDataField<CheckboxGroupFieldDef>();\n\n  const { options, isLoading } = useFieldOptions(field.options);\n  const ui = field.ui as CheckboxGroupUi | undefined;\n\n  const selected = Array.isArray(fieldApi.state.value)\n    ? (fieldApi.state.value as string[])\n    : [];\n\n  const variant = ui?.variant ?? \"default\";\n  const direction = ui?.direction ?? \"vertical\";\n  const columns = ui?.columns;\n  const cardSize = ui?.card?.size ?? \"md\";\n  const cardBordered = ui?.card?.bordered ?? true;\n  const isCardVariant = variant === \"card\";\n  const isHorizontal = direction === \"horizontal\";\n  const layoutClass = getLayoutClass({ variant, direction, columns });\n\n  const width = ui?.width;\n  const widthStyle = width\n    ? {\n        width: typeof width === \"number\" ? `${width}px` : width,\n        flex: \"0 1 auto\",\n      }\n    : undefined;\n\n  const handleToggle = (val: string, checked: boolean) => {\n    if (isReadOnly) return;\n    const maxSelected = field.maxSelected;\n    const hasMax = typeof maxSelected === \"number\";\n    if (checked && hasMax && selected.length >= maxSelected) return;\n\n    const next = checked\n      ? [...selected, val]\n      : selected.filter((v) => v !== val);\n\n    fieldApi.handleChange(next);\n    handleBlur();\n  };\n\n  return (\n    <FieldGroup\n      data-field={fieldApi.name}\n      className={ui?.className}\n      style={widthStyle}\n    >\n      <div\n        className=\"flex flex-col gap-2\"\n        data-invalid={isInvalid}\n        data-disabled={isDisabled}\n      >\n        {label && (\n          <FieldLabel className=\"gap-1 items-baseline\">\n            {isRequired && ui?.asterisk !== false && (\n              <span className=\"text-destructive\">*</span>\n            )}\n            {label}\n          </FieldLabel>\n        )}\n\n        {description && !isInvalid && (\n          <FieldDescription id={descriptionId} className=\"-mt-1 mb-1\">\n            {description}\n          </FieldDescription>\n        )}\n\n        <div\n          className={layoutClass}\n          role=\"group\"\n          aria-describedby={ariaDescribedBy}\n        >\n          {isLoading ? (\n            <div className=\"py-2 text-sm text-muted-foreground animate-pulse\">\n              Loading options...\n            </div>\n          ) : (\n            options.map((opt, i) => {\n            const {\n              value: val,\n              label: optLabel,\n              disabled: optDisabled,\n              ui: optUi,\n            } = opt;\n            const optDesc = (optUi as OptionUi | undefined)?.description;\n            const checked = selected.includes(val);\n            const hasMax = typeof field.maxSelected === \"number\";\n            const atMax = hasMax && selected.length >= field.maxSelected!;\n            const isOptDisabled =\n              optDisabled || isDisabled || (atMax && !checked);\n            const id = `${fieldApi.name}-${i}`;\n\n            if (isCardVariant) {\n              const showDescription = optDesc && cardSize !== \"sm\";\n              return (\n                <label\n                  key={`${val}-${i}`}\n                  htmlFor={id}\n                  className={cn(\n                    \"cursor-pointer transition-all duration-150 rounded-lg flex items-start gap-3\",\n                    cardSizeClasses[cardSize],\n                    cardBordered && \"border\",\n                    cardBordered &&\n                      checked &&\n                      \"border-primary ring-1 ring-primary/20 bg-primary/5 dark:bg-primary/10\",\n                    cardBordered &&\n                      !checked &&\n                      \"border-border hover:border-foreground/20 hover:bg-accent/30\",\n                    !cardBordered && \"hover:bg-accent/50\",\n                    !cardBordered && checked && \"bg-accent\",\n                    isOptDisabled &&\n                      \"opacity-50 cursor-not-allowed pointer-events-none\",\n                  )}\n                  data-checked={checked}\n                  data-disabled={isOptDisabled}\n                >\n                  <Checkbox\n                    id={id}\n                    checked={checked}\n                    onCheckedChange={(next) => handleToggle(val, next === true)}\n                    disabled={isOptDisabled}\n                    className=\"shrink-0 mt-0.5\"\n                    autoFocus={ui?.autoFocus && i === 0}\n                  />\n                  <div className=\"flex flex-col gap-0.5 min-w-0 flex-1\">\n                    <div className=\"flex items-center gap-2\">\n                      <span\n                        className={cn(\n                          \"font-medium\",\n                          cardSize === \"lg\" ? \"text-base\" : \"text-sm\",\n                        )}\n                      >\n                        {optLabel}\n                      </span>\n                    </div>\n                    {showDescription && (\n                      <span\n                        className={cn(\n                          \"text-muted-foreground line-clamp-2\",\n                          cardSize === \"lg\" ? \"text-sm\" : \"text-xs\",\n                        )}\n                      >\n                        {optDesc}\n                      </span>\n                    )}\n                  </div>\n                </label>\n              );\n            }\n\n            return (\n              <Field\n                key={`${val}-${i}`}\n                orientation=\"horizontal\"\n                className={cn(\n                  \"items-center gap-2.5 space-y-0\",\n                  isHorizontal && !columns && \"w-auto\",\n                  isOptDisabled && \"opacity-50 cursor-not-allowed\",\n                )}\n              >\n                <Checkbox\n                  id={id}\n                  checked={checked}\n                  onCheckedChange={(next) => handleToggle(val, next === true)}\n                  disabled={isOptDisabled}\n                  autoFocus={ui?.autoFocus && i === 0}\n                />\n                <FieldLabel\n                  htmlFor={id}\n                  className={cn(\n                    \"font-normal cursor-pointer m-0 flex items-center gap-2 text-sm\",\n                    isOptDisabled && \"cursor-not-allowed\",\n                  )}\n                >\n                  {optLabel}\n                </FieldLabel>\n              </Field>\n            );\n            })\n          )}\n          {options.length === 0 && !isLoading && (\n            <div className=\"py-2 text-sm text-muted-foreground italic\">\n              No options available\n            </div>\n          )}\n        </div>\n\n        {isInvalid && <FieldError id={errorId} errors={errors} />}\n      </div>\n    </FieldGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/buzzform/fields/checkbox-group.tsx"
    },
    {
      "path": "registry/shadcn/fields/tristate-checkbox.tsx",
      "content": "\"use client\";\n\nimport type { TristateCheckboxField as TristateCheckboxFieldDef } from \"@buildnbuzz/form-react\";\nimport { useDataField } from \"@buildnbuzz/form-react\";\nimport { Checkbox } from \"../components/checkbox\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldGroup,\n  FieldLabel,\n} from \"@/components/ui/field\";\n\ninterface TristateCheckboxUi {\n  autoFocus?: boolean;\n  className?: string;\n  width?: string | number;\n  asterisk?: boolean;\n}\n\n/** Cycles null → true → false → null on each click. */\nfunction nextTristateValue(current: boolean | null): boolean | null {\n  if (current === null) return true;\n  if (current === true) return false;\n  return null;\n}\n\n/**\n * A tristate checkbox field component.\n *\n * Uses a registry Checkbox wrapper to support indeterminate state\n * with icon placeholders.\n */\nexport function TristateCheckboxField() {\n  const {\n    fieldApi,\n    field,\n    isDisabled,\n    isReadOnly,\n    isRequired,\n    label,\n    description,\n    errors,\n    isInvalid,\n    descriptionId,\n    errorId,\n    ariaDescribedBy,\n    handleChange,\n    handleBlur,\n  } = useDataField<TristateCheckboxFieldDef>();\n\n  const value = (fieldApi.state.value as boolean | null) ?? null;\n  const ui = field.ui as TristateCheckboxUi | undefined;\n\n  const width = ui?.width;\n  const widthStyle = width\n    ? {\n        width: typeof width === \"number\" ? `${width}px` : width,\n        flex: \"0 1 auto\",\n      }\n    : undefined;\n\n  // Base UI uses `checked` (boolean) + `indeterminate` (boolean) separately\n  const checked = value === true;\n  const indeterminate = value === null;\n\n  return (\n    <FieldGroup\n      data-field={fieldApi.name}\n      className={ui?.className}\n      style={widthStyle}\n    >\n      <Field\n        orientation=\"horizontal\"\n        data-invalid={isInvalid}\n        data-disabled={isDisabled}\n      >\n        <Checkbox\n          id={fieldApi.name}\n          checked={checked}\n          indeterminate={indeterminate}\n          onCheckedChange={() => {\n            if (isReadOnly) return;\n            handleChange(nextTristateValue(value));\n            handleBlur();\n          }}\n          disabled={isDisabled}\n          aria-invalid={isInvalid}\n          aria-describedby={ariaDescribedBy}\n          aria-readonly={isReadOnly}\n          autoFocus={ui?.autoFocus}\n          data-slot=\"checkbox\"\n        />\n\n        <FieldContent>\n          {label && (\n            <FieldLabel\n              htmlFor={fieldApi.name}\n              className=\"cursor-pointer m-0 font-normal inline\"\n            >\n              {label}\n              {isRequired && ui?.asterisk !== false && (\n                <span className=\"text-destructive\">&nbsp;*</span>\n              )}\n            </FieldLabel>\n          )}\n\n          {description && !isInvalid && (\n            <FieldDescription id={descriptionId}>\n              {description}\n            </FieldDescription>\n          )}\n\n          {isInvalid && <FieldError id={errorId} errors={errors} />}\n        </FieldContent>\n      </Field>\n    </FieldGroup>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/buzzform/fields/tristate-checkbox.tsx"
    },
    {
      "path": "registry/shadcn/components/checkbox.tsx",
      "content": "\"use client\";\n\nimport { Checkbox as CheckboxPrimitive } from \"@base-ui/react/checkbox\";\nimport { IconPlaceholder } from \"@/components/icon-placeholder\";\nimport { cn } from \"@/lib/utils\";\n\nexport type CheckboxProps = Omit<CheckboxPrimitive.Root.Props, \"checked\"> & {\n  checked?: boolean | \"indeterminate\";\n  indeterminate?: boolean;\n};\n\nexport function Checkbox({\n  className,\n  checked,\n  indeterminate,\n  ...props\n}: CheckboxProps) {\n  const isIndeterminate = indeterminate ?? checked === \"indeterminate\";\n  const normalizedChecked = checked === \"indeterminate\" ? false : checked;\n\n  return (\n    <CheckboxPrimitive.Root\n      data-slot=\"checkbox\"\n      checked={normalizedChecked}\n      indeterminate={isIndeterminate}\n      data-indeterminate={isIndeterminate ? \"\" : undefined}\n      className={cn(\n        \"border-input dark:bg-input/30 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary data-indeterminate:bg-primary data-indeterminate:text-primary-foreground dark:data-indeterminate:bg-primary data-indeterminate:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[4px] border transition-colors group-has-disabled/field:opacity-50 focus-visible:ring-[3px] aria-invalid:ring-[3px] peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50\",\n        className,\n      )}\n      {...props}\n    >\n      <CheckboxPrimitive.Indicator\n        data-slot=\"checkbox-indicator\"\n        className=\"[&>svg]:size-3.5 grid place-content-center text-current transition-none\"\n      >\n        {isIndeterminate ? (\n          <IconPlaceholder\n            hugeicons=\"MinusSignIcon\"\n            lucide=\"Minus\"\n            tabler=\"IconMinus\"\n            phosphor=\"Minus\"\n            remixicon=\"RiSubtractLine\"\n          />\n        ) : (\n          <IconPlaceholder\n            hugeicons=\"Tick02Icon\"\n            lucide=\"Check\"\n            tabler=\"IconCheck\"\n            phosphor=\"Check\"\n            remixicon=\"RiCheckLine\"\n          />\n        )}\n      </CheckboxPrimitive.Indicator>\n    </CheckboxPrimitive.Root>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/buzzform/components/checkbox.tsx"
    }
  ],
  "type": "registry:ui"
}