{
  "docPath": "docs/data-display/tree.tsx",
  "example": "import * as React from \"react\";\nimport { Tree } from \"@godxjp/ui/data-display\";\n\nconst permissions = [\n  {\n    value: \"billing\",\n    label: \"Billing\",\n    children: [\n      { value: \"billing.invoice.read\", label: \"Read invoices\" },\n      { value: \"billing.invoice.write\", label: \"Issue invoices\" },\n    ],\n  },\n  {\n    value: \"people\",\n    label: \"People\",\n    children: [\n      { value: \"people.read\", label: \"Read profiles\" },\n      { value: \"people.write\", label: \"Edit profiles\", disabled: true },\n    ],\n  },\n];\n\nexport function PermissionTree() {\n  // The name is YOUR copy — run it through your app's own i18n, not the library's.\n  const [granted, setGranted] = React.useState<string[]>([\"billing.invoice.read\"]);\n\n  return (\n    <Tree\n      aria-label=\"Permissions\"\n      treeData={permissions}\n      checkable\n      defaultExpandAll\n      checkedValues={granted}\n      onCheckedValuesChange={setGranted}\n    />\n  );\n}",
  "group": "data-display",
  "importPath": "@godxjp/ui/data-display",
  "name": "Tree",
  "props": [
    {
      "description": "The hierarchy. Each node: `{ value: string; label: ReactNode; disabled?: boolean; disableCheckbox?: boolean; isLeaf?: boolean; icon?: ReactNode; children?: TreeNodeProp[] }`. Same node shape as TreeSelect/Cascader; use `fieldNames` to remap an API response instead of transforming it.",
      "name": "treeData",
      "required": true,
      "type": "TreeNodeProp[]"
    },
    {
      "description": "Remap the data's own key names, e.g. `{ label: 'name', value: 'id', children: 'items' }`.",
      "name": "fieldNames",
      "type": "{ label?: string; value?: string; children?: string }"
    },
    {
      "description": "Controlled SELECTION (antd `selectedKeys`). `string` while single, `string[]` once `multiple` is on. Selection and checks are separate axes — do not use this for checkboxes.",
      "name": "value",
      "type": "string | string[]"
    },
    {
      "description": "Initial selection for an uncontrolled tree. Ignored once `value` is passed.",
      "name": "defaultValue",
      "type": "string | string[]"
    },
    {
      "description": "Selection change (antd `onSelect`). Emits `string | undefined` while single, `string[]` once `multiple` is on.",
      "name": "onValueChange",
      "type": "(value: string | string[] | undefined) => void"
    },
    {
      "defaultValue": "false",
      "description": "Allow more than one selected node. Also sets `aria-multiselectable` on the tree.",
      "name": "multiple",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Draw a checkbox on every node (antd `checkable`). Checking cascades to descendants and a parent shows `indeterminate` while only some are checked. With `checkable`, Enter/Space toggles the checkbox rather than selecting.",
      "name": "checkable",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Parent and child checks are independent (antd `checkStrictly`) — no cascade and no indeterminate state.",
      "name": "checkStrictly",
      "type": "boolean"
    },
    {
      "description": "Controlled checked nodes (antd `checkedKeys`).",
      "name": "checkedValues",
      "type": "string[]"
    },
    {
      "description": "Initial checked nodes for an uncontrolled tree (antd `defaultCheckedKeys`).",
      "name": "defaultCheckedValues",
      "type": "string[]"
    },
    {
      "description": "Checked-set change (antd `onCheck`). The array is normalised: a branch appears only when every tickable child is checked, so it can never claim a partial selection is complete.",
      "name": "onCheckedValuesChange",
      "type": "(values: string[]) => void"
    },
    {
      "description": "Controlled expanded branches (antd `expandedKeys`).",
      "name": "expandedValues",
      "type": "string[]"
    },
    {
      "description": "Initial expanded branches (antd `defaultExpandedKeys`).",
      "name": "defaultExpandedValues",
      "type": "string[]"
    },
    {
      "description": "Expansion change (antd `onExpand`). Fires for controlled and uncontrolled trees.",
      "name": "onExpandedValuesChange",
      "type": "(values: string[]) => void"
    },
    {
      "defaultValue": "false",
      "description": "Start with every branch open. Seeded once on mount, never re-applied.",
      "name": "defaultExpandAll",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Expand a parent by clicking its ROW, not only its switcher (antd DirectoryTree): \"click\" selects AND toggles — a page tree where choosing a folder opens it; \"doubleClick\" toggles on a double click (gh#1162). Keyboard keeps the APG map.",
      "name": "expandAction",
      "type": "false | \"click\" | \"doubleClick\""
    },
    {
      "description": "Lazy children (antd `loadData`). Called when a branch with no `children` and `isLeaf !== true` is expanded, and never again once its promise RESOLVES; a Skeleton row and `aria-busy` cover the wait. Push the fetched children into `treeData`. A REJECTED promise is not a load (rc-tree, gh#1041): the branch folds back shut (uncontrolled expansion) and the next expand asks again, up to 10 attempts.",
      "name": "loadData",
      "type": "(node: TreeNodeProp) => void | Promise<void>"
    },
    {
      "defaultValue": "false",
      "description": "Move nodes by dragging (antd `draggable`, gh#1093). Dropping on a row's upper / lower quarter lands in the gap before / after it, the middle files it inside. The tree never edits `treeData` — it reports the move via `onDrop` and you persist it. Keyboard equivalent (WCAG 2.5.7): Alt+↑ / Alt+↓ move past the previous / next sibling, Alt+← outdents (after the parent), Alt+→ indents (into the previous sibling, as its last child); every move is announced in a live region and described on the tree. A node never drops into itself or its own subtree.",
      "name": "draggable",
      "type": "boolean"
    },
    {
      "description": "Per-node drag gate (antd `draggable.nodeDraggable`). Disabled nodes never drag.",
      "name": "allowDrag",
      "type": "(node: TreeNodeProp) => boolean"
    },
    {
      "description": "Per-target drop gate (antd `allowDrop`), asked for pointer AND keyboard moves. Use it for your own rules (max depth, no leaf parents); the self/subtree cycle is already refused.",
      "name": "allowDrop",
      "type": "(info: { dragNode: TreeNodeProp; dropNode: TreeNodeProp; dropPosition: -1 | 0 | 1 }) => boolean"
    },
    {
      "description": "A committed move (antd `onDrop`). `dropPosition` is RELATIVE to `node` — -1 before, 0 inside, 1 after (antd reports an absolute index that every antd demo converts back to this). `dropToGap` is true for before/after (sibling of `node`), false for inside (child of `node`). Update `treeData` here.",
      "name": "onDrop",
      "type": "(info: { dragNode: TreeNodeProp; node: TreeNodeProp; dropPosition: -1 | 0 | 1; dropToGap: boolean }) => void"
    },
    {
      "description": "Render a node's title yourself (antd `titleRender`).",
      "name": "titleRender",
      "type": "(node: TreeNodeProp) => ReactNode"
    },
    {
      "description": "Highlight the nodes this returns true for (antd `filterTreeNode`, gh#1043) — the match of a search box above the tree. Matching rows are MARKED, not hidden: `data-filter-node=\"true\"`, the label in `--tree-node-filter-foreground` (default `hsl(var(--primary))`) at the medium weight, plus sr-only \"matches the filter\" text. Outline, keyboard and ARIA are unchanged. To also open the branches that hold a match, drive `expandedValues`.",
      "name": "filterTreeNode",
      "type": "(node: TreeNodeProp) => boolean"
    },
    {
      "description": "Viewport height in px (antd `height`, gh#1042). The tree scrolls inside it and, unless `virtual={false}`, renders only the rows in view plus a small overscan — the windowed rows stay a flat run of treeitems whose `aria-level`/`aria-setsize`/`aria-posinset` still describe the whole outline. Rows must share one height (the `size` tier).",
      "name": "height",
      "type": "number"
    },
    {
      "defaultValue": "true",
      "description": "Set `false` to keep the `height` viewport but render every row (antd `virtual`).",
      "name": "virtual",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Draw the connector rails between a parent and its children (antd `showLine`). Off by default — a rail is chrome, and chrome defaults quiet.",
      "name": "showLine",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Rule between the rows: a hairline on every node's block-start except the outline's first, so a tree used as navigation inside a Card reads as a list instead of one block (gh#732). The rule runs the FULL width of the row at every depth — never indented per level, which would make the children read as a nested table. Colour is `--tree-divider-color` (default `hsl(var(--border))`). Off by default, like `showLine` — chrome defaults quiet. Not antd's name: antd's Tree has no such capability, and `bordered` means a grid's frame plus column rules elsewhere in this library.",
      "name": "divided",
      "type": "boolean"
    },
    {
      "defaultValue": "false",
      "description": "Draw each node's `icon` (antd `showIcon`). `variant=\"directory\"` supplies folder/file glyphs when a node has no `icon` of its own.",
      "name": "showIcon",
      "type": "boolean"
    },
    {
      "defaultValue": "\"default\"",
      "description": "`directory` is antd's `<DirectoryTree>`: folder/file glyphs and a full-row selected band.",
      "name": "variant",
      "type": "\"default\" | \"directory\""
    },
    {
      "defaultValue": "\"md\"",
      "description": "Row height tier — the shared `--control-height` ladder, so rows stay in step with the controls beside them under every density.",
      "name": "size",
      "type": "\"xs\" | \"sm\" | \"md\" | \"lg\""
    },
    {
      "defaultValue": "false",
      "description": "Disable the whole tree: nothing selects, checks or expands, and the nodes stay readable.",
      "name": "disabled",
      "type": "boolean"
    },
    {
      "description": "Classes applied to the `role=\"tree\"` container.",
      "name": "className",
      "type": "string"
    },
    {
      "description": "HTML id on the `role=\"tree\"` container.",
      "name": "id",
      "type": "string"
    },
    {
      "description": "Accessible name of the tree. Required unless `aria-labelledby` names it from a visible heading.",
      "name": "aria-label",
      "type": "string"
    }
  ],
  "related": [
    "TreeList — REMOVED in 21.0.0, replaced by Tree. It was a flat `<ul>` whose `depth` only drove `margin-inline-start`: it LOOKED like a tree and had no expand/collapse, no `role=\"tree\"`, no keyboard and no selection contract. Migration: nest the flat `items` into `treeData` (`id`→`value`, `title`→`label`, `depth`→nesting) and pass `aria-label`. A genuinely static indented outline that never opens is a `Descriptions` or a `ListRow` stack, not a tree.",
    "TreeSelect — the same hierarchy INSIDE a Popover, as a form field. Use TreeSelect when the answer is a value in a form; use Tree when the hierarchy itself is the page.",
    "Cascader — a path picker across columns. Use it when the user walks one path to a leaf; use Tree when several branches are open at once.",
    "Accordion — single-level disclosure with rich panel content. It is not a hierarchy and has no tree keyboard model.",
    "ScrollArea — not needed around Tree: pass `height`, and the tree is its own (windowed) scroll viewport."
  ],
  "rules": [
    2,
    6,
    23,
    31,
    44,
    45
  ],
  "storyPath": "data-display/tree.tsx",
  "tagline": "The standalone WAI-ARIA tree view on a page (antd `Tree` / `DirectoryTree`) — nested `treeData`, a disclosure triangle per branch, roving-tabindex arrow navigation, optional tri-state checkboxes, async `loadData` and `draggable` reorder/reparent (antd `onDrop`). Reach for it whenever nodes expand, collapse or are navigated by keyboard; TreeSelect is the same hierarchy inside a Popover.",
  "usage": [
    "DO name the tree — pass `aria-label` (through your own `t()`) or `aria-labelledby`. A tree with no name is an unlabelled landmark for every screen reader user.",
    "DO rely on Tree for a cut label (gh#1308): a label that is actually truncated gets its full text as a `title` and unfolds while its row holds keyboard focus (not in a windowed tree, whose rows share one height — the `title` stays). It is re-measured when a label's box, the tree's width or the loaded fonts change — text spacing (WCAG 1.4.12) or a late web font included (gh#1318). No `titleRender` wrapper with a hand-set `title` is needed; one you set is kept.",
    "DO keep SELECTION and CHECKS apart: `value`/`onValueChange` is which node is open in the detail pane; `checkedValues`/`onCheckedValuesChange` is which nodes are ticked. They are two axes, exactly as in antd — never drive one from the other.",
    "DO keep SELECTION and EXPANSION apart too — this is the THIRD axis and the one gh#910 tripped on. `onValueChange` says which node a detail pane should show; `onExpandedValuesChange` says which branches are unfolded. A sibling pane listens to the first and ignores the second, so unfolding a branch cannot reload it. If your pane reloads when a branch opens, it is being driven from the wrong event — not from a missing feature.",
    "DO let the keyboard work: the tree ships the full APG contract (Up/Down through visible nodes, Right expands then descends, Left collapses then climbs, Home/End, Enter/Space, `*` to expand the current level, type-ahead). Do not add your own key handling on top.",
    "DON'T nest a Button, Checkbox, Link or any focusable control inside a node label. A tree item owns exactly ONE tab stop; the disclosure triangle and the tick box are decorative glyphs for that reason. Put row actions in a sibling column outside the tree, or open a detail pane on selection.",
    "DON'T hand-roll an indented `<ul>` (or a NavList / ListRow stack with a per-depth margin) for a hierarchy. A flat indented list only LOOKS like a tree: no expand/collapse, no `role=\"tree\"`, no keyboard model, no selection contract. `TreeList` was exactly that list and was REMOVED in 21.0.0 — Tree is what replaced it, and it is the one to reach for whenever nodes expand, collapse or are keyboard-navigated.",
    "DO pass `divided` when the tree IS the navigation of a page — a wiki/document outline or a section index sitting in a `Card` (`Card` > `CardContent flush` > `Tree divided`). Without a rule the rows run together and the outline reads as one block; with it, it reads as the ruled list ListRow and Table already give a flat list. Retint it per theme with `--tree-divider-color`, never with a per-page utility class.",
    "DO pass `height` for a long tree (antd `height` + `virtual`): the tree scrolls inside that height and keeps only the rows in view in the DOM, so a 421-child level renders a few dozen rows. Do not wrap it in `ScrollArea` as well — the tree is its own viewport.",
    "DO push fetched children into `treeData` from `loadData`; the tree calls it once per node that loads and shows a Skeleton row until the data lands. Reject the promise on failure — the branch folds shut and the next expand retries.",
    "DON'T wait for a paged \"load more\" node — antd's Tree has none, and `height` covers a long list. If the API itself is paged, append a leaf node such as `{ value: `${parent}::more`, label: t(\"…load more\"), isLeaf: true }` to the loaded children and fetch the next page from `onValueChange` when it is selected: it is a real treeitem, so it is keyboard-reachable and announced."
  ],
  "useCases": [
    "A permission tree: modules → resources → actions with tri-state checkboxes, where ticking a module ticks everything under it and a partly-granted module shows the dash.",
    "A category browser beside a detail pane — selecting a category loads its products, and the whole hierarchy stays navigable by keyboard.",
    "An organisation chart / department picker on a settings page, where a branch's children are fetched on demand with `loadData`.",
    "A file explorer (`variant=\"directory\"`, `showIcon`, `showLine`) where folders and files read differently and the selected row spans the width.",
    "A chart-of-accounts outline that must expand and collapse — the case a flat indented list only ever looked like it handled.",
    "A wiki / document page index used as the navigation of a screen, inside a Card: `divided` rules the rows so the outline reads as a list, and selection drives the reading pane."
  ]
}
