{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "choicebox",
  "title": "Choicebox",
  "description": "A group of selectable cards for single or multiple choice.",
  "registryDependencies": [
    "@tetra-ui/checkbox",
    "@tetra-ui/input",
    "@tetra-ui/radio",
    "@tetra-ui/stack"
  ],
  "files": [
    {
      "path": "ui/choicebox.tsx",
      "content": "import {\n  Children,\n  cloneElement,\n  createContext,\n  isValidElement,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\";\nimport {\n  type GestureResponderEvent,\n  type PressableProps,\n  Text,\n  View,\n} from \"react-native\";\nimport { cn } from \"../lib/utils\";\nimport { Checkbox } from \"./checkbox\";\nimport { InputPressable } from \"./input\";\nimport { Radio } from \"./radio\";\nimport { Stack, type StackProps } from \"./stack\";\n\n// Types\ntype ChoiceboxType = \"single\" | \"multiple\";\n\ntype ChoiceboxRootContextValue = {\n  type: ChoiceboxType;\n  disabled?: boolean;\n  invalid?: boolean;\n  isSelected: (value: string) => boolean;\n  toggle: (value: string) => void;\n};\n\ntype ChoiceboxItemContextValue = {\n  value: string;\n  selected: boolean;\n  disabled?: boolean;\n};\n\nexport type ChoiceboxProps = {\n  type?: ChoiceboxType;\n  /** When `type` is `single`, allow clearing the selected item */\n  clearable?: boolean;\n  value?: string | string[];\n  defaultValue?: string | string[];\n  onValueChange?: (value: string | string[] | undefined) => void;\n  disabled?: boolean;\n  invalid?: boolean;\n  direction?: StackProps[\"direction\"];\n  className?: string;\n  children: React.ReactNode;\n};\n\nexport type ChoiceboxItemProps = Omit<PressableProps, \"children\"> & {\n  value: string;\n  disabled?: boolean;\n  children: React.ReactNode;\n};\n\nexport type ChoiceboxItemHeaderProps = React.ComponentProps<typeof View>;\nexport type ChoiceboxItemTitleProps = React.ComponentProps<typeof Text>;\nexport type ChoiceboxItemDescriptionProps = React.ComponentProps<typeof Text>;\n\n// Context\nconst ChoiceboxRootContext = createContext<ChoiceboxRootContextValue | null>(\n  null\n);\n\nconst ChoiceboxItemContext = createContext<ChoiceboxItemContextValue | null>(\n  null\n);\n\nconst useChoiceboxRoot = () => {\n  const context = useContext(ChoiceboxRootContext);\n  if (!context) {\n    throw new Error(\"Choicebox components must be used within a Choicebox\");\n  }\n  return context;\n};\n\nconst useChoiceboxItem = () => {\n  const context = useContext(ChoiceboxItemContext);\n  if (!context) {\n    throw new Error(\n      \"ChoiceboxItem subcomponents must be used within a ChoiceboxItem\"\n    );\n  }\n  return context;\n};\n\n// Components\nexport const Choicebox = ({\n  type = \"single\",\n  clearable = false,\n  value: valueProp,\n  defaultValue,\n  onValueChange,\n  disabled,\n  invalid,\n  direction = \"column\",\n  className,\n  children,\n}: ChoiceboxProps) => {\n  const isControlled = valueProp !== undefined;\n\n  const [internalSingle, setInternalSingle] = useState<string | undefined>(\n    typeof defaultValue === \"string\" ? defaultValue : undefined\n  );\n  const [internalMultiple, setInternalMultiple] = useState<string[]>(\n    Array.isArray(defaultValue) ? defaultValue : []\n  );\n\n  const singleValue = isControlled\n    ? typeof valueProp === \"string\"\n      ? valueProp\n      : undefined\n    : internalSingle;\n\n  const multipleValues = useMemo(() => {\n    if (type !== \"multiple\") {\n      return new Set<string>();\n    }\n    if (isControlled) {\n      return new Set(Array.isArray(valueProp) ? valueProp : []);\n    }\n    return new Set(internalMultiple);\n  }, [type, isControlled, valueProp, internalMultiple]);\n\n  const isSelected = useCallback(\n    (itemValue: string) => {\n      if (type === \"multiple\") {\n        return multipleValues.has(itemValue);\n      }\n      return singleValue === itemValue;\n    },\n    [type, singleValue, multipleValues]\n  );\n\n  const toggle = useCallback(\n    (itemValue: string) => {\n      if (disabled) {\n        return;\n      }\n\n      if (type === \"multiple\") {\n        const next = new Set(multipleValues);\n        if (next.has(itemValue)) {\n          next.delete(itemValue);\n        } else {\n          next.add(itemValue);\n        }\n        const arr = [...next];\n        if (!isControlled) {\n          setInternalMultiple(arr);\n        }\n        onValueChange?.(arr);\n        return;\n      }\n\n      let next: string | undefined;\n      if (singleValue === itemValue) {\n        next = clearable ? undefined : itemValue;\n      } else {\n        next = itemValue;\n      }\n      if (!isControlled) {\n        setInternalSingle(next);\n      }\n      onValueChange?.(next);\n    },\n    [\n      type,\n      disabled,\n      multipleValues,\n      singleValue,\n      clearable,\n      isControlled,\n      onValueChange,\n    ]\n  );\n\n  const ctx = useMemo(\n    () => ({\n      disabled,\n      invalid,\n      isSelected,\n      toggle,\n      type,\n    }),\n    [type, disabled, invalid, isSelected, toggle]\n  );\n\n  return (\n    <ChoiceboxRootContext.Provider value={ctx}>\n      <Stack className={cn(\"w-full\", className)} direction={direction} gap=\"sm\">\n        {children}\n      </Stack>\n    </ChoiceboxRootContext.Provider>\n  );\n};\n\nexport const ChoiceboxItem = ({\n  value,\n  disabled: itemDisabled,\n  children,\n  className,\n  onPress,\n  ...props\n}: ChoiceboxItemProps) => {\n  const {\n    type,\n    disabled: rootDisabled,\n    invalid,\n    isSelected,\n    toggle,\n  } = useChoiceboxRoot();\n\n  const selected = isSelected(value);\n  const disabled = rootDisabled || itemDisabled;\n\n  const handlePress = useCallback(\n    (event: GestureResponderEvent) => {\n      if (disabled) {\n        return;\n      }\n\n      onPress?.(event);\n      toggle(value);\n    },\n    [disabled, onPress, toggle, value]\n  );\n\n  const itemCtx = useMemo(\n    () => ({\n      disabled,\n      selected,\n      value,\n    }),\n    [value, selected, disabled]\n  );\n\n  const accessibilityRole = type === \"multiple\" ? \"checkbox\" : \"radio\";\n\n  const content: React.ReactNode[] = [];\n  let indicator: React.ReactNode = null;\n\n  const getContentKey = (child: React.ReactElement) => {\n    if (child.type === ChoiceboxItemHeader) {\n      return `${value}-header`;\n    }\n\n    const typeName =\n      typeof child.type === \"function\" ? child.type.name : String(child.type);\n\n    return `${value}-${typeName || \"content\"}`;\n  };\n\n  Children.forEach(children, (child) => {\n    if (isValidElement(child) && child.type === ChoiceboxIndicator) {\n      indicator = child;\n      return;\n    }\n\n    if (typeof child === \"string\") {\n      content.push(\n        <ChoiceboxItemTitle key={`${value}-label`}>{child}</ChoiceboxItemTitle>\n      );\n      return;\n    }\n\n    if (isValidElement(child)) {\n      if (child.key === null) {\n        content.push(cloneElement(child, { key: getContentKey(child) }));\n        return;\n      }\n\n      content.push(child);\n    }\n  });\n\n  return (\n    <ChoiceboxItemContext.Provider value={itemCtx}>\n      <InputPressable\n        {...props}\n        accessibilityRole={accessibilityRole}\n        accessibilityState={{ checked: selected, disabled }}\n        className={cn(\n          \"items-start py-3\",\n          selected && \"border-primary\",\n          className\n        )}\n        disabled={disabled}\n        focused={selected}\n        invalid={invalid}\n        onPress={handlePress}\n      >\n        <View className=\"w-full flex-row items-start gap-3\">\n          <View className=\"min-w-0 flex-1\">{content}</View>\n          <View className=\"shrink-0\">\n            {indicator ?? <ChoiceboxIndicator />}\n          </View>\n        </View>\n      </InputPressable>\n    </ChoiceboxItemContext.Provider>\n  );\n};\n\nexport const ChoiceboxItemHeader = ({\n  className,\n  ...props\n}: ChoiceboxItemHeaderProps) => {\n  return (\n    <Stack className={cn(\"w-full min-w-0\", className)} gap=\"xs\" {...props} />\n  );\n};\n\nexport const ChoiceboxItemTitle = ({\n  className,\n  ...props\n}: ChoiceboxItemTitleProps) => {\n  return (\n    <Text\n      className={cn(\"font-semibold text-base text-foreground\", className)}\n      {...props}\n    />\n  );\n};\n\nexport const ChoiceboxItemDescription = ({\n  className,\n  ...props\n}: ChoiceboxItemDescriptionProps) => {\n  return (\n    <Text\n      className={cn(\"text-muted-foreground text-sm\", className)}\n      {...props}\n    />\n  );\n};\n\nexport const ChoiceboxIndicator = () => {\n  const { type, invalid } = useChoiceboxRoot();\n  const { selected } = useChoiceboxItem();\n\n  if (type === \"multiple\") {\n    return <Checkbox checked={selected} invalid={invalid} />;\n  }\n\n  return <Radio checked={selected} invalid={invalid} />;\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
