# 树形结构

- 页面: https://jiguang132.com/zh/resources/mantine/docs/tree
- 资源: Mantine Core (https://jiguang132.com/zh/resources/mantine.md)
- 全部指南: https://jiguang132.com/zh/resources/mantine/docs.md

Mantine Core 的独立组件，请不要替换为 JGuang UI/Base UI 的同名 API。

## 安装

```bash
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1
```

入口引入 `@mantine/core/styles.css`，并在根组件使用 `MantineProvider`；通过 `forceColorScheme` 同步深浅色主题。

## 使用与验证

在左侧选择组件，在预览中操作它的状态；使用“查看代码”检查真实源码。安装后执行类型检查和构建，再验证键盘焦点、手机布局与深浅色主题。

## 来源

https://github.com/mantinedev/mantine/tree/f0e2bc6d28b0f0b8aaefb5b859937bf77f539df7
MIT 许可证；不包括付费内容。下方提供上游英文参考内容，中文使用指南已适配；上游详细参考暂保留英文。

---


## 版本与兼容性

固定的 Mantine Core 9.7.1 要求 React 和 React DOM 满足 `^19.2.0`，并配套 `@mantine/hooks@9.7.1`。此条件来自实际 npm 包的 peerDependencies。React 18 或其他不兼容版本不能强行安装；先向项目负责人说明升级影响并确认方案，不使用 --force/--legacy-peer-deps 掩盖冲突。入口的原生 Provider 开启 `theme.respectReducedMotion`，并用 `forceColorScheme` 控制深浅色。

## 适用场景

浏览层级数据结构。

## 本地示例使用的原生参数

以下参数由本地、经过类型检查的 JSX 示例提取，按所属组件区分。不是全部属性清单；完整签名和继承关系见下方固定源码参考。

| 组件参数 | 说明 |
| --- | --- |
| `Tree.data` | 本地选项或节点，保持 value 唯一并遵循原生结构。 |

## 交互与状态检查

当前场景：默认。切换支持的禁用、错误或只读场景后检查真实行为；无此原生状态的组件不会被强行添加统一属性。验证键盘焦点和退出方式，修改值后按原生回调签名连接业务状态。

## 业务接入边界

保持节点 value 唯一，通过原生树状态 API 控制展开与选择；本地节点不代表文件系统权限。

## 宿主配置

先安装固定版本 @mantine/core 和 @mantine/hooks，在入口引入 @mantine/core/styles.css，并使用 MantineProvider。示例的 theme 和 variant 是 JGuang 编写的宿主参数，不是所有原生组件都接受的统一 API；通过 forceColorScheme 同步应用主题。源码面板中的原生实现仅供阅读，使用 npm 导出，不要单独复制内部实现。此批只覆盖 Core，不包括日期、图表和表单扩展包。上游详细说明保留英文，不宣称全文中文化。


## 可运行用法

以下代码使用资源原生 API；不是把其他设计系统的同名组件替换进来。

```tsx
import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';

const tree = [{ value: "src", label: "src", children: [{ value: "components", label: "components" }, { value: "styles", label: "styles" }] }];
function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const [value, setValue] = useState(35);
return <M.Tree data={tree} />;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

```


## 可用状态与示例

默认

示例遵循 Mantine 原生 API。支持多状态的输入组件可将示例中的 variant 改为 Disabled、Invalid 或 Read only；这些只是示例标签，不是新增的组件属性。改造时保留组件要求的数据、受控值、store 和复合子组件结构。


## Usage

The `Tree` component is used to display hierarchical data. The `Tree` component
has minimal styling by default; you can customize styles with [Styles API](/styles/styles-api).



## Data prop

Data passed to the `data` prop should follow these rules:

- Data must be a stable reference (memoized)
- Data must be an array
- Each item in the array represents a node in the tree
- Each node must be an object with `value` and `label` keys
- Each node can have a `children` key with an array of child nodes
- The `value` of each node must be unique

Valid data example:

```tsx
// ✅ Valid data, all values are unique
const data = [
  {
    value: 'src',
    label: 'src',
    children: [
      { value: 'src/components', label: 'components' },
      { value: 'src/hooks', label: 'hooks' },
    ],
  },
  { value: 'package.json', label: 'package.json' },
];
```

Invalid data example:

```tsx
// ❌ Invalid data, values are not unique (components is used twice)
const data = [
  {
    value: 'src',
    label: 'src',
    children: [{ value: 'components', label: 'components' }],
  },
  { value: 'components', label: 'components' },
];
```

## Data type

You can import the `TreeNodeData` type to define the data type for your tree:

```tsx
import { TreeNodeData } from '@mantine/core';

const data: TreeNodeData[] = [
  {
    value: 'src',
    label: 'src',
    children: [
      { value: 'src/components', label: 'components' },
      { value: 'src/hooks', label: 'hooks' },
    ],
  },
  { value: 'package.json', label: 'package.json' },
];
```

## renderNode

Use the `renderNode` prop to customize node rendering.
The `renderNode` function receives an object with the following properties as a single argument:

```tsx
export interface RenderTreeNodePayload {
  /** Node level in the tree */
  level: number;

  /** `true` if the node is expanded, applicable only for nodes with `children` */
  expanded: boolean;

  /** `true` if the node has non-empty `children` array or `hasChildren` is set to `true` */
  hasChildren: boolean;

  /** `true` if the node is selected */
  selected: boolean;

  /** `true` if the node's children are currently being loaded */
  isLoading: boolean;

  /** Error from the last failed load attempt, or `null` */
  loadError: Error | null;

  /** Node data from the `data` prop of `Tree` */
  node: TreeNodeData;

  /** Tree controller instance, return value of `useTree` hook */
  tree: TreeController;

  /** Props to spread into the root node element */
  elementProps: {
    className: string;
    style: React.CSSProperties;
    onClick: (event: React.MouseEvent) => void;
    'data-selected': boolean | undefined;
    'data-value': string;
  };

  /** Props to spread into the drag handle element when `withDragHandle` is set on `Tree`,
   * `undefined` otherwise */
  dragHandleProps: { onMouseDown: (event: React.MouseEvent) => void } | undefined;
}
```



## useTree hook

`useTree` hook can be used to control selected and expanded state of the tree.

The hook accepts an object with the following properties:

```tsx
export interface UseTreeInput {
  /** Initial expanded state of all nodes, uncontrolled state */
  initialExpandedState?: TreeExpandedState;

  /** Expanded state of all nodes, controlled state */
  expandedState?: TreeExpandedState;

  /** Called when the tree expanded state changes */
  onExpandedStateChange?: (expandedState: TreeExpandedState) => void;

  /** Initial selected state of nodes */
  initialSelectedState?: string[];

  /** Selected state of all nodes, controlled state */
  selectedState?: string[];

  /** Called when the tree selected state changes */
  onSelectedStateChange?: (selectedState: string[]) => void;

  /** Initial checked state of nodes */
  initialCheckedState?: string[];

  /** Checked state of all nodes, controlled state */
  checkedState?: string[];

  /** Called when the tree checked state changes */
  onCheckedStateChange?: (checkedState: string[]) => void;

  /** Determines whether multiple node can be selected at a time */
  multiple?: boolean;

  /** Called with the node value when it is expanded */
  onNodeExpand?: (value: string) => void;

  /** Called with the node value when it is collapsed */
  onNodeCollapse?: (value: string) => void;

  /** Called when a node with `hasChildren: true` is expanded for the first time */
  onLoadChildren?: (nodeValue: string) => Promise<void>;

  /** When `true`, checking a parent does not affect children and vice versa.
   * Each node's checked state is fully independent. @default false
   */
  checkStrictly?: boolean;
}
```

And returns an object with the following properties:

```tsx
export interface UseTreeReturnType {
  /** When `true`, each node's checked state is independent (no parent-child cascading) */
  checkStrictly: boolean;

  /** Determines whether multiple node can be selected at a time */
  multiple: boolean;

  /** A record of `node.value` and boolean values that represent nodes expanded state */
  expandedState: TreeExpandedState;

  /** An array of selected nodes values */
  selectedState: string[];

  /** An array of checked nodes values */
  checkedState: string[];

  /** A value of the node that was last clicked
   * Anchor node is used to determine range of selected nodes for multiple selection
   */
  anchorNode: string | null;

  /** Initializes tree state based on provided data, called automatically by the Tree component */
  initialize: (data: TreeNodeData[]) => void;

  /** Toggles expanded state of the node with provided value */
  toggleExpanded: (value: string) => void;

  /** Collapses node with provided value */
  collapse: (value: string) => void;

  /** Expands node with provided value */
  expand: (value: string) => void;

  /** Expands all nodes */
  expandAllNodes: () => void;

  /** Collapses all nodes */
  collapseAllNodes: () => void;

  /** Sets expanded state */
  setExpandedState: React.Dispatch<
    React.SetStateAction<TreeExpandedState>
  >;

  /** Toggles selected state of the node with provided value */
  toggleSelected: (value: string) => void;

  /** Selects node with provided value */
  select: (value: string) => void;

  /** Deselects node with provided value */
  deselect: (value: string) => void;

  /** Clears selected state */
  clearSelected: () => void;

  /** Sets selected state */
  setSelectedState: React.Dispatch<React.SetStateAction<string[]>>;

  /** Checks node with provided value */
  checkNode: (value: string) => void;

  /** Unchecks node with provided value */
  uncheckNode: (value: string) => void;

  /** Checks all nodes */
  checkAllNodes: () => void;

  /** Unchecks all nodes */
  uncheckAllNodes: () => void;

  /** Sets checked state */
  setCheckedState: React.Dispatch<React.SetStateAction<string[]>>;

  /** Returns all checked nodes with status */
  getCheckedNodes: () => CheckedNodeStatus[];

  /** Returns `true` if node with provided value is checked */
  isNodeChecked: (value: string) => boolean;

  /** Returns `true` if node with provided value is indeterminate */
  isNodeIndeterminate: (value: string) => boolean;

  /** Returns `true` if the node's children are currently being loaded */
  isNodeLoading: (value: string) => boolean;

  /** Returns the error from the last failed load attempt, or `null` */
  getNodeLoadError: (value: string) => Error | null;

  /** Programmatically triggers loading of a node's children */
  loadNode: (value: string) => Promise<void>;

  /** Clears the loaded cache for a node, causing it to re-fetch on next expand */
  invalidateNode: (value: string) => void;
}
```

You can pass the value returned by the `useTree` hook to the `tree` prop of the `Tree` component
to control tree state:



## Checked state

`Tree` can be used to display checked state with checkboxes.
To implement checked state, you need to render `Checkbox.Indicator` in the `renderNode` function:



To check/uncheck nodes, use `checkAllNodes` and `uncheckAllNodes` functions:



## Check strictly

By default, checking a parent node also checks all of its children (and unchecking works
the same way). Set `checkStrictly: true` on `useTree` to make each node's checked state
fully independent – checking a parent does not affect children and vice versa.
In this mode, `isNodeIndeterminate` always returns `false`.



## Initial expanded state

Expanded state is an object of `node.value` and boolean values that represent nodes expanded state.
To change initial expanded state, pass `initialExpandedState` to the `useTree` hook.
To generate expanded state from data with expanded nodes, you can use `getTreeExpandedState` function:
it accepts data and an array of expanded nodes values and returns expanded state object.

If `'*'` is passed as the second argument to `getTreeExpandedState`, all nodes will be expanded:

```tsx
import { getTreeExpandedState } from '@mantine/core';

// Expand two given nodes
getTreeExpandedState(data, ['src', 'src/components']);

// Expand all nodes
getTreeExpandedState(data, '*');
```



## Async loading

`Tree` supports lazy loading of children. Set `hasChildren: true` on a node without providing `children` –
when the node is expanded for the first time, `onLoadChildren` callback passed to `useTree` is called.
Use the `mergeAsyncChildren` utility to splice loaded children into your data:

```tsx
import { mergeAsyncChildren, Tree, TreeNodeData, useTree } from '@mantine/core';

function Demo() {
  const [data, setData] = useState<TreeNodeData[]>([
    { label: 'Documents', value: 'documents', hasChildren: true },
  ]);

  const tree = useTree({
    onLoadChildren: async (value) => {
      const children = await fetchChildren(value);
      setData((prev) => mergeAsyncChildren(prev, value, children));
    },
  });

  return <Tree data={data} tree={tree} />;
}
```

The `renderNode` payload includes `isLoading` and `loadError` fields that you can use
to display a loading indicator or an error message. Use `tree.invalidateNode(value)` to clear
the cache for a node and allow re-fetching on next expand.



## Search and filter

`Tree` does not include built-in search controls – search input and filtering logic are always external.
Use the `filterTreeData` utility to filter tree data based on a search query. The function
accepts tree data, a query string, and an optional custom filter function:

```tsx
import { filterTreeData } from '@mantine/core';

// Filter with default case-insensitive label matching
const filtered = filterTreeData(data, 'button');

// Filter with a custom function
const filtered = filterTreeData(data, 'btn', (query, node) =>
  node.value.includes(query)
);
```

The default filter compares the query against `node.label` (when it is a string) or `node.value` as a fallback.
Matching nodes and their ancestors are preserved in the result. You can provide a custom `TreeNodeFilter`
function for more advanced matching (for example, fuzzy search with fuse.js).

### Highlight matching nodes

In this example, all nodes remain visible and matching text is highlighted using the `Highlight` component
inside `renderNode`. Ancestor nodes of matching nodes are auto-expanded.



### Filter non-matching nodes

In this example, non-matching branches are removed from the tree using `filterTreeData`.
The filtered tree is auto-expanded with `getTreeExpandedState(filteredData, '*')`.



### Fuzzy search with fuse.js

You can pass a custom filter function to `filterTreeData` for fuzzy matching. This example
uses [fuse.js](https://www.fusejs.io/):



## Drag and drop

`Tree` component supports drag-and-drop reordering of nodes. To enable it, provide `onDragDrop` callback.
The callback receives an object with `draggedNode` (value of the dragged node), `targetNode` (value of the node it was dropped on),
and `position` (`'before'`, `'after'`, or `'inside'`).

Use `moveTreeNode` utility function to update the data based on the drag-and-drop result:

```tsx
import { moveTreeNode, Tree, TreeNodeData } from '@mantine/core';

function Demo() {
  const [data, setData] = useState<TreeNodeData[]>(initialData);

  return (
    <Tree
      data={data}
      onDragDrop={(payload) =>
        setData((current) => moveTreeNode(current, payload))
      }
    />
  );
}
```

When dragging over a node, the drop position is determined by cursor position:
- **Top zone** – drop before the target node (shown as a line above)
- **Middle zone** – drop inside the target node as a child (shown as a background highlight, only for nodes with children)
- **Bottom zone** – drop after the target node (shown as a line below)

Nodes cannot be dropped onto their own descendants.



### Restricting drop targets

Use the `allowDrop` prop to forbid certain drops. The callback receives the same payload as
`onDragDrop` (`draggedNode`, `targetNode`, `position`) and should return `false` to reject the drop.
When it returns `false`, the drop indicator is hidden and the browser displays the
"not-allowed" cursor, so the user gets visual feedback before releasing the mouse.



### Drag handle

By default, drag can be initiated from anywhere on a node. Set `withDragHandle` on `Tree` to
restrict drag initiation to an element that spreads `dragHandleProps` from the `renderNode`
payload. This is useful when a node contains interactive controls (inputs, buttons) that
would otherwise interfere with dragging.



## Connecting lines

Set `withLines` prop to display connecting lines showing parent-child relationships.
Lines adapt to `levelOffset` spacing automatically.



## Virtualization

`Tree` does not depend on any virtualization library – you supply one yourself.
Use the `flattenTreeData` utility to convert hierarchical data into a flat list of
visible nodes based on the current expanded state, then render each node with
`FlatTreeNode` which provides Tree's styles, aria attributes, click/keyboard
handlers, and `renderNode` support.

```tsx
import { FlatTreeNode, flattenTreeData, useTree } from '@mantine/core';

const tree = useTree();
const flatList = flattenTreeData(data, tree.expandedState);
// flatList is FlattenedTreeNodeData[] – spread each entry into FlatTreeNode
```

`FlatTreeNode` accepts the same behavioral props as `Tree` (`expandOnClick`,
`selectOnClick`, `expandOnSpace`, `checkOnSpace`, `renderNode`) and a `style` prop
for virtualizer positioning. The container element must have `data-tree-root`
and `role="tree"` attributes for keyboard navigation to work.



## Example: files tree



## Example: docs navigation editor

The example below combines drag-and-drop, search with `Highlight`, single-selection,
hover-revealed actions menu, a folder page count badge and `withLines` into a single
documentation-navigation editor. The `renderNode` callback receives every payload field
the component exposes, so most application-level UX can be assembled from these primitives.




## 源码参数参考

以下实现签名和类型从本地固定版本源码生成。保留可选属性、原生继承类型、联合分支和 never 限制；不会将其他设计系统的 API 混入。forwardRef 实现中的 ref 属于 React 引用，不是新增业务属性。此区域是原始类型参考，不是上游说明的全文翻译。


```ts
Tree(_props)
```



```ts
export interface TreeNodeData {
  label: React.ReactNode;
  value: string;
  nodeProps?: Record<string, any>;
  children?: TreeNodeData[];
  hasChildren?: boolean;
}

export interface RenderTreeNodePayload {
  /** Node level in the tree */
  level: number;

  /** `true` if the node is expanded, applicable only for nodes with `children` */
  expanded: boolean;

  /** `true` if the node has non-empty `children` array or `hasChildren` is set to `true` in the data */
  hasChildren: boolean;

  /** `true` if the node is selected */
  selected: boolean;

  /** `true` if the node is at the top-most level of the tree (level 1) */
  isRoot: boolean;

  /** `true` if the node's children are currently being loaded */
  isLoading: boolean;

  /** Error from the last failed load attempt, or `null` */
  loadError: Error | null;

  /** Node data from the `data` prop of `Tree` */
  node: TreeNodeData;

  /** Tree controller instance, return value of `useTree` hook */
  tree: TreeController;

  /** Props to spread into the root node element */
  elementProps: {
    className: string;
    style: React.CSSProperties;
    onClick: (event: React.MouseEvent) => void;
    'data-selected': boolean | undefined;
    'data-value': string;
    draggable?: boolean;
    onDragStart?: (event: React.DragEvent) => void;
    onDragOver?: (event: React.DragEvent) => void;
    onDragLeave?: (event: React.DragEvent) => void;
    onDrop?: (event: React.DragEvent) => void;
    onDragEnd?: (event: React.DragEvent) => void;
  };

  /** Props to spread into the drag handle element when `withDragHandle` is set on `Tree`.
   * `undefined` when `withDragHandle` is not enabled or drag-and-drop is disabled. */
  dragHandleProps: TreeDragHandleProps | undefined;
}

export type RenderNode = (payload: RenderTreeNodePayload) => React.ReactNode;

export type TreeStylesNames = 'root' | 'node' | 'subtree' | 'label';

export type TreeCssVariables = {
  root: '--level-offset';
};

export interface TreeProps extends BoxProps, StylesApiProps<TreeFactory>, ElementProps<'ul'> {
  /** Data used to render nodes */
  data: TreeNodeData[];

  /** Horizontal padding of each subtree level, key of `theme.spacing` or any valid CSS value @default 'lg' */
  levelOffset?: MantineSpacing;

  /** If set, tree node with children is expanded on click @default true */
  expandOnClick?: boolean;

  /** If set, tree node with children is expanded on space key press @default true */
  expandOnSpace?: boolean;

  /** If set, tree node is checked on space key press @default false */
  checkOnSpace?: boolean;

  /** If set, tree node is selected on click @default false */
  selectOnClick?: boolean;

  /** Use-tree hook instance that can be used to manipulate component state */
  tree?: TreeController;

  /** A function to render tree node label */
  renderNode?: RenderNode;

  /** If set, selection is cleared when user clicks outside of the tree @default false */
  clearSelectionOnOutsideClick?: boolean;

  /** If set, tree nodes range can be selected with click when `Shift` key is pressed @default true */
  allowRangeSelection?: boolean;

  /** If set, subtree content is kept mounted when collapsed. React 19 `Activity` is used to preserve state. @default false */
  keepMounted?: boolean;

  /** Called when a node is dropped on another node, enables drag-and-drop when provided */
  onDragDrop?: (payload: TreeDragDropPayload) => void;

  /** Called for each potential drop target to determine whether a drop is allowed.
   * When it returns `false`, the drop indicator is hidden and the drop is rejected. */
  allowDrop?: TreeAllowDrop;

  /** If set, drag-and-drop must be initiated from an element that spreads `dragHandleProps`
   * from the `renderNode` payload, rather than anywhere on the node. @default false */
  withDragHandle?: boolean;

  /** If set, connecting lines are rendered showing parent-child relationships @default false */
  withLines?: boolean;
}

export type TreeFactory = Factory<{
  props: TreeProps;
  ref: HTMLUListElement;
  stylesNames: TreeStylesNames;
  vars: TreeCssVariables;
}>;

export type Mod = Record<string, any> | string;

export type BoxMod = Mod | Mod[] | BoxMod[];

export interface BoxProps extends MantineStyleProps {
  /** Class added to the root element, if applicable */
  className?: string | undefined;

  /** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
  style?: MantineStyleProp;

  /** CSS variables defined on root component element */
  __vars?: CssVarsProp;

  /** `size` property passed down the HTML element */
  __size?: string;

  /** Breakpoint above which the component is hidden with `display: none` */
  hiddenFrom?: MantineBreakpoint;

  /** Breakpoint below which the component is hidden with `display: none` */
  visibleFrom?: MantineBreakpoint;

  /** Determines whether component should be hidden in light color scheme with `display: none` */
  lightHidden?: boolean;

  /** Determines whether component should be hidden in dark color scheme with `display: none` */
  darkHidden?: boolean;

  /** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
  mod?: BoxMod;
}

export type ElementProps<
  ElementType extends React.ElementType,
  PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;

export interface CSSProperties extends React.CSSProperties {
  [key: string]: any;
}

type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);

export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;

export type CssVariable = `--${string}`;

export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;

export type CssVars<Variable extends string = CssVariable> =
  | CssVariables<Variable>
  | ((theme: MantineTheme) => CssVariables<Variable>)
  | CssVars<Variable>[];

export type CssVarsProp<Variable extends string = CssVariable> =
  | CssVars<Variable>
  | CssVars<Variable>[];
```


### 原生与外部类型

以下外部类型仍由 React 或资源自身依赖定义，没有手工展开为虚构属性。实际可用类型以安装版本的 TypeScript 检查为准。


```ts
Factory<{
  props: TreeProps;
  ref: HTMLUListElement;
  stylesNames: TreeStylesNames;
  vars: TreeCssVariables;
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
StylesApiProps<TreeFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
React.ReactNode
MantineSpacing
TreeController
Error
React.MouseEvent
React.DragEvent
TreeDragHandleProps
TreeDragDropPayload
TreeAllowDrop
HTMLUListElement
```

---

JGuang 全部资源索引: https://jiguang132.com/llms.txt
