{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chip",
  "type": "registry:ui",
  "title": "Chip",
  "description": "목록을 좁히거나 속성을 토글하는 작은 알약 모양 컨트롤입니다. 네 가지 타입으로 동작합니다.",
  "registryDependencies": [
    "@atom/tokens",
    "@atom/icons",
    "@atom/lib-bem",
    "@atom/lib-cn",
    "@atom/lib-context",
    "@atom/lib-slot",
    "@atom/lib-use-controllable-state"
  ],
  "files": [
    {
      "path": "poc/design-system/implement/src/components/chip.tsx",
      "content": "\"use client\";\n\nimport { type HTMLAttributes, type InputHTMLAttributes, type ReactNode, type Ref } from \"react\";\nimport { flag, mod } from \"../lib/bem\";\nimport { cn } from \"../lib/cn\";\nimport { createStrictContext } from \"../lib/context\";\nimport { Slot } from \"../lib/slot\";\nimport { useControllableState } from \"../lib/use-controllable-state\";\nimport { CheckIcon, ChevronDownIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Chip (components/chip.md, styles/atom/elements/chip.css)                  */\n/*    <Chip>필터 초기화</Chip>                                                 */\n/*    <Chip variant=\"neutral-outline\" caret open={open}>분류</Chip>            */\n/*    <Chip type=\"toggle\" selected={on} onSelectedChange={setOn}>알림 받기</Chip> */\n/*    <ChipGroup role=\"radiogroup\" aria-label=\"정렬\" name=\"sort\">              */\n/*      <Chip type=\"radio\" selected={sort === \"new\"}                           */\n/*            onSelectedChange={() => setSort(\"new\")}>최신순</Chip>            */\n/*    </ChipGroup>                                                            */\n/* -------------------------------------------------------------------------- */\n\nexport type ChipType = \"button\" | \"toggle\" | \"radio\" | \"checkbox\";\nexport type ChipVariant = \"neutral-weak\" | \"neutral-outline\" | \"neutral-solid\";\nexport type ChipSize = \"small\" | \"medium\";\nexport type ChipGroupOverflow = \"wrap\" | \"scroll\";\n\nconst B = \"atom-chip\";\nconst G = \"atom-chip-group\";\n\n/** ChipGroup 이 radio · checkbox Chip 에 나눠 주는 name (없어도 Chip 은 단독으로 동작) */\nconst [ChipGroupContext, , useChipGroupContext] = createStrictContext<{ name: string | undefined }>(\"ChipGroup\");\n\nexport type ChipProps = Omit<HTMLAttributes<HTMLElement>, \"onClick\"> & {\n  ref?: Ref<HTMLElement>;\n  /** Chip 이 따르는 동작 계약입니다. 렌더되는 마크업이 함께 바뀝니다. */\n  type?: ChipType;\n  /** 라벨 텍스트입니다. */\n  children: ReactNode;\n  /** 선택되지 않았을 때의 생김새입니다. */\n  variant?: ChipVariant;\n  /** Chip 높이와 폰트 크기를 결정합니다. */\n  size?: ChipSize;\n  /** `button` 을 뺀 세 타입에서 씁니다. 켜지면 solid 로 채워집니다. */\n  selected?: boolean;\n  /** uncontrolled 로 쓸 때의 처음 선택 상태입니다 (`toggle` · `radio` · `checkbox`). */\n  defaultSelected?: boolean;\n  /** 선택됐을 때 앞에 체크 표시를 넣습니다. */\n  showCheck?: boolean;\n  /** 라벨 앞 아이콘입니다. */\n  prefixIcon?: ReactNode;\n  /** 라벨 뒤에 붙는 개수입니다. */\n  count?: number;\n  /** 메뉴를 여는 `button` 타입에 화살표를 답니다. */\n  caret?: boolean;\n  /** `caret` 이 있을 때 화살표를 뒤집습니다. */\n  open?: boolean;\n  /** 누를 수 없게 만듭니다. */\n  disabled?: boolean;\n  /** `radio` 타입에서 같은 그룹을 묶는 이름입니다. */\n  name?: string;\n  /** `radio` · `checkbox` 타입의 폼 값입니다. */\n  value?: string;\n  /** `button` 타입에서 호출됩니다. */\n  onClick?: () => void;\n  /** `toggle` · `radio` · `checkbox` 에서 상태가 바뀔 때 호출됩니다. */\n  onSelectedChange?: (selected: boolean) => void;\n  /** `radio` · `checkbox` 타입에서 실제 `<input>` 에 넘길 속성입니다. */\n  inputProps?: InputHTMLAttributes<HTMLInputElement>;\n};\n\n/** 목록을 좁히거나 속성을 토글하는 알약 모양 컨트롤. `type` 에 따라 `<button>` 또는 `<label>` + `<input>` 을 렌더합니다. */\nexport function Chip({\n  ref,\n  type = \"button\",\n  children,\n  variant = \"neutral-weak\",\n  size = \"medium\",\n  selected: selectedProp,\n  defaultSelected = false,\n  showCheck = false,\n  prefixIcon,\n  count,\n  caret = false,\n  open = false,\n  disabled = false,\n  name,\n  value,\n  onClick,\n  onSelectedChange,\n  inputProps,\n  className,\n  ...rest\n}: ChipProps) {\n  const group = useChipGroupContext();\n  const [selected, setSelected] = useControllableState({\n    value: selectedProp,\n    defaultValue: defaultSelected,\n    onChange: onSelectedChange,\n  });\n\n  const classes = cn(B, mod(B, \"type\", type), mod(B, \"variant\", variant), mod(B, \"size\", size, \"medium\"), className);\n  const check = showCheck && type !== \"button\" && <CheckIcon className={`${B}__check`} />;\n  const body = (\n    <>\n      {prefixIcon !== undefined && prefixIcon !== null && (\n        <Slot aria-hidden=\"true\" className={`${B}__prefix`}>\n          {prefixIcon}\n        </Slot>\n      )}\n      <span className={`${B}__label`}>{children}</span>\n      {count !== undefined && <span className={`${B}__count`}>{count}</span>}\n      {caret && type === \"button\" && <ChevronDownIcon className={`${B}__caret`} />}\n    </>\n  );\n\n  // radio · checkbox — <label> 안에 실제 <input>. 선택 표현은 CSS 의 :has(__input:checked) 가 맡습니다.\n  if (type === \"radio\" || type === \"checkbox\") {\n    const controlled = selectedProp !== undefined;\n    return (\n      <label\n        {...rest}\n        ref={ref as Ref<HTMLLabelElement>}\n        className={classes}\n        aria-disabled={disabled || undefined}\n      >\n        <input\n          {...inputProps}\n          className={cn(`${B}__input`, inputProps?.className)}\n          type={type}\n          name={name ?? group?.name}\n          value={value}\n          disabled={disabled}\n          {...(controlled ? { checked: selected } : { defaultChecked: defaultSelected })}\n          onChange={(e) => {\n            inputProps?.onChange?.(e);\n            setSelected(e.currentTarget.checked);\n          }}\n        />\n        {check}\n        {body}\n      </label>\n    );\n  }\n\n  // button · toggle — <button type=\"button\">. toggle 은 aria-pressed 로 상태를 알립니다.\n  return (\n    <button\n      {...rest}\n      ref={ref as Ref<HTMLButtonElement>}\n      type=\"button\"\n      className={classes}\n      disabled={disabled}\n      aria-pressed={type === \"toggle\" ? selected : undefined}\n      data-state={caret && type === \"button\" && open ? \"open\" : undefined}\n      onClick={() => {\n        if (type === \"toggle\") setSelected(!selected);\n        else onClick?.();\n      }}\n    >\n      {check}\n      {body}\n    </button>\n  );\n}\n\nexport type ChipGroupProps = HTMLAttributes<HTMLDivElement> & {\n  ref?: Ref<HTMLDivElement>;\n  /** 넘칠 때의 동작. `scroll` 은 한 줄을 유지하고 옆으로 넘기고, `wrap` 은 다음 줄로 넘깁니다. */\n  overflow?: ChipGroupOverflow;\n  /** 안의 `radio` · `checkbox` Chip 이 함께 쓸 name 입니다. radio 그룹에는 `role=\"radiogroup\"` 과 `aria-label` 을 답니다. */\n  name?: string;\n};\n\n/** 여러 Chip 을 한 줄로 늘어놓는 묶음 (`atom-chip-group`). */\nexport function ChipGroup({ ref, overflow, name, className, children, ...rest }: ChipGroupProps) {\n  return (\n    <ChipGroupContext value={{ name }}>\n      <div\n        {...rest}\n        ref={ref}\n        className={cn(G, flag(G, \"wrap\", overflow === \"wrap\"), flag(G, \"scroll\", overflow === \"scroll\"), className)}\n      >\n        {children}\n      </div>\n    </ChipGroupContext>\n  );\n}\n\n",
      "type": "registry:ui",
      "target": "components/ui/chip.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/chip.css",
      "content": "/* ============================================================================\n   Element — Chip\n   ----------------------------------------------------------------------------\n   One pill, four behaviours. What changes between them is not the look but the\n   contract: whether the chip holds a state at all, and if so whether that state\n   is exclusive within its group.\n\n     button     상태 없음. 누르면 액션이 일어난다\n     toggle     혼자서 켜고 끈다\n     radio      그룹 안에서 하나만\n     checkbox   그룹 안에서 여러 개\n\n   So `--type_*` is a documentation marker for the author, and the real\n   difference lives in the markup each type demands — see the Specification\n   table. The selected treatment is shared: whatever the unselected variant was,\n   a selected chip fills solid.\n\n   atom-chip\n     --type_{button|toggle|radio|checkbox}   button is the default\n     --variant_{neutral-weak|neutral-outline|neutral-solid}\n                                             neutral-weak is the default\n     --size_{small|medium}                   medium is the default\n     [data-selected]  [aria-pressed=\"true\"]  __input:checked\n     [disabled]  [aria-disabled=\"true\"]\n     __input     visually-hidden radio/checkbox behind a <label>\n     __prefix    leading icon\n     __label\n     __check     선택 표시. 넣으면 선택됐을 때만 보인다\n     __caret     메뉴를 여는 button 타입에서\n     __count     뒤에 붙는 개수\n\n   atom-chip-group  (+ --wrap  --scroll)\n   ========================================================================== */\n\n.atom-chip {\n  position: relative;\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  height: 33px;\n  padding-inline: var(--atom-x3);\n  border-radius: var(--atom-radius-full);\n  font-size: 13px;\n  line-height: 1;\n  white-space: nowrap;\n  cursor: pointer;\n  user-select: none;\n  transition: background-color var(--atom-duration-fast) ease,\n    box-shadow var(--atom-duration-fast) ease,\n    color var(--atom-duration-fast) ease;\n}\n\n/* radio and checkbox chips are a real input behind a label, so the control is\n   removed from view but not from the accessibility tree or the tab order. */\n.atom-chip__input {\n  position: absolute;\n  width: 1px;\n  height: 1px;\n  opacity: 0;\n  pointer-events: none;\n}\n\n.atom-chip__label {\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n\n.atom-chip__prefix {\n  width: 14px;\n  height: 14px;\n  flex-shrink: 0;\n  margin-inline-start: -2px;\n}\n\n.atom-chip__caret {\n  width: 12px;\n  height: 12px;\n  flex-shrink: 0;\n  margin-inline-end: -2px;\n  opacity: 0.7;\n  transition: transform var(--atom-duration-base) var(--atom-ease-out);\n}\n\n/* The check is opt-in and costs width, so it collapses instead of merely fading\n   — a row of chips that reserves space for a mark none of them shows reads as\n   badly aligned rather than as unselected. */\n.atom-chip__check {\n  width: 0;\n  height: 14px;\n  flex-shrink: 0;\n  opacity: 0;\n  transition: width var(--atom-duration-fast) ease,\n    opacity var(--atom-duration-fast) ease;\n}\n\n.atom-chip__count {\n  flex-shrink: 0;\n  font-variant-numeric: tabular-nums;\n  opacity: 0.7;\n}\n\n/* --- types — guardrails, not decoration ---------------------------------- */\n/* These modifiers mostly document which contract a chip follows; the real\n   difference is the markup each type demands. The two rules below are the\n   exceptions, and both exist to stop wrong markup from shipping a wrong signal.\n\n   A button chip holds no state, so a check on one can only mislead. */\n.atom-chip--type_button .atom-chip__check {\n  display: none;\n}\n\n/* A caret promises \"this opens a menu\". The stateful three change a value in\n   place, so a caret on them is a promise the chip does not keep. */\n.atom-chip--type_toggle .atom-chip__caret,\n.atom-chip--type_radio .atom-chip__caret,\n.atom-chip--type_checkbox .atom-chip__caret {\n  display: none;\n}\n\n/* --- variants — the unselected look -------------------------------------- */\n.atom-chip,\n.atom-chip--variant_neutral-weak {\n  background: var(--atom-bg-neutral-weak);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-outline {\n  background: transparent;\n  box-shadow: inset 0 0 0 1px var(--atom-border-default);\n  color: var(--atom-fg-neutral);\n}\n\n.atom-chip--variant_neutral-solid {\n  background: var(--atom-bg-neutral-solid);\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n/* --- selected — shared by toggle, radio and checkbox ---------------------- */\n/* One rule for three types: whatever the chip looked like unselected, selecting\n   it fills solid. Fill is the only difference a chip can carry that survives\n   being read at a glance in a scrolling row. */\n.atom-chip[data-selected],\n.atom-chip[aria-pressed=\"true\"],\n.atom-chip:has(.atom-chip__input:checked) {\n  background: var(--atom-bg-neutral-solid);\n  box-shadow: none;\n  color: var(--atom-fg-on-solid);\n  font-weight: 600;\n}\n\n.atom-chip[data-selected] .atom-chip__check,\n.atom-chip[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 14px;\n  opacity: 1;\n}\n\n/* --- sizes --------------------------------------------------------------- */\n.atom-chip--size_small {\n  height: 28px;\n  padding-inline: var(--atom-x2);\n  font-size: 12px;\n}\n\n.atom-chip--size_small .atom-chip__prefix,\n.atom-chip--size_small .atom-chip__check {\n  height: 12px;\n}\n\n.atom-chip--size_small[data-selected] .atom-chip__check,\n.atom-chip--size_small[aria-pressed=\"true\"] .atom-chip__check,\n.atom-chip--size_small:has(.atom-chip__input:checked) .atom-chip__check {\n  width: 12px;\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-chip:hover:not([disabled]):not([aria-disabled=\"true\"]) {\n  filter: brightness(0.96);\n}\n\n.atom-chip[data-state=\"open\"] .atom-chip__caret {\n  transform: rotate(180deg);\n}\n\n.atom-chip:focus-visible,\n.atom-chip:has(.atom-chip__input:focus-visible) {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: 2px;\n}\n\n.atom-chip[disabled],\n.atom-chip[aria-disabled=\"true\"] {\n  opacity: 0.4;\n  cursor: not-allowed;\n}\n\n/* --- group --------------------------------------------------------------- */\n.atom-chip-group {\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-width: 0;\n}\n\n.atom-chip-group--wrap {\n  flex-wrap: wrap;\n  row-gap: var(--atom-x2);\n}\n\n/* A single-line rail. The row keeps its height when it scrolls, which a wrapping\n   group cannot promise, so filter bars pinned under a header use this one. */\n.atom-chip-group--scroll {\n  flex-wrap: nowrap;\n  overflow-x: auto;\n  scrollbar-width: none;\n}\n\n.atom-chip-group--scroll::-webkit-scrollbar {\n  display: none;\n}\n\n.atom-chip-group--scroll > .atom-chip {\n  flex-shrink: 0;\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/chip.css"
    }
  ],
  "categories": [
    "display"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "element",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "Chip",
      "ChipGroup"
    ],
    "docs": "poc/design-system/components/chip.md"
  }
}
