{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox",
  "type": "registry:ui",
  "title": "Checkbox",
  "description": "여러 항목을 독립적으로 켜고 끌 수 있게 하는 선택 컴포넌트입니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-context",
    "@atom/lib-field-context",
    "@atom/lib-use-controllable-state"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/checkbox.tsx",
      "content": "\"use client\";\n\nimport {\n  Children,\n  type HTMLAttributes,\n  type InputHTMLAttributes,\n  type ReactNode,\n  type Ref,\n  isValidElement,\n  useEffect,\n  useRef,\n} from \"react\";\nimport { mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { createStrictContext } from \"../lib/context\";\nimport { useFieldControl } from \"../lib/field-context\";\nimport { useControllableState } from \"../lib/use-controllable-state\";\n\n/* -------------------------------------------------------------------------- */\n/*  Checkbox (components/checkbox.md, styles/atom/elements/checkbox.css)      */\n/*    <Checkbox description=\"선택 항목이에요\">마케팅 정보 수신 동의</Checkbox>   */\n/*    <Checkbox checked=\"indeterminate\" onCheckedChange={toggleAll}>전체 선택</Checkbox> */\n/*    <CheckboxGroup aria-label=\"상태\" value={value} onValueChange={setValue}>  */\n/*      <Checkbox value=\"active\">사용중</Checkbox>                             */\n/*      <Checkbox value=\"idle\">                                                */\n/*        <Checkbox.Label>미사용</Checkbox.Label>                              */\n/*        <Checkbox.Description>30일 이상 접속 없음</Checkbox.Description>      */\n/*      </Checkbox>                                                           */\n/*    </CheckboxGroup>                                                        */\n/* -------------------------------------------------------------------------- */\n\nexport type CheckboxSize = \"small\" | \"medium\" | \"large\";\nexport type CheckboxChecked = boolean | \"indeterminate\";\nexport type CheckboxGroupLayout = \"vertical\" | \"horizontal\";\n\nconst B = \"atom-checkbox\";\nconst G = \"atom-checkbox-group\";\n\nconst [CheckboxContext, useCheckboxContext] = createStrictContext<true>(\"Checkbox\");\n\ntype GroupContext = {\n  name: string | undefined;\n  value: string[];\n  toggle: (value: string, checked: boolean) => void;\n  disabled: boolean;\n  invalid: boolean;\n};\nconst [CheckboxGroupContext, , useCheckboxGroupContext] = createStrictContext<GroupContext>(\"CheckboxGroup\");\n\n/** 원본 데모의 체크 글리프 (스프라이트가 아니라 viewBox 16 인라인 svg) */\nfunction CheckGlyph() {\n  return (\n    <svg className={`${B}__check`} viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\">\n      <path\n        d=\"m3 8.5 3.2 3.2L13 5\"\n        fill=\"none\"\n        stroke=\"currentColor\"\n        strokeWidth=\"2.2\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n    </svg>\n  );\n}\n\nexport type CheckboxProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"size\" | \"checked\" | \"defaultChecked\" | \"type\"> & {\n  ref?: Ref<HTMLInputElement>;\n  /** 선택 상태입니다. */\n  checked?: CheckboxChecked;\n  /** uncontrolled 로 쓸 때의 처음 선택 상태입니다. */\n  defaultChecked?: CheckboxChecked;\n  /** Control 크기와 폰트 크기를 결정합니다. */\n  size?: CheckboxSize;\n  /** Control 테두리를 오류 색으로 바꿉니다. 오류 메시지는 Field 가 보여줍니다. */\n  invalid?: boolean;\n  /** 조작할 수 없게 만듭니다. */\n  disabled?: boolean;\n  /** 라벨 아래에 놓이는 보충 설명입니다. */\n  description?: ReactNode;\n  /** 선택 상태가 바뀔 때 호출됩니다. */\n  onCheckedChange?: (checked: boolean) => void;\n  /** 라벨이 여러 줄일 때 Control 을 첫 줄에 맞춥니다 (`--align_top`). description 이 있으면 자동으로 켜집니다. */\n  alignTop?: boolean;\n};\n\n/** 라벨 · 설명 자식을 원본 구조로 배치합니다: 설명이 있으면 `__text` 로 묶고, 라벨은 `<span>` 입니다. */\nfunction layoutText(children: ReactNode, description: ReactNode, blockName: string, labelType: unknown, descType: unknown) {\n  const label: ReactNode[] = [];\n  const descriptions: ReactNode[] = [];\n  Children.forEach(children, (child) => {\n    if (child === null || child === undefined || child === false) return;\n    if (isValidElement(child) && child.type === descType) descriptions.push(child);\n    else label.push(child);\n  });\n  const labelNode =\n    label.length === 1 && isValidElement(label[0]) && label[0].type === labelType ? label[0] : label.length ? <span>{label}</span> : null;\n  const hasDescription = descriptions.length > 0 || (description !== undefined && description !== null);\n  if (!hasDescription) return { text: labelNode, hasDescription };\n  return {\n    hasDescription,\n    text: (\n      <span className={`${blockName}__text`}>\n        {labelNode}\n        {descriptions}\n        {description !== undefined && description !== null && (\n          <span className={`${blockName}__description`}>{description}</span>\n        )}\n      </span>\n    ),\n  };\n}\n\nfunction CheckboxRoot({\n  ref,\n  checked: checkedProp,\n  defaultChecked = false,\n  size = \"medium\",\n  invalid = false,\n  disabled = false,\n  description,\n  onCheckedChange,\n  alignTop,\n  value,\n  name,\n  id,\n  required,\n  className,\n  children,\n  onChange,\n  \"aria-describedby\": ariaDescribedBy,\n  ...rest\n}: CheckboxProps) {\n  const group = useCheckboxGroupContext();\n  // 그룹 안의 항목은 Field 의 controlId 를 쓰지 않습니다 (id 가 겹침). Field 배선은 그룹이 받습니다.\n  const fieldContext = useFieldControl();\n  const field = group ? null : fieldContext;\n  const groupValue = value === undefined ? undefined : String(value);\n\n  const [own, setOwn] = useControllableState<CheckboxChecked>({\n    value: group && groupValue !== undefined ? group.value.includes(groupValue) : checkedProp,\n    defaultValue: defaultChecked,\n    onChange: (next) => onCheckedChange?.(next === true),\n  });\n  const state = own === \"indeterminate\" ? \"indeterminate\" : own ? \"checked\" : \"unchecked\";\n\n  const isDisabled = disabled || !!group?.disabled || !!field?.disabled;\n  const isInvalid = invalid || !!group?.invalid || !!field?.invalid;\n  const describedBy =\n    cn(ariaDescribedBy, field?.describedBy) || undefined;\n\n  // indeterminate 는 클래스만으로 전달되지 않습니다 — input.indeterminate 를 설정해야 aria-checked=\"mixed\" 로 읽힙니다.\n  const inputRef = useRef<HTMLInputElement | null>(null);\n  useEffect(() => {\n    if (inputRef.current) inputRef.current.indeterminate = own === \"indeterminate\";\n  }, [own]);\n\n  const { text, hasDescription } = layoutText(children, description, B, CheckboxLabel, CheckboxDescription);\n\n  return (\n    <CheckboxContext value={true}>\n      <label\n        className={cn(\n          B,\n          mod(B, \"size\", size, \"medium\"),\n          (alignTop ?? hasDescription) && mod(B, \"align\", \"top\"),\n          className,\n        )}\n        data-state={state}\n        data-invalid={isInvalid ? \"\" : undefined}\n        data-disabled={isDisabled ? \"\" : undefined}\n      >\n        <input\n          {...rest}\n          ref={(node) => {\n            inputRef.current = node;\n            if (typeof ref === \"function\") ref(node);\n            else if (ref) ref.current = node;\n          }}\n          className={`${B}__input`}\n          type=\"checkbox\"\n          id={id ?? field?.controlId}\n          name={name ?? group?.name}\n          value={value}\n          required={required ?? field?.required}\n          checked={own === true}\n          disabled={isDisabled}\n          aria-invalid={isInvalid || undefined}\n          aria-describedby={describedBy}\n          onChange={(e) => {\n            onChange?.(e);\n            const next = e.currentTarget.checked;\n            if (group && groupValue !== undefined) group.toggle(groupValue, next);\n            setOwn(next);\n          }}\n        />\n        <span className={`${B}__control`}>\n          {state === \"indeterminate\" ? <span className={`${B}__indeterminate`} /> : <CheckGlyph />}\n        </span>\n        {text}\n      </label>\n    </CheckboxContext>\n  );\n}\n\n/** 라벨 텍스트 (`<span>`). 문자열 자식은 자동으로 이 자리에 들어가므로 Description 과 함께 쓸 때만 필요합니다. */\nfunction CheckboxLabel({ className, ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  useCheckboxContext(\"Checkbox.Label\");\n  return <span {...rest} className={className} />;\n}\n\n/** 라벨 아래 보충 설명 (`atom-checkbox__description`). invalid 이면 오류 색이 됩니다. */\nfunction CheckboxDescription({ className, ...rest }: HTMLAttributes<HTMLSpanElement>) {\n  useCheckboxContext(\"Checkbox.Description\");\n  return <span {...rest} className={cn(`${B}__description`, className)} />;\n}\n\nexport const Checkbox = Object.assign(CheckboxRoot, { Label: CheckboxLabel, Description: CheckboxDescription });\n\nexport type CheckboxGroupProps = Omit<HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"onChange\"> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 세로(기본) 또는 가로 배치입니다. 가로는 라벨이 아주 짧을 때만 씁니다. */\n  layout?: CheckboxGroupLayout;\n  /** 선택된 항목들의 value 입니다. */\n  value?: string[];\n  /** uncontrolled 로 쓸 때의 처음 선택 값입니다. */\n  defaultValue?: string[];\n  /** 선택 값이 바뀔 때 호출됩니다. */\n  onValueChange?: (value: string[]) => void;\n  /** 안의 체크박스가 함께 쓸 name 입니다. */\n  name?: string;\n  /** 그룹 전체를 조작할 수 없게 만듭니다. */\n  disabled?: boolean;\n  /** 그룹 전체를 오류 상태로 표시합니다. */\n  invalid?: boolean;\n};\n\n/**\n * 공통 질문이 있는 체크박스 묶음 (`atom-checkbox-group`, `role=\"group\"`).\n * `aria-label` / `aria-labelledby` 로 그룹 이름을 붙입니다. Field 안에서는 Field.Label 이 이름이 됩니다.\n */\nexport function CheckboxGroup({\n  ref,\n  layout = \"vertical\",\n  value: valueProp,\n  defaultValue = [],\n  onValueChange,\n  name,\n  disabled = false,\n  invalid = false,\n  className,\n  children,\n  ...rest\n}: CheckboxGroupProps) {\n  const field = useFieldControl();\n  const [value, setValue] = useControllableState({ value: valueProp, defaultValue, onChange: onValueChange });\n  const isInvalid = invalid || !!field?.invalid;\n  return (\n    <CheckboxGroupContext\n      value={{\n        name,\n        value,\n        toggle: (item, on) => setValue(on ? [...value.filter((v) => v !== item), item] : value.filter((v) => v !== item)),\n        disabled: disabled || !!field?.disabled,\n        invalid: isInvalid,\n      }}\n    >\n      <div\n        role=\"group\"\n        aria-labelledby={rest[\"aria-label\"] ? undefined : field?.labelId}\n        aria-describedby={field?.describedBy}\n        {...rest}\n        ref={ref}\n        id={rest.id ?? field?.controlId}\n        className={cn(G, mod(G, \"layout\", layout, \"vertical\"), className)}\n      >\n        {children}\n      </div>\n    </CheckboxGroupContext>\n  );\n}\n\n",
      "type": "registry:ui",
      "target": "components/ui/checkbox.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/checkbox.css",
      "content": "/* ============================================================================\n   Element — Checkbox\n   ----------------------------------------------------------------------------\n   atom-checkbox\n     --size_{small|medium|large}             medium is the default\n     --align_top                             label wraps to more than one line\n     [data-state=\"unchecked|checked|indeterminate\"]\n     [data-invalid]  [data-disabled]\n     __control  __check  __indeterminate  __label  __description\n\n   atom-checkbox-group  (+ --layout_horizontal)\n\n   Reads as a real control: <label> + a visually-hidden <input type=\"checkbox\">\n   also works, since :checked is wired up alongside the data-state attribute.\n   ========================================================================== */\n\n.atom-checkbox {\n  display: inline-flex;\n  align-items: center;\n  gap: 10px;\n  font-size: 15px;\n  line-height: 1.45;\n  color: var(--atom-fg-neutral);\n  cursor: pointer;\n}\n\n.atom-checkbox--align_top {\n  align-items: flex-start;\n}\n\n.atom-checkbox__input {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  opacity: 0;\n  pointer-events: none;\n}\n\n.atom-checkbox__control {\n  position: relative;\n  width: 22px;\n  height: 22px;\n  flex-shrink: 0;\n  border-radius: var(--atom-radius-r1);\n  background: var(--atom-bg-layer);\n  box-shadow: inset 0 0 0 1.5px var(--atom-border-default);\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease;\n}\n\n.atom-checkbox--align_top .atom-checkbox__control {\n  margin-top: 1px;\n}\n\n.atom-checkbox__check,\n.atom-checkbox__indeterminate {\n  position: absolute;\n  inset: 0;\n  margin: auto;\n  color: var(--atom-fg-on-solid);\n  opacity: 0;\n  transition: opacity var(--atom-duration-fast) ease;\n}\n\n.atom-checkbox__check {\n  width: 14px;\n  height: 14px;\n}\n\n.atom-checkbox__indeterminate {\n  width: 10px;\n  height: 2px;\n  border-radius: var(--atom-radius-full);\n  background: currentColor;\n}\n\n.atom-checkbox__text {\n  display: flex;\n  flex-direction: column;\n  gap: 2px;\n  min-width: 0;\n}\n\n.atom-checkbox__description {\n  font-size: 13px;\n  color: var(--atom-fg-neutral-muted);\n  word-break: keep-all;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-checkbox--size_small {\n  gap: 8px;\n  font-size: 14px;\n}\n\n.atom-checkbox--size_small .atom-checkbox__control {\n  width: 18px;\n  height: 18px;\n}\n\n.atom-checkbox--size_small .atom-checkbox__check {\n  width: 12px;\n  height: 12px;\n}\n\n.atom-checkbox--size_large {\n  gap: 12px;\n  font-size: 16px;\n}\n\n.atom-checkbox--size_large .atom-checkbox__control {\n  width: 26px;\n  height: 26px;\n  border-radius: 5px;\n}\n\n.atom-checkbox--size_large .atom-checkbox__check {\n  width: 17px;\n  height: 17px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-checkbox[data-state=\"checked\"] .atom-checkbox__control,\n.atom-checkbox[data-state=\"indeterminate\"] .atom-checkbox__control,\n.atom-checkbox__input:checked + .atom-checkbox__control {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: inset 0 0 0 1.5px var(--atom-bg-neutral-solid);\n}\n\n.atom-checkbox[data-state=\"checked\"] .atom-checkbox__check,\n.atom-checkbox__input:checked + .atom-checkbox__control .atom-checkbox__check {\n  opacity: 1;\n}\n\n.atom-checkbox[data-state=\"indeterminate\"] .atom-checkbox__indeterminate {\n  opacity: 1;\n}\n\n.atom-checkbox:hover:not([data-disabled]) .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-fg-neutral-subtle);\n}\n\n.atom-checkbox[data-state=\"checked\"]:hover:not([data-disabled])\n  .atom-checkbox__control,\n.atom-checkbox[data-state=\"indeterminate\"]:hover:not([data-disabled])\n  .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-bg-neutral-solid);\n  filter: brightness(0.9);\n}\n\n.atom-checkbox__input:focus-visible + .atom-checkbox__control {\n  outline: 2px solid var(--atom-primary-600);\n  outline-offset: 2px;\n}\n\n.atom-checkbox[data-invalid] .atom-checkbox__control {\n  box-shadow: inset 0 0 0 1.5px var(--atom-fg-critical);\n}\n\n.atom-checkbox[data-invalid] .atom-checkbox__description {\n  color: var(--atom-fg-critical);\n}\n\n.atom-checkbox[data-disabled] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* --- group --------------------------------------------------------------- */\n.atom-checkbox-group {\n  display: flex;\n  flex-direction: column;\n  gap: var(--atom-x4);\n}\n\n.atom-checkbox-group--layout_horizontal {\n  flex-direction: row;\n  flex-wrap: wrap;\n  gap: var(--atom-x5);\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/checkbox.css"
    }
  ],
  "categories": [
    "action-input"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "element",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "CheckboxGroup",
      "Checkbox",
      "Checkbox.Label",
      "Checkbox.Description"
    ],
    "docs": "poc/design-system/components/checkbox.md"
  }
}
