@cloud-ru/ds-tree

中文文档
1.0.45发布于 2026/11/11Apache-2.0TypeScriptESM + CJS近 30 天下载 6623

Иерархический список с раскрывающимися узлами, выбором (single/multiple), кастомными иконками, контекстными действиями и асинхронной подгрузкой потомков.

原始使用文档
文档目录15 节
  1. Когда использовать
  2. Анатомия
  3. Selection mode (default — без выбора)
  4. Size (default m )
  5. Show lines (default false )
  6. Show icons (default true )
  7. Установка
  8. Примеры использования
  9. Базовый просмотр
  10. Одиночный выбор
  11. Множественный выбор
  12. Кастомные иконки
  13. Контекстные действия
  14. Асинхронная подгрузка
  15. Props

Tree

@cloud-ru/ds-tree — Иерархический список с раскрывающимися узлами, выбором (single/multiple), кастомными иконками, контекстными действиями и асинхронной подгрузкой потомков.

Дерево с раскрывающимися узлами для навигации по иерархии (файлы, регионы, теги, ACL). Поддерживает одиночный и множественный выбор, кастомные иконки и асинхронную подгрузку.

Когда использовать

  • Реальная иерархия с переменной глубиной (≥ 3 уровней): файловая система, dataset-навигация, организационные структуры.
  • Нужно одновременно видеть несколько уровней, а не цепочку breadcrumb'ов.
  • Большие наборы лениво подгружаются — onDataLoad доставляет потомков по раскрытию.

Когда не нужен:

  • Плоский список 5–20 элементов:
    • используйте List или Select.
  • Фиксированные 2–7 разделов одного уровня:
    • используйте Tabs.
  • Пошаговый процесс:
    • используйте Stepper.

Анатомия

Selection mode (default — без выбора)

  • undefined — read-only режим, узлы только раскрываются.
  • 'single' — один выбранный узел, рендерится Radio (если showToggle) либо подсвечивается строка.
  • 'multiple' — множественный выбор через Checkbox на каждом узле; родитель показывает indeterminate-состояние от детей.

Size (default m)

  • s — плотные списки, sidebar-навигация (16px-иконки).
  • m — стандартная плотность для админок.
  • l — крупные посадочные/landing-страницы.

Текущая ось size соответствует только compact-плотности Figma-мастера (compact/S=24px, compact/M=32px, compact/L=40px row-h). Альтернативная density comfort (40/48/56px row-h из Figma-узла 26653:105451) не реализована — в публичном API нет пропа density. Если потребитель просит «comfort»-плотность, на текущей версии её получить нельзя. Track: расширить constants.ts осью DENSITY и пересчитать токены density-size-icon-* на обе плотности.

Show lines (default false)

Полосы вложенности └─, соединяющие родителя с его детьми. Включай для глубоких структур (3+ уровней) — без них теряется визуальная иерархия.

Show icons (default true)

Folder/file-иконки слева от названия. Отключай, если узел сам по себе уже визуально отличим (например, цветной чип-роль).

Установка

pnpm add @cloud-ru/ds-tree
import { SELECTION_MODE, SIZE, Tree } from '@cloud-ru/ds-tree'

Примеры использования

Базовый просмотр

Read-only дерево с раскрытием через onExpand

import { Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const DATA: TreeNodeProps[] = [
  {
    id: 'fruits',
    title: 'Fruits',
    nested: [
      { id: 'apple', title: 'Apple' },
      { id: 'banana', title: 'Banana' },
      {
        id: 'citrus',
        title: 'Citrus',
        nested: [
          { id: 'orange', title: 'Orange' },
          { id: 'lemon', title: 'Lemon' },
        ],
      },
    ],
  },
  {
    id: 'vegetables',
    title: 'Vegetables',
    nested: [
      { id: 'carrot', title: 'Carrot' },
      { id: 'potato', title: 'Potato' },
    ],
  },
];

export function Basic() {
  const [expanded, setExpanded] = useState<string[]>(['fruits']);

  return <Tree data={DATA} expandedNodes={expanded} onExpand={setExpanded} showLines />;
}

Одиночный выбор

selectionMode=single + showToggle: один Radio на дерево

import { SELECTION_MODE, Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const DATA: TreeNodeProps[] = [
  {
    id: 'docs',
    title: 'Documents',
    nested: [
      { id: 'specs', title: 'Specs.pdf' },
      { id: 'budget', title: 'Budget.xlsx' },
      { id: 'roadmap', title: 'Roadmap.md' },
    ],
  },
  {
    id: 'media',
    title: 'Media',
    nested: [
      { id: 'logo', title: 'Logo.svg' },
      { id: 'hero', title: 'Hero.png' },
    ],
  },
];

export function SingleSelect() {
  const [selected, setSelected] = useState<string | undefined>('specs');
  const [expanded, setExpanded] = useState<string[]>(['docs', 'media']);

  return (
    <Tree
      data={DATA}
      selectionMode={SELECTION_MODE.Single}
      selected={selected}
      onSelect={setSelected}
      expandedNodes={expanded}
      onExpand={setExpanded}
      showToggle
      showLines
    />
  );
}

Множественный выбор

selectionMode=multiple: Checkbox на каждом узле, у родителя — indeterminate

import { SELECTION_MODE, Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const DATA: TreeNodeProps[] = [
  {
    id: 'compute',
    title: 'Compute',
    nested: [
      { id: 'vm', title: 'Virtual machines' },
      { id: 'k8s', title: 'Kubernetes' },
      { id: 'serverless', title: 'Serverless' },
    ],
  },
  {
    id: 'storage',
    title: 'Storage',
    nested: [
      { id: 's3', title: 'Object storage' },
      { id: 'block', title: 'Block storage' },
    ],
  },
];

export function MultiSelect() {
  const [selected, setSelected] = useState<string[]>(['vm', 's3']);
  const [expanded, setExpanded] = useState<string[]>(['compute', 'storage']);

  return (
    <Tree
      data={DATA}
      selectionMode={SELECTION_MODE.Multiple}
      selected={selected}
      onSelect={setSelected}
      expandedNodes={expanded}
      onExpand={setExpanded}
      showLines
    />
  );
}

Кастомные иконки

icon у листа, expandedIcon/collapsedIcon у папки

import { FileSVG, FolderOpenSVG, FolderSVG } from '@cloud-ru/ds-icons/interface/system';
import { Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const DATA: TreeNodeProps[] = [
  {
    id: 'src',
    title: 'src',
    expandedIcon: <FolderOpenSVG />,
    collapsedIcon: <FolderSVG />,
    nested: [
      { id: 'app.tsx', title: 'App.tsx', icon: <FileSVG /> },
      { id: 'main.tsx', title: 'main.tsx', icon: <FileSVG /> },
      {
        id: 'components',
        title: 'components',
        expandedIcon: <FolderOpenSVG />,
        collapsedIcon: <FolderSVG />,
        nested: [
          { id: 'button.tsx', title: 'Button.tsx', icon: <FileSVG /> },
          { id: 'tree.tsx', title: 'Tree.tsx', icon: <FileSVG /> },
        ],
      },
    ],
  },
];

export function WithIcons() {
  const [expanded, setExpanded] = useState<string[]>(['src', 'components']);

  return <Tree data={DATA} expandedNodes={expanded} onExpand={setExpanded} showLines />;
}

Контекстные действия

nodeActions возвращает массив пунктов меню — открывается по hover/focus

import { Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const DATA: TreeNodeProps[] = [
  {
    id: 'project',
    title: 'Project Alpha',
    nested: [
      { id: 'overview.md', title: 'overview.md' },
      { id: 'changelog.md', title: 'changelog.md' },
    ],
  },
  { id: 'project-beta', title: 'Project Beta', nested: [{ id: 'todo.md', title: 'todo.md' }] },
];

export function WithActions() {
  const [expanded, setExpanded] = useState<string[]>(['project']);
  const [lastItemAction, setLastItemAction] = useState<string>('');

  const nodeActions = (node: TreeNodeProps) => [
    { id: 'rename', content: { label: `Rename ${node.id}` }, onClick: () => setLastItemAction(`rename ${node.id}`) },
    { id: 'delete', content: { label: 'Delete' }, onClick: () => setLastItemAction(`delete ${node.id}`) },
  ];

  return (
    <>
      <Tree data={DATA} expandedNodes={expanded} onExpand={setExpanded} nodeActions={nodeActions} showLines />
      <small>Last clicked item action: {lastItemAction || '—'}</small>
    </>
  );
}

Асинхронная подгрузка

onDataLoad подтягивает детей при первом раскрытии

import { Tree, TreeNodeProps } from '@cloud-ru/ds-tree';
import { useState } from 'react';

const INITIAL: TreeNodeProps[] = [
  { id: 'region-eu', title: 'EU region', nested: [] },
  { id: 'region-ru', title: 'RU region', nested: [] },
];

export function AsyncLoad() {
  const [data, setData] = useState(INITIAL);
  const [expanded, setExpanded] = useState<string[]>([]);

  const onDataLoad = async (node: TreeNodeProps) => {
    await new Promise(r => setTimeout(r, 600));
    setData(prev =>
      prev.map((item): TreeNodeProps => {
        if (item.id !== node.id) return item;
        return {
          id: item.id,
          title: item.title as string,
          nested: [
            { id: `${item.id}-vm1`, title: 'vm-prod-01' },
            { id: `${item.id}-vm2`, title: 'vm-prod-02' },
            { id: `${item.id}-vm3`, title: 'vm-staging-01' },
          ],
        };
      }),
    );
  };

  return <Tree data={data} expandedNodes={expanded} onExpand={setExpanded} onDataLoad={onDataLoad} showLines />;
}

Props

TreeProps

PropTypeDefaultDescription
classNamestring—CSS-класс
dataTreeNodeProps—Данные для отрисовки
data-test-idstring—
expandedNodesTreeNodeId—Состояние для раскрытых элементов
nodeActions((node: TreeNodeProps) => Item[])—Дополнительные действия для элемента-потомка
onDataLoad((node: TreeNodeProps) => Promise<unknown>)—Колбэк для асинхронной загрузки данных при раскрытии дерева
onExpand((expandedKeys: string[], node: TreeNodeProps) => void)—Колбэк при раскрытии/закрытии элементов
onNodeClickOnNodeClick—Обработчик клика по элементу дерева
onSelect((selectedKeys: string[], node: TreeNodeProps) => void) | ((selectedKey: string, node: TreeNodeProps) => void)—Колбэк при изменении выбраных элементов:

- При selectionMode=Multiple - возвращает массив строк

- При selectionMode=Single - возвращает строку
parentActions((node: TreeNodeProps) => Item[])—Дополнительные действия для элемента-родителя
selectedstring | string[]—Состояние для выбраных элементов:

- При selectionMode=Multiple - принимает массив строк

- При selectionMode=Single - принимает строку
selectionMode"multiple" | "single"—Режим выбора элементов:

- Single - одиночный выбор

- Multi - множественный выбор

- undefined - без выбора
showIconsbooleantrueФлаг отвечающий за отображение иконок у элементов дерева
showLinesbooleantrueФлаг отвечающий за отображение линий вложенности
showToggleboolean—
size"l" | "m" | "s"mРазмер строк дерева (Figma variant size).
titleMaxLinesnumber1Максимальное число строк заголовка узла до обрезки многоточием.
Значение больше 1 разрешает перенос; строка растёт по высоте.
  • OnNodeClick = (node: TreeNodeProps, e: MouseEvent) => void

  • Size = "l" | "m" | "s"

  • TreeNodeId = string

  • TreeNodeProps = ChildTreeNode | ParentTreeNode

版本与发布记录

@cloud-ru/ds-tree 共发布 176 个版本,首次发布于 2026/08/18;过去 12 个月发布了 176 个版本。

版本发布日期距今
1.0.45latest2026/11/11今天
1.0.45-preview-e8fe6b56.0preview2026/11/11今天
1.0.45-preview-c0e895ed.02026/11/11今天
1.0.45-preview-90b10112.02026/11/11今天
1.0.45-preview-445f2191.02026/11/111 天前
1.0.45-preview-fd7dd437.02026/11/101 天前

关于 @cloud-ru/ds-tree 的常见问题

@cloud-ru/ds-tree 是什么?

Иерархический список с раскрывающимися узлами, выбором (single/multiple), кастомными иконками, контекстными действиями и асинхронной подгрузкой потомков.

如何安装 @cloud-ru/ds-tree?

在项目目录运行 npm install @cloud-ru/ds-tree,或使用 pnpm add / yarn add / bun add。

@cloud-ru/ds-tree 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@cloud-ru/ds-tree 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@cloud-ru/ds-tree 可以商用吗?

@cloud-ru/ds-tree 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@cloud-ru/ds-tree 的下载量有多少?

根据 npm 官方下载统计,@cloud-ru/ds-tree 在 2026/10/10 – 2026/11/08 这 30 天内被下载 6,623 次,日均约 221 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cloud-ru/ds-tree 还在维护吗?

最新版本 1.0.45 发布于 2026/11/11(今天);至今共发布 176 个版本,其中过去 12 个月发布了 176 个;首次发布于 2026/08/18。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。