{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tree",
  "type": "registry:ui",
  "title": "Tree",
  "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/tree.tsx",
      "content": "\"use client\";\n\nimport {\n  type CSSProperties,\n  type HTMLAttributes,\n  type KeyboardEvent,\n  type MouseEvent,\n  type ReactNode,\n  type Ref,\n  useId,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { flag } 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, ChevronRightIcon } from \"./icons\";\n\n/* -------------------------------------------------------------------------- */\n/*  Tree (components/tree.md, styles/atom/elements/tree.css)                  */\n/*    <Tree                                                                   */\n/*      aria-label=\"프로젝트\"                                                  */\n/*      items={[{ key: \"docs\", label: \"문서\", badge: 12, children: [...] }]}  */\n/*      defaultExpandedKeys={[\"docs\"]}                                        */\n/*      selectionMode=\"single\"                                                */\n/*      onSelectionChange={setKeys}                                           */\n/*    />                                                                      */\n/*  WAI-ARIA APG treeview: role tree / treeitem / group, roving tabindex.     */\n/*  깊이는 Item 의 인라인 --atom-tree-depth (클래스 사다리 없음).              */\n/* -------------------------------------------------------------------------- */\n\nexport type TreeSelectionMode = \"none\" | \"single\" | \"multiple\";\n\nexport type TreeNode = {\n  /** 노드 식별자 — selectedKeys · expandedKeys · disabledKeys 가 이 값을 씁니다. */\n  key: string;\n  /** 라벨 */\n  label: ReactNode;\n  /** 자식이 있으면 부모가 됩니다. */\n  children?: TreeNode[];\n  /** 라벨 앞 아이콘 슬롯 (atom-tree__icon) */\n  icon?: ReactNode;\n  /** 라벨 뒤 배지 슬롯 (atom-tree__badge) — 행을 열지 않고도 알아야 하는 개수 등 */\n  badge?: ReactNode;\n  /** 잘린 라벨의 전체 이름. 생략하면 문자열 라벨을 그대로 씁니다. */\n  title?: string;\n};\n\ntype Checked = \"true\" | \"false\" | \"mixed\";\n\ntype FlatNode = {\n  node: TreeNode;\n  depth: number;\n  parent: string | undefined;\n  posinset: number;\n  setsize: number;\n};\n\nconst B = \"atom-tree\";\n\ntype TreeContextValue = {\n  index: Map<string, FlatNode>;\n  expanded: Set<string>;\n  selected: Set<string>;\n  checked: Map<string, Checked>;\n  disabledKeys: Set<string>;\n  selectionMode: TreeSelectionMode;\n  disabled: boolean;\n  wrapLabel: boolean;\n  focusKey: string | undefined;\n  baseId: string;\n  itemRefs: Map<string, HTMLLIElement>;\n  toggleExpanded: (key: string) => void;\n  select: (key: string) => void;\n  focus: (key: string) => void;\n};\n\nconst [TreeContext, useTreeContext] = createStrictContext<TreeContextValue>(\"Tree\");\n\n/* -------------------------------- helpers --------------------------------- */\n\nfunction indexTree(items: TreeNode[]): Map<string, FlatNode> {\n  const map = new Map<string, FlatNode>();\n  const walk = (nodes: TreeNode[], depth: number, parent: string | undefined) => {\n    nodes.forEach((node, i) => {\n      map.set(node.key, { node, depth, parent, posinset: i + 1, setsize: nodes.length });\n      if (node.children?.length) walk(node.children, depth + 1, node.key);\n    });\n  };\n  walk(items, 0, undefined);\n  return map;\n}\n\nconst isParent = (node: TreeNode) => !!node.children?.length;\n\n/** 펼쳐진 경로만 따라간 화면 순서 — ↑ ↓ Home End 의 이동 대상 */\nfunction visibleKeys(items: TreeNode[], expanded: Set<string>): string[] {\n  const out: string[] = [];\n  const walk = (nodes: TreeNode[]) => {\n    for (const n of nodes) {\n      out.push(n.key);\n      if (isParent(n) && expanded.has(n.key)) walk(n.children!);\n    }\n  };\n  walk(items);\n  return out;\n}\n\nfunction leavesOf(node: TreeNode): TreeNode[] {\n  return isParent(node) ? node.children!.flatMap(leavesOf) : [node];\n}\n\n/** checkStrictly=false: leaf 가 진실이고 부모는 자식에서 계산합니다 (일부만 → mixed). */\nfunction deriveChecked(items: TreeNode[], selected: Set<string>, strictly: boolean): Map<string, Checked> {\n  const out = new Map<string, Checked>();\n  const walk = (node: TreeNode, forced: boolean): Checked => {\n    // 부모 key 가 직접 들어 있으면 서브트리 전체가 선택된 것으로 봅니다.\n    const on = forced || selected.has(node.key);\n    if (strictly) {\n      node.children?.forEach((c) => walk(c, false));\n      const v: Checked = selected.has(node.key) ? \"true\" : \"false\";\n      out.set(node.key, v);\n      return v;\n    }\n    if (!isParent(node)) {\n      const v: Checked = on ? \"true\" : \"false\";\n      out.set(node.key, v);\n      return v;\n    }\n    const states = node.children!.map((c) => walk(c, on));\n    const v: Checked = states.every((s) => s === \"true\")\n      ? \"true\"\n      : states.every((s) => s === \"false\")\n        ? \"false\"\n        : \"mixed\";\n    out.set(node.key, v);\n    return v;\n  };\n  items.forEach((n) => walk(n, false));\n  return out;\n}\n\n/* ---------------------------------- Tree ---------------------------------- */\n\nexport type TreeProps = Omit<HTMLAttributes<HTMLUListElement>, \"children\"> & {\n  ref?: Ref<HTMLUListElement>;\n  /** 트리 데이터입니다. 각 노드가 `children` 을 가지면 부모가 됩니다. */\n  items: TreeNode[];\n  /** `multiple` 이면 행에 체크박스가 붙습니다. */\n  selectionMode?: TreeSelectionMode;\n  /** 선택된 노드입니다. `single` 이면 길이 1로 씁니다. */\n  selectedKeys?: string[];\n  /** 펼쳐진 노드입니다. 여기 없는 부모는 접힙니다. */\n  expandedKeys?: string[];\n  /** 처음 펼쳐 둘 노드입니다. */\n  defaultExpandedKeys?: string[];\n  /** 선택되지 않는 노드입니다. 초점은 받습니다. */\n  disabledKeys?: string[];\n  /** `true` 면 부모와 자식의 체크를 연동하지 않습니다. `false` 일 때만 `mixed` 가 생깁니다. */\n  checkStrictly?: boolean;\n  /** 들여쓰기 가이드라인을 켭니다. 깊이 3 이상에서 권장합니다. */\n  showGuides?: boolean;\n  /** 긴 이름을 자르지 않고 줄바꿈합니다. */\n  wrapLabel?: boolean;\n  /** 트리 전체를 잠급니다. */\n  disabled?: boolean;\n  /** 선택이 바뀔 때 호출됩니다. */\n  onSelectionChange?: (keys: string[]) => void;\n  /** 펼침이 바뀔 때 호출됩니다. */\n  onExpandedChange?: (keys: string[]) => void;\n};\n\nconst EMPTY: string[] = [];\n\nfunction TreeRoot({\n  ref,\n  items,\n  selectionMode = \"single\",\n  selectedKeys,\n  expandedKeys,\n  defaultExpandedKeys = EMPTY,\n  disabledKeys = EMPTY,\n  checkStrictly = false,\n  showGuides = false,\n  wrapLabel = false,\n  disabled = false,\n  onSelectionChange,\n  onExpandedChange,\n  className,\n  onKeyDown,\n  ...rest\n}: TreeProps) {\n  const baseId = useId();\n  const index = useMemo(() => indexTree(items), [items]);\n  const [expandedArr, setExpandedArr] = useControllableState({\n    value: expandedKeys,\n    defaultValue: defaultExpandedKeys,\n    onChange: onExpandedChange,\n  });\n  const [selectedArr, setSelectedArr] = useControllableState({\n    value: selectedKeys,\n    defaultValue: EMPTY,\n    onChange: onSelectionChange,\n  });\n  const expanded = useMemo(() => new Set(expandedArr), [expandedArr]);\n  const selected = useMemo(() => new Set(selectedArr), [selectedArr]);\n  const disabledSet = useMemo(() => new Set(disabledKeys), [disabledKeys]);\n  const multiple = selectionMode === \"multiple\";\n  const checked = useMemo(\n    () => (multiple ? deriveChecked(items, selected, checkStrictly) : new Map<string, Checked>()),\n    [multiple, items, selected, checkStrictly],\n  );\n  const visible = useMemo(() => visibleKeys(items, expanded), [items, expanded]);\n\n  const [focusState, setFocusState] = useState<string | undefined>(undefined);\n  // roving tabindex: 현재 항목이 화면에서 사라지면(부모가 접힘) 보이는 첫 선택 항목 → 첫 항목\n  const focusKey =\n    focusState !== undefined && visible.includes(focusState)\n      ? focusState\n      : (visible.find((k) => (multiple ? checked.get(k) === \"true\" : selected.has(k))) ?? visible[0]);\n  const itemRefs = useRef(new Map<string, HTMLLIElement>()).current;\n  const typeahead = useRef({ text: \"\", at: 0 });\n\n  const focus = (key: string) => {\n    setFocusState(key);\n    itemRefs.get(key)?.focus();\n  };\n\n  const setExpanded = (keys: Set<string>) => setExpandedArr([...keys]);\n\n  const toggleExpanded = (key: string) => {\n    const n = index.get(key)?.node;\n    if (disabled || !n || !isParent(n)) return;\n    const next = new Set(expanded);\n    if (next.has(key)) next.delete(key);\n    else next.add(key);\n    setExpanded(next);\n  };\n\n  const select = (key: string) => {\n    if (disabled || selectionMode === \"none\" || disabledSet.has(key)) return;\n    if (!multiple) {\n      if (!selected.has(key) || selected.size !== 1) setSelectedArr([key]);\n      return;\n    }\n    const node = index.get(key)!.node;\n    if (checkStrictly) {\n      const next = new Set(selected);\n      if (next.has(key)) next.delete(key);\n      else next.add(key);\n      setSelectedArr([...next]);\n      return;\n    }\n    // 연동: 대상과 (비활성이 아닌) 자손을 함께 켜고 끕니다. mixed 를 누르면 전부 선택.\n    const on = checked.get(key) !== \"true\";\n    const leafOn = new Set<string>();\n    for (const [k, v] of checked) {\n      const n = index.get(k)!.node;\n      if (!isParent(n) && v === \"true\") leafOn.add(k);\n    }\n    for (const leaf of leavesOf(node)) {\n      if (disabledSet.has(leaf.key)) continue;\n      if (on) leafOn.add(leaf.key);\n      else leafOn.delete(leaf.key);\n    }\n    const derived = deriveChecked(items, leafOn, false);\n    const out = [...index.keys()].filter((k) => derived.get(k) === \"true\");\n    setSelectedArr(out);\n  };\n\n  const handleKeyDown = (e: KeyboardEvent<HTMLUListElement>) => {\n    onKeyDown?.(e);\n    if (e.defaultPrevented || focusKey === undefined) return;\n    const target = e.target as HTMLElement;\n    if (target.getAttribute(\"role\") !== \"treeitem\") return;\n    const flat = index.get(focusKey);\n    if (!flat) return;\n    const node = flat.node;\n    const at = visible.indexOf(focusKey);\n    const go = (key: string | undefined) => {\n      if (key === undefined) return;\n      e.preventDefault();\n      focus(key);\n    };\n    switch (e.key) {\n      case \"ArrowDown\":\n        return go(visible[at + 1]);\n      case \"ArrowUp\":\n        return go(visible[at - 1]);\n      case \"Home\":\n        return go(visible[0]);\n      case \"End\":\n        return go(visible[visible.length - 1]);\n      case \"ArrowRight\":\n        e.preventDefault();\n        if (!isParent(node)) return;\n        if (!expanded.has(focusKey)) toggleExpanded(focusKey);\n        else focus(node.children![0]!.key);\n        return;\n      case \"ArrowLeft\":\n        e.preventDefault();\n        if (isParent(node) && expanded.has(focusKey)) toggleExpanded(focusKey);\n        else if (flat.parent !== undefined) focus(flat.parent);\n        return;\n      case \" \":\n        e.preventDefault();\n        select(focusKey);\n        return;\n      case \"Enter\":\n        // 활성화 — 부모는 펼침/접기, leaf 는 선택\n        e.preventDefault();\n        if (isParent(node)) toggleExpanded(focusKey);\n        else select(focusKey);\n        return;\n      case \"*\": {\n        // 같은 단계의 형제를 모두 펼칩니다 (APG)\n        e.preventDefault();\n        if (disabled) return;\n        const siblings = flat.parent === undefined ? items : index.get(flat.parent)!.node.children!;\n        const next = new Set(expanded);\n        for (const s of siblings) if (isParent(s)) next.add(s.key);\n        if (next.size !== expanded.size) setExpanded(next);\n        return;\n      }\n      default: {\n        // type-ahead: 보이는 항목 중 그 글자로 시작하는 다음 항목\n        if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey || !e.key.trim()) return;\n        const now = Date.now();\n        const ta = typeahead.current;\n        ta.text = now - ta.at < 500 ? ta.text + e.key.toLowerCase() : e.key.toLowerCase();\n        ta.at = now;\n        const textOf = (k: string) => (itemRefs.get(k)?.querySelector(`.${B}__label`)?.textContent ?? \"\").toLowerCase();\n        const order = [...visible.slice(at + (ta.text.length === 1 ? 1 : 0)), ...visible.slice(0, at + 1)];\n        const hit = order.find((k) => textOf(k).startsWith(ta.text));\n        if (hit !== undefined) go(hit);\n      }\n    }\n  };\n\n  const ctx: TreeContextValue = {\n    index,\n    expanded,\n    selected,\n    checked,\n    disabledKeys: disabledSet,\n    selectionMode,\n    disabled,\n    wrapLabel,\n    focusKey,\n    baseId,\n    itemRefs,\n    toggleExpanded,\n    select,\n    focus,\n  };\n\n  return (\n    <TreeContext value={ctx}>\n      <ul\n        {...rest}\n        ref={ref}\n        role=\"tree\"\n        aria-multiselectable={multiple || undefined}\n        aria-disabled={disabled || undefined}\n        data-disabled={disabled ? \"\" : undefined}\n        className={cn(B, flag(B, \"guides\", showGuides), flag(B, \"multi\", multiple), flag(B, \"wrap\", wrapLabel), className)}\n        onKeyDown={handleKeyDown}\n      >\n        {items.map((n) => (\n          <TreeItem key={n.key} node={n} />\n        ))}\n      </ul>\n    </TreeContext>\n  );\n}\n\n/* ---------------------------------- Item ---------------------------------- */\n\nfunction TreeItem({ node }: { node: TreeNode }) {\n  const ctx = useTreeContext(\"Tree.Item\");\n  const flat = ctx.index.get(node.key)!;\n  const parent = isParent(node);\n  const open = parent && ctx.expanded.has(node.key);\n  const itemDisabled = ctx.disabledKeys.has(node.key);\n  const labelId = `${ctx.baseId}-${node.key}-label`.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n  const title = node.title ?? (!ctx.wrapLabel && typeof node.label === \"string\" ? node.label : undefined);\n  const style = { \"--atom-tree-depth\": flat.depth } as CSSProperties;\n\n  const onRowClick = (e: MouseEvent<HTMLDivElement>) => {\n    if (ctx.disabled) return;\n    e.stopPropagation();\n    ctx.focus(node.key);\n    if (ctx.selectionMode === \"none\") ctx.toggleExpanded(node.key);\n    else ctx.select(node.key);\n  };\n\n  return (\n    <li\n      ref={(el) => {\n        if (el) ctx.itemRefs.set(node.key, el);\n        else ctx.itemRefs.delete(node.key);\n      }}\n      className={`${B}__item`}\n      role=\"treeitem\"\n      data-state={parent ? (open ? \"expanded\" : \"collapsed\") : undefined}\n      aria-expanded={parent ? open : undefined}\n      aria-selected={ctx.selectionMode === \"single\" ? ctx.selected.has(node.key) : undefined}\n      aria-checked={ctx.selectionMode === \"multiple\" ? ctx.checked.get(node.key) : undefined}\n      aria-disabled={itemDisabled || undefined}\n      aria-level={flat.depth + 1}\n      aria-setsize={flat.setsize}\n      aria-posinset={flat.posinset}\n      aria-labelledby={labelId}\n      tabIndex={ctx.focusKey === node.key ? 0 : -1}\n      style={style}\n      onFocus={(e) => {\n        if (e.target === e.currentTarget && ctx.focusKey !== node.key) ctx.focus(node.key);\n      }}\n    >\n      <div className={`${B}__row`} onClick={onRowClick}>\n        {parent ? (\n          <span\n            className={`${B}__caret`}\n            onClick={(e) => {\n              if (ctx.disabled) return;\n              e.stopPropagation();\n              ctx.focus(node.key);\n              ctx.toggleExpanded(node.key);\n            }}\n          >\n            <ChevronRightIcon />\n          </span>\n        ) : (\n          <span className={`${B}__caret ${B}__caret--empty`} />\n        )}\n        {ctx.selectionMode === \"multiple\" && (\n          <span className={`${B}__checkbox`} aria-hidden=\"true\">\n            <CheckIcon className={`${B}__check`} />\n            <span className={`${B}__dash`} />\n          </span>\n        )}\n        {node.icon !== undefined && (\n          <Slot aria-hidden=\"true\" className={`${B}__icon`}>\n            {node.icon}\n          </Slot>\n        )}\n        <span id={labelId} className={`${B}__label`} title={title}>\n          {node.label}\n        </span>\n        {node.badge !== undefined && <span className={`${B}__badge`}>{node.badge}</span>}\n      </div>\n      {parent && (\n        <ul className={`${B}__group`} role=\"group\" hidden={!open}>\n          {node.children!.map((c) => (\n            <TreeItem key={c.key} node={c} />\n          ))}\n        </ul>\n      )}\n    </li>\n  );\n}\n\nexport const Tree = Object.assign(TreeRoot, {});\n",
      "type": "registry:ui",
      "target": "components/ui/tree.tsx"
    },
    {
      "path": "poc/design-system/implement/src/styles/atom/elements/tree.css",
      "content": "/* ============================================================================\n   Element — Tree\n   ----------------------------------------------------------------------------\n   A nested list of rows that expand, collapse and get selected. Modelled on the\n   WAI-ARIA APG treeview pattern: the DOM nesting carries the semantics\n   (role=\"group\") and every row is one role=\"treeitem\".\n\n   Depth arrives as an inline custom property, never as a class. A\n   `--depth_1 … --depth_9` ladder would cap the tree at whatever number this\n   file happened to stop at, and a tree's depth is data, not a design decision.\n\n   atom-tree                       role=\"tree\"\n     --multi                       checkbox rows, aria-multiselectable=\"true\"\n     --guides                      vertical indent guide per level\n     --wrap                        long labels wrap instead of truncating\n     [data-disabled]               the whole tree stops responding\n\n     __group     role=\"group\"      [hidden] while its parent is collapsed\n     __item      role=\"treeitem\"   [data-state=\"expanded|collapsed\"]\n                                   [aria-selected] [aria-checked=\"true|mixed\"]\n                                   [aria-disabled]\n     __row       the full-width visual row — hover, focus, selection live here\n     __caret     --empty           rotates 90deg when expanded\n     __checkbox  __check  __dash   multi mode, drawn here rather than imported\n     __icon      __label  __badge  optional slots\n\n   Local custom properties (--atom-tree-depth / -indent / -row-h) are always\n   read with a fallback: a virtualised tree renders rows into a flat container\n   without .atom-tree above them, and a row with no depth must still draw.\n   ========================================================================== */\n\n.atom-tree {\n  --atom-tree-indent: 20px;\n  --atom-tree-row-h: 32px;\n\n  display: flex;\n  flex-direction: column;\n  min-width: 0;\n  margin: 0;\n  padding: var(--atom-x2);\n  border-radius: var(--atom-radius-r2);\n  background: var(--atom-bg-layer);\n  box-shadow: inset 0 0 0 1px var(--atom-border-subtle);\n  font-size: 14px;\n  line-height: 1.45;\n  color: var(--atom-fg-neutral);\n  list-style: none;\n}\n\n.atom-tree__group {\n  position: relative;\n  margin: 0;\n  padding: 0;\n  list-style: none;\n}\n\n/* Collapsed children leave the accessibility tree, not just the screen. A\n   `display: none` reached only through a class would still let a screen reader\n   walk into rows the sighted user cannot see. */\n.atom-tree__group[hidden] {\n  display: none;\n}\n\n.atom-tree__item {\n  list-style: none;\n}\n\n.atom-tree__item:focus {\n  outline: none;\n}\n\n/* --- row ----------------------------------------------------------------- */\n/* Indentation is padding on the row, not margin on the group, so the hover and\n   selection fills still run the full width of the tree. A tree whose highlight\n   starts at the indent reads as a set of disconnected boxes. */\n.atom-tree__row {\n  position: relative;\n  display: flex;\n  align-items: center;\n  gap: var(--atom-x2);\n  min-height: var(--atom-tree-row-h, 32px);\n  padding-block: 4px;\n  padding-inline: calc(\n      var(--atom-x2) + var(--atom-tree-depth, 0) * var(--atom-tree-indent, 20px)\n    )\n    var(--atom-x2);\n  border-radius: var(--atom-radius-r1);\n  cursor: pointer;\n  transition: background-color var(--atom-duration-fast) ease;\n}\n\n/* --- caret --------------------------------------------------------------- */\n/* A leaf keeps the slot and drops the glyph. Removing it outright would pull\n   every leaf 28px left of its siblings, and the indent — the only thing telling\n   you which parent a row belongs to — would stop lining up. */\n.atom-tree__caret {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n  transition: transform var(--atom-duration-fast) var(--atom-ease-out);\n}\n\n.atom-tree__caret svg {\n  display: block;\n  width: 20px;\n  height: 20px;\n}\n\n.atom-tree__caret--empty {\n  pointer-events: none;\n}\n\n.atom-tree__item[data-state=\"expanded\"] > .atom-tree__row > .atom-tree__caret {\n  transform: rotate(90deg);\n}\n\n/* --- indent guides ------------------------------------------------------- */\n/* The line is drawn by the group, not by the rows, so it spans the parent's\n   whole subtree and stops where the group ends. Drawing it per row would break\n   the line wherever a sibling in between is expanded — its descendants sit at a\n   deeper level and would not continue the shallower one.\n\n   x = the parent caret's centre. The group inherits its parent item's depth;\n   the rows inside it declare depth + 1 on themselves. */\n.atom-tree--guides .atom-tree__group::before {\n  content: \"\";\n  position: absolute;\n  inset-block: 0;\n  inset-inline-start: calc(\n    var(--atom-x2) + var(--atom-tree-depth, 0) * var(--atom-tree-indent, 20px) +\n      10px\n  );\n  width: 1px;\n  background: var(--atom-border-default);\n}\n\n/* --- checkbox (multi mode) ----------------------------------------------- */\n/* Same shape as Checkbox at its small size, redrawn here rather than imported:\n   a preview card loads exactly one stylesheet. */\n.atom-tree__checkbox {\n  position: relative;\n  width: 16px;\n  height: 16px;\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-tree__check,\n.atom-tree__dash {\n  position: absolute;\n  inset: 0;\n  margin: auto;\n  color: var(--atom-fg-on-solid);\n  opacity: 0;\n}\n\n.atom-tree__check {\n  width: 11px;\n  height: 11px;\n}\n\n.atom-tree__dash {\n  width: 8px;\n  height: 2px;\n  border-radius: var(--atom-radius-full);\n  background: currentColor;\n}\n\n.atom-tree__item[aria-checked=\"true\"] > .atom-tree__row .atom-tree__checkbox,\n.atom-tree__item[aria-checked=\"mixed\"] > .atom-tree__row .atom-tree__checkbox {\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-tree__item[aria-checked=\"true\"] > .atom-tree__row .atom-tree__check {\n  opacity: 1;\n}\n\n/* `mixed` is a third value, not a dimmed check: some children are in, some are\n   out, and clicking it resolves to all-in. */\n.atom-tree__item[aria-checked=\"mixed\"] > .atom-tree__row .atom-tree__dash {\n  opacity: 1;\n}\n\n/* --- slots --------------------------------------------------------------- */\n.atom-tree__icon {\n  width: 20px;\n  height: 20px;\n  flex-shrink: 0;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* Truncating is the default: every level takes 20px off the usable width, so a\n   wrapping label turns rows into different heights exactly where the tree is\n   deepest — and the up/down rhythm is the whole point of a tree. */\n.atom-tree__label {\n  flex: 1;\n  min-width: 0;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.atom-tree--wrap .atom-tree__label {\n  overflow: visible;\n  white-space: normal;\n  word-break: keep-all;\n}\n\n.atom-tree__badge {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n  min-width: 18px;\n  height: 18px;\n  padding-inline: 6px;\n  border-radius: var(--atom-radius-full);\n  background: var(--atom-bg-neutral-weak);\n  font-size: 11px;\n  font-weight: 600;\n  font-variant-numeric: tabular-nums;\n  color: var(--atom-fg-neutral-muted);\n}\n\n/* --- states -------------------------------------------------------------- */\n.atom-tree__row:hover {\n  background: var(--atom-bg-side-hover);\n}\n\n/* Hover is a fill, focus is a ring. A tree is walked with the arrow keys while\n   the pointer sits somewhere else on screen; if the two states looked alike the\n   keyboard user could not tell which row is theirs. */\n.atom-tree__item:focus-visible > .atom-tree__row {\n  outline: 2px solid var(--atom-fg-brand);\n  outline-offset: -2px;\n  background: var(--atom-bg-side-hover);\n}\n\n.atom-tree__item[aria-selected=\"true\"] > .atom-tree__row {\n  background: var(--atom-bg-side-active);\n  font-weight: 600;\n}\n\n/* Selection is not signalled by colour alone — the bar and the weight carry it\n   for anyone who cannot separate the two fills. */\n.atom-tree__item[aria-selected=\"true\"] > .atom-tree__row::before {\n  content: \"\";\n  position: absolute;\n  inset-block: 4px;\n  inset-inline-start: 0;\n  width: 2px;\n  border-radius: var(--atom-radius-full);\n  background: var(--atom-fg-brand);\n}\n\n.atom-tree__item[aria-selected=\"true\"] > .atom-tree__row .atom-tree__icon,\n.atom-tree__item[aria-selected=\"true\"] > .atom-tree__row .atom-tree__caret {\n  color: var(--atom-fg-brand);\n}\n\n/* Multi mode: the checkbox owns the state, so the row drops the single-select\n   fill. Carrying both says the same thing twice and leaves hover and focus\n   nothing of their own to show. */\n.atom-tree--multi .atom-tree__item[aria-selected=\"true\"] > .atom-tree__row {\n  background: none;\n  font-weight: 400;\n}\n\n.atom-tree--multi\n  .atom-tree__item[aria-selected=\"true\"]\n  > .atom-tree__row::before {\n  content: none;\n}\n\n.atom-tree__item[aria-disabled=\"true\"] > .atom-tree__row {\n  color: var(--atom-fg-neutral-subtle);\n  cursor: not-allowed;\n}\n\n.atom-tree__item[aria-disabled=\"true\"] > .atom-tree__row:hover {\n  background: none;\n}\n\n.atom-tree__item[aria-disabled=\"true\"] > .atom-tree__row .atom-tree__icon,\n.atom-tree__item[aria-disabled=\"true\"] > .atom-tree__row .atom-tree__badge,\n.atom-tree__item[aria-disabled=\"true\"] > .atom-tree__row .atom-tree__checkbox {\n  opacity: 0.45;\n}\n\n/* A disabled tree keeps its rows readable — it is still telling you what is in\n   there — and only stops answering the pointer. */\n.atom-tree[data-disabled] {\n  cursor: not-allowed;\n}\n\n.atom-tree[data-disabled] .atom-tree__row {\n  pointer-events: none;\n  color: var(--atom-fg-neutral-subtle);\n}\n\n.atom-tree[data-disabled] .atom-tree__icon,\n.atom-tree[data-disabled] .atom-tree__badge,\n.atom-tree[data-disabled] .atom-tree__checkbox {\n  opacity: 0.45;\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .atom-tree__caret,\n  .atom-tree__row,\n  .atom-tree__checkbox {\n    transition: none;\n  }\n}\n",
      "type": "registry:file",
      "target": "styles/atom/elements/tree.css"
    }
  ],
  "categories": [
    "display"
  ],
  "meta": {
    "version": "0.1.0",
    "kind": "component",
    "status": "stable",
    "specSource": "measured",
    "parts": [
      "Tree"
    ],
    "docs": "poc/design-system/components/tree.md"
  }
}
