@sberbusiness/triplex-next-mcp-server 1.42.0-beta → 1.44.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/components/AlertProcess.md +3 -2
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/Button.md +1 -0
- package/bundle/components/Checkbox.md +7 -1
- package/bundle/components/CheckboxTreeExtended.md +149 -248
- package/bundle/components/CheckboxXGroup.md +2 -0
- package/bundle/components/CheckboxYGroup.md +7 -2
- package/bundle/components/ChipGroup.md +195 -126
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/Island.md +36 -1
- package/bundle/components/LightBox.md +14 -3
- package/bundle/components/MediaWidth.md +7 -1
- package/bundle/components/Notification.md +21 -3
- package/bundle/components/Overlay.md +24 -1
- package/bundle/components/Pagination.md +12 -13
- package/bundle/components/SliderExtended.md +358 -222
- package/bundle/components/TableBasic.md +2294 -0
- package/bundle/components/TabsExtended.md +380 -194
- package/bundle/components/Tag.md +259 -78
- package/bundle/components/TopOverlay.md +366 -90
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/components/UnorderedList.md +254 -0
- package/bundle/components/UnorderedListExtended.md +255 -0
- package/bundle/components/WindowResizeListener.md +249 -0
- package/bundle/guides/commits.md +121 -15
- package/bundle/guides/stories.md +1 -1
- package/bundle/manifest.json +46 -20
- package/bundle/release-notes/1.43.0.mdx +87 -0
- package/bundle/release-notes/1.44.0.mdx +120 -0
- package/bundle/release-notes/1.45.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: TreeViewNode
|
|
3
|
+
category: TreeView
|
|
4
|
+
related: [TreeView]
|
|
5
|
+
tokens: []
|
|
6
|
+
stories: stories/TreeView/TreeView.stories.tsx
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# TreeViewNode
|
|
11
|
+
|
|
12
|
+
## Назначение
|
|
13
|
+
|
|
14
|
+
Узел визуального дерева `TreeView` (доступен также как `TreeView.Node`). Рендерит
|
|
15
|
+
`<li role="treeitem">` с `aria-expanded` и `tabIndex`, регистрирует себя в
|
|
16
|
+
абстрактном дереве `TreeView`, обрабатывает `focus`/`blur` для перемещения
|
|
17
|
+
активной ноды с клавиатуры и отдаёт потребителю пять provide-props через
|
|
18
|
+
render-функцию `children`.
|
|
19
|
+
|
|
20
|
+
Собственного оформления у ноды нет: она добавляет только семантику и сброс
|
|
21
|
+
маркеров списка — всю разметку узла (шеврон, подпись, линии-коннекторы,
|
|
22
|
+
подсветку активного состояния) рисует потребитель.
|
|
23
|
+
|
|
24
|
+
Используй когда: строишь дерево на `TreeView` и нужен полный контроль над
|
|
25
|
+
разметкой узла.
|
|
26
|
+
|
|
27
|
+
Не используй когда:
|
|
28
|
+
- нужно типовое дерево «папка/файл» с шевроном, hover и focus-visible — бери
|
|
29
|
+
`CollapsibleTree`;
|
|
30
|
+
- нужен свой UI узла, но с анимацией раскрытия и controlled/uncontrolled
|
|
31
|
+
состоянием — бери `CollapsibleTreeExtended`, он оборачивает `TreeView.Node`
|
|
32
|
+
и добавляет accordion-логику;
|
|
33
|
+
- узел нужен вне `TreeView` — вне провайдера контекста нода получает
|
|
34
|
+
`initialTreeContext`, где `registerNode` и остальные методы пустые: `<li>`
|
|
35
|
+
отрисуется, но в дерево нода не попадёт, `tabIndex` останется `-1`,
|
|
36
|
+
`hasChildNodes` всегда будет `false` (дети не регистрируются), а
|
|
37
|
+
`isLastNode` — всегда `true` (у ноды нет родителя).
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Варианты и props
|
|
42
|
+
|
|
43
|
+
Экспортируемый `TreeViewNode` — это `withTreeViewContext(TreeViewNodeWithContext)`,
|
|
44
|
+
то есть class-компонент, обёрнутый HOC-ом контекста. Prop `treeViewContext`
|
|
45
|
+
подставляет HOC, снаружи его передавать не нужно.
|
|
46
|
+
|
|
47
|
+
### Обязательные props
|
|
48
|
+
|
|
49
|
+
| Prop | Тип | Описание |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `id` | `string` | Идентификатор ноды, уникальный в пределах дерева. По нему абстрактное дерево ищет и связывает ноды, на него ссылаются `prevNodeId` / `nextNodeId` соседей |
|
|
52
|
+
| `children` | `(props: ITreeViewNodeProvideProps) => JSX.Element` | Render-функция разметки узла. Не ReactNode: нода отдаёт своё состояние только через аргумент |
|
|
53
|
+
|
|
54
|
+
### Опциональные props
|
|
55
|
+
|
|
56
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| `opened` | `boolean` | `false` | Состояние раскрытия. Синхронизируется в абстрактное дерево в конструкторе и далее в `componentDidUpdate` при смене значения |
|
|
59
|
+
| `prevNodeId` | `string` | — | Id ноды, после которой вставить текущую в абстрактное дерево |
|
|
60
|
+
| `nextNodeId` | `string` | — | Id ноды, перед которой вставить текущую в абстрактное дерево |
|
|
61
|
+
| `...HTMLAttributes` | — | — | Уходят на `<li>`. `className` мерджится с собственным классом, `onFocus` / `onBlur` вызываются после внутренних обработчиков |
|
|
62
|
+
|
|
63
|
+
`ITreeViewNodeProps` расширяет `Omit<React.HTMLAttributes<HTMLElement>, "children">` —
|
|
64
|
+
`children` переопределён под render-функцию намеренно.
|
|
65
|
+
|
|
66
|
+
### Provide-props render-функции (`ITreeViewNodeProvideProps`)
|
|
67
|
+
|
|
68
|
+
| Поле | Описание |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `activeNode` | Нода активна при перемещении с клавиатуры. Подсветку рисует потребитель |
|
|
71
|
+
| `openedNode` | Нода раскрыта — значение из абстрактного дерева, а не из prop `opened` |
|
|
72
|
+
| `hasChildNodes` | У ноды есть **зарегистрированные** дочерние ноды |
|
|
73
|
+
| `isLastNode` | Нода последняя в дереве целиком (последняя на своём уровне и все её предки — последние). Используется для линий-коннекторов |
|
|
74
|
+
| `setOpenedNode(opened)` | Пишет флаг раскрытия в абстрактную ноду и инкрементит `updateCount` в `TreeView` для перерендера. Uncontrolled-путь: prop `opened` при этом не меняется |
|
|
75
|
+
|
|
76
|
+
### Порядок нод и `prevNodeId` / `nextNodeId`
|
|
77
|
+
|
|
78
|
+
Абстрактное дерево заполняется в **конструкторах** нод, то есть в порядке
|
|
79
|
+
рендера. `prevNodeId` / `nextNodeId` позволяют вставить ноду в произвольное
|
|
80
|
+
место уровня: порядок в абстрактном дереве (а значит `isLastNode` и порядок
|
|
81
|
+
обхода стрелками) может отличаться от порядка в разметке. Ссылаться можно
|
|
82
|
+
только на **уже зарегистрированную** ноду: к моменту конструктора текущей ноды
|
|
83
|
+
конструктор соседа должен был отработать — монтирование при этом не требуется,
|
|
84
|
+
оно происходит позже. Id соседей выводят из массива данных
|
|
85
|
+
`CollapsibleTree` и `CheckboxTree`; `CollapsibleTreeExtended` своего массива не
|
|
86
|
+
имеет и пробрасывает `prevNodeId` / `nextNodeId` как есть от потребителя.
|
|
87
|
+
|
|
88
|
+
Промаха по id компонент не сигнализирует, но два случая промаха ведут себя
|
|
89
|
+
по-разному. Ноду с указанным id ищут **в поддереве текущего родителя**, а не по
|
|
90
|
+
всему дереву.
|
|
91
|
+
|
|
92
|
+
- **Id не найден** — нода добавляется в конец своего уровня, то есть в порядке
|
|
93
|
+
рендера. Детерминированно и безопасно; это штатный режим, а не ошибка.
|
|
94
|
+
Именно так работает `nextNodeId` в `CollapsibleTree` и `CheckboxTree`: они
|
|
95
|
+
передают id следующей ноды из массива данных, а та на момент конструктора
|
|
96
|
+
текущей ещё не зарегистрирована — промах и откат в порядок рендера здесь
|
|
97
|
+
происходят всегда.
|
|
98
|
+
- **Id найден, но это не прямой ребёнок того же родителя** — позиция
|
|
99
|
+
определяется индексной арифметикой `splice` в `AbstractTreeNode.addChild`.
|
|
100
|
+
Это **деталь текущей реализации, а не контракт**: тестами не закреплена,
|
|
101
|
+
класс общий для всех абстрактных деревьев, полагаться нельзя.
|
|
102
|
+
|
|
103
|
+
Правильное употребление одно: ссылаться на уже зарегистрированную соседнюю ноду
|
|
104
|
+
того же уровня.
|
|
105
|
+
|
|
106
|
+
### Раскрытие и регистрация детей
|
|
107
|
+
|
|
108
|
+
`hasChildNodes` вычисляется по абстрактному дереву, поэтому дочерние ноды
|
|
109
|
+
должны быть **смонтированы**. Если размонтировать `TreeView.Group` в свёрнутом
|
|
110
|
+
состоянии, родитель получит `hasChildNodes === false` и, например, не отрисует
|
|
111
|
+
шеврон. Рабочий приём (используется в stories и в `CollapsibleTreeExtended`) —
|
|
112
|
+
держать группу в разметке всегда, скрывая её стилями.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Дизайн-токены
|
|
117
|
+
|
|
118
|
+
Собственных CSS-переменных нода не определяет. Класс `treeViewNode` в
|
|
119
|
+
`styles/TreeView.module.less` задаёт только сброс отступов и маркера списка
|
|
120
|
+
плюс `outline: none` — focus-стиль рисует потребитель.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Инварианты
|
|
125
|
+
|
|
126
|
+
- **`TreeViewNodeWithContext` — class-компонент, и `forwardRef` на нём нет.**
|
|
127
|
+
Регистрация в абстрактном дереве происходит в **конструкторе** (до маунта
|
|
128
|
+
детей), а снятие — в `componentWillUnmount`; порядок этих вызовов и есть
|
|
129
|
+
порядок нод в дереве. Перевод на функциональный компонент меняет момент
|
|
130
|
+
регистрации и ломает `prevNodeId` / `nextNodeId`. Не мигрировать без
|
|
131
|
+
отдельного решения мейнтейнера.
|
|
132
|
+
- **Корневой DOM-элемент — `<li>`.** Менять тег нельзя: `role="treeitem"`
|
|
133
|
+
валиден только внутри `<ul role="tree">` / `<ul role="group">`.
|
|
134
|
+
- **`role="treeitem"` и `aria-expanded` выставляются после `{...props}`** —
|
|
135
|
+
семантика узла часть контракта компонента, потребитель её не переопределяет.
|
|
136
|
+
`tabIndex` остаётся **до** спреда: roving tabindex — поведение, а не
|
|
137
|
+
семантика, и потребитель при желании может задать его сам. Но переданный
|
|
138
|
+
`tabIndex` побеждает навсегда: `TreeView.updateTabIndexNodes` продолжит
|
|
139
|
+
пересчитывать значение в абстрактной ноде, на DOM это уже не повлияет. Поставив
|
|
140
|
+
`tabIndex={0}` всем нодам, потребитель получит не свой roving tabindex, а
|
|
141
|
+
дерево, где каждая нода — отдельная остановка Tab. Переопределять — только если
|
|
142
|
+
берёшь управление фокусом целиком на себя.
|
|
143
|
+
- **`event.stopPropagation()` в `handleFocus` / `handleBlur` обязателен.**
|
|
144
|
+
`focusin` всплывает, и без остановки активной становилась бы не текущая нода,
|
|
145
|
+
а её родитель. Пользовательские `onFocus` / `onBlur` вызываются после
|
|
146
|
+
внутренней логики — порядок менять нельзя.
|
|
147
|
+
- **`children` — функция, а не `ReactNode`.** Смена сигнатуры — breaking change
|
|
148
|
+
для всей цепочки потребителей. Напрямую `TreeView.Node` рендерит только
|
|
149
|
+
`CollapsibleTreeExtended`; `CollapsibleTree` строится поверх него, а
|
|
150
|
+
`CheckboxTree` доходит до ноды через `CheckboxTreeExtended` →
|
|
151
|
+
`CollapsibleTreeExtended`.
|
|
152
|
+
- **Barrel `src/components/TreeView/index.ts`** реэкспортирует модуль ноды
|
|
153
|
+
целиком (`export * from "./components/TreeViewNode"`), поэтому публичны и
|
|
154
|
+
`TreeViewNode`, и класс `TreeViewNodeWithContext`, и типы
|
|
155
|
+
`ITreeViewNodeProps`, `ITreeViewNodeProvideProps`,
|
|
156
|
+
`ITreeViewNodePropsWithContext` — всё это через `src/components/index.ts`
|
|
157
|
+
доходит до корня пакета. Состав и имена менять нельзя.
|
|
158
|
+
- **`displayName` класса — `"TreeViewNodeWithContext"`**, у экспортируемой
|
|
159
|
+
обёртки — `"WithTreeViewContext(TreeViewNodeWithContext)"` (добавляет HOC).
|
|
160
|
+
Значения видны в React DevTools и в snapshot-тестах.
|
|
161
|
+
- **Абстрактная нода мутируемая.** `this.abstractNode` меняется на месте, и сам
|
|
162
|
+
по себе перерендер не вызывает: любая мутация должна идти через методы
|
|
163
|
+
контекста (`setOpenedNode`, `setActiveNode`, `registerNode`, `removeNode`),
|
|
164
|
+
которые инкрементят `updateCount` в состоянии `TreeView`. Прямая запись в
|
|
165
|
+
`abstractNode` в обход контекста оставит ноду с устаревшими
|
|
166
|
+
`hasChildNodes` / `isLastNode`.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Accessibility
|
|
171
|
+
|
|
172
|
+
Ориентир — WAI-ARIA Authoring Practices,
|
|
173
|
+
[treeview-2a](https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html).
|
|
174
|
+
|
|
175
|
+
- Узел — `<li role="treeitem">` с `aria-expanded`, отражающим состояние
|
|
176
|
+
раскрытия из абстрактного дерева. Вложенные ноды оборачиваются в
|
|
177
|
+
`TreeView.Group` (`<ul role="group">`).
|
|
178
|
+
- **Roving tabindex.** `tabIndex` нода читает из абстрактной ноды
|
|
179
|
+
(`getTabIndex()`): первая нода первого уровня получает `0`, остальные `-1`.
|
|
180
|
+
Пересчитывает значения `TreeView` при регистрации и удалении нод — в дерево
|
|
181
|
+
можно попасть одним Tab.
|
|
182
|
+
- **Активная нода ≠ DOM-фокус.** Активной нода становится по `focus`, а стрелки
|
|
183
|
+
(`ArrowDown` / `ArrowUp`, обрабатываются в `TreeView` на `window`) двигают
|
|
184
|
+
только флаг активности в абстрактном дереве — DOM-фокус остаётся на исходном
|
|
185
|
+
узле. Потребитель обязан визуально отрисовать активное состояние по
|
|
186
|
+
provide-prop `activeNode`, иначе клавиатурная навигация не видна.
|
|
187
|
+
- `blur` снимает активность; когда активной ноды в дереве не остаётся,
|
|
188
|
+
`TreeView` отписывает слушатель стрелок.
|
|
189
|
+
- Раскрытие/сворачивание по клавиатуре (`ArrowRight` / `ArrowLeft`,
|
|
190
|
+
`Home` / `End`) нода **не** реализует — это на потребителе, в его разметке
|
|
191
|
+
узла.
|
|
192
|
+
- Текстовые подписи и `aria-label` нода не хардкодит: библиотека мультиязычная,
|
|
193
|
+
всю разметку узла даёт потребитель. `aria-label` на дерево вешается на
|
|
194
|
+
`TreeView`.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Связанные компоненты
|
|
199
|
+
|
|
200
|
+
- `TreeView` (`src/components/TreeView`) — родительский компонент и владелец
|
|
201
|
+
абстрактного дерева: регистрирует ноды, считает `tabIndex`, обрабатывает
|
|
202
|
+
стрелки. Нода без него не работает.
|
|
203
|
+
- `TreeViewGroup` (`TreeView.Group`) — `<ul role="group">`, обёртка вложенных
|
|
204
|
+
нод. Тривиальная обёртка (`forwardRef` + `clsx` + спред), отдельного AI.md
|
|
205
|
+
нет.
|
|
206
|
+
- `CollapsibleTreeExtended` (`src/components/CollapsibleTreeExtended`) — обёртка
|
|
207
|
+
над `TreeView.Node`, добавляющая анимацию раскрытия и
|
|
208
|
+
controlled/uncontrolled состояние. Берут вместо голой ноды, когда свой UI
|
|
209
|
+
узла нужен, а возиться с состоянием — нет.
|
|
210
|
+
- `CollapsibleTree` (`src/components/CollapsibleTree`) и `CheckboxTree`
|
|
211
|
+
(`src/components/CheckboxTree`) — готовые деревья поверх ноды; оба
|
|
212
|
+
протаскивают `prevNodeId` / `nextNodeId` из массива данных.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Stories
|
|
217
|
+
|
|
218
|
+
Отдельных stories у ноды нет: без `TreeView` она не рендерится, а собственного
|
|
219
|
+
оформления у неё нет — все её состояния показаны в stories родителя. Ниже —
|
|
220
|
+
только те, что демонстрируют API самой ноды; полный список историй семейства
|
|
221
|
+
(`Playground`, `Default`) — в [TreeView-ai.md](./TreeView-ai.md).
|
|
222
|
+
|
|
223
|
+
Основные истории: `stories/TreeView/TreeView.stories.tsx`
|
|
224
|
+
Файлы примеров: `stories/TreeView/examples/`
|
|
225
|
+
|
|
226
|
+
| Story | Example file | Что демонстрирует |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `Nested` | `Nested.tsx` | Вложенность через `TreeView.Group`, prop `opened`, provide-props `openedNode` и `hasChildNodes` |
|
|
229
|
+
| `Keyboard navigation` | `KeyboardNavigation.tsx` | Перемещение `activeNode` стрелками, подсветка активной ноды, `isLastNode` |
|
|
230
|
+
| `VisualTests` | `VisualTests.tsx` | Свёрнутая и раскрытая нода, узел с детьми и лист, `isLastNode`; активная нода снимается через `play` |
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## История изменений
|
|
235
|
+
|
|
236
|
+
| Дата | Изменение |
|
|
237
|
+
|---|---|
|
|
238
|
+
| 2026-08-26 | Создан документ. Описание ноды вынесено из `TreeView-ai.md`. Добавлен unit-тест на `prevNodeId` — единственный публичный prop ноды, не покрытый тестами |
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Примеры использования
|
|
243
|
+
|
|
244
|
+
### Nested
|
|
245
|
+
|
|
246
|
+
`stories/TreeView/examples/Nested.tsx`
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
import React from "react";
|
|
250
|
+
import clsx from "clsx";
|
|
251
|
+
import { CaretrightStrokeSrvIcon24 } from "@sberbusiness/icons-next";
|
|
252
|
+
import { ETextSize, Text, TreeView } from "@sberbusiness/triplex-next";
|
|
253
|
+
import "./Nested.less";
|
|
254
|
+
|
|
255
|
+
interface INodeData {
|
|
256
|
+
id: string;
|
|
257
|
+
label: string;
|
|
258
|
+
children?: INodeData[];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const nodes: INodeData[] = [
|
|
262
|
+
{
|
|
263
|
+
id: "folder-1",
|
|
264
|
+
label: "Folder text",
|
|
265
|
+
children: [
|
|
266
|
+
{ id: "file-1-1", label: "File text" },
|
|
267
|
+
{ id: "file-1-2", label: "File text" },
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
id: "folder-2",
|
|
272
|
+
label: "Folder text",
|
|
273
|
+
children: [
|
|
274
|
+
{
|
|
275
|
+
id: "folder-2-1",
|
|
276
|
+
label: "Folder text",
|
|
277
|
+
children: [{ id: "file-2-1-1", label: "File text" }],
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
{ id: "file-3", label: "File text" },
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
interface ITreeNodeProps {
|
|
285
|
+
node: INodeData;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const TreeNode = ({ node }: ITreeNodeProps) => {
|
|
289
|
+
const [opened, setOpened] = React.useState(false);
|
|
290
|
+
const branch = Boolean(node.children?.length);
|
|
291
|
+
|
|
292
|
+
const toggle = () => setOpened((prevOpened) => !prevOpened);
|
|
293
|
+
|
|
294
|
+
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
|
295
|
+
// Клик по вложенной ноде не должен раскрывать родительскую.
|
|
296
|
+
event.stopPropagation();
|
|
297
|
+
|
|
298
|
+
if (branch) {
|
|
299
|
+
toggle();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
304
|
+
if (event.key !== "Enter" && event.key !== " ") {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
event.stopPropagation();
|
|
310
|
+
|
|
311
|
+
if (branch) {
|
|
312
|
+
toggle();
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
return (
|
|
317
|
+
<TreeView.Node id={node.id} opened={opened} onClick={handleClick} onKeyDown={handleKeyDown}>
|
|
318
|
+
{({ hasChildNodes, openedNode }) => (
|
|
319
|
+
<>
|
|
320
|
+
<div className={clsx("tree-view-nested-example-row", { branch })}>
|
|
321
|
+
{hasChildNodes ? (
|
|
322
|
+
<CaretrightStrokeSrvIcon24
|
|
323
|
+
paletteIndex={5}
|
|
324
|
+
className={clsx("tree-view-nested-example-chevron", { opened: openedNode })}
|
|
325
|
+
aria-hidden
|
|
326
|
+
/>
|
|
327
|
+
) : (
|
|
328
|
+
<span className="tree-view-nested-example-chevron-placeholder" />
|
|
329
|
+
)}
|
|
330
|
+
<Text size={ETextSize.B1} tag="span">
|
|
331
|
+
{node.label}
|
|
332
|
+
</Text>
|
|
333
|
+
</div>
|
|
334
|
+
{node.children ? (
|
|
335
|
+
// Группа остаётся смонтированной: так вложенные ноды зарегистрированы в дереве
|
|
336
|
+
// и родитель знает про hasChildNodes даже в свёрнутом состоянии.
|
|
337
|
+
<TreeView.Group className={clsx("tree-view-nested-example-group", { collapsed: !openedNode })}>
|
|
338
|
+
{node.children.map((child) => (
|
|
339
|
+
<TreeNode key={child.id} node={child} />
|
|
340
|
+
))}
|
|
341
|
+
</TreeView.Group>
|
|
342
|
+
) : null}
|
|
343
|
+
</>
|
|
344
|
+
)}
|
|
345
|
+
</TreeView.Node>
|
|
346
|
+
);
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
export const Nested = () => (
|
|
350
|
+
<div className="tree-view-nested-example" style={{ maxWidth: 420 }}>
|
|
351
|
+
<TreeView aria-label="Tree">
|
|
352
|
+
{nodes.map((node) => (
|
|
353
|
+
<TreeNode key={node.id} node={node} />
|
|
354
|
+
))}
|
|
355
|
+
</TreeView>
|
|
356
|
+
</div>
|
|
357
|
+
);
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Keyboard navigation
|
|
361
|
+
|
|
362
|
+
`stories/TreeView/examples/KeyboardNavigation.tsx`
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
import React from "react";
|
|
366
|
+
import clsx from "clsx";
|
|
367
|
+
import { ECaptionSize, ETextSize, Caption, Text, TreeView } from "@sberbusiness/triplex-next";
|
|
368
|
+
import "./KeyboardNavigation.less";
|
|
369
|
+
|
|
370
|
+
interface INodeData {
|
|
371
|
+
id: string;
|
|
372
|
+
label: string;
|
|
373
|
+
children?: INodeData[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const nodes: INodeData[] = [
|
|
377
|
+
{
|
|
378
|
+
id: "folder-1",
|
|
379
|
+
label: "Folder text",
|
|
380
|
+
children: [
|
|
381
|
+
{ id: "file-1-1", label: "File text" },
|
|
382
|
+
{ id: "file-1-2", label: "File text" },
|
|
383
|
+
],
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
id: "folder-2",
|
|
387
|
+
label: "Folder text",
|
|
388
|
+
children: [{ id: "file-2-1", label: "File text" }],
|
|
389
|
+
},
|
|
390
|
+
];
|
|
391
|
+
|
|
392
|
+
interface ITreeNodeProps {
|
|
393
|
+
node: INodeData;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const TreeNode = ({ node }: ITreeNodeProps) => (
|
|
397
|
+
// Ветки раскрыты сразу: стрелки спускаются только в раскрытые ноды.
|
|
398
|
+
<TreeView.Node id={node.id} opened>
|
|
399
|
+
{({ activeNode, isLastNode }) => (
|
|
400
|
+
<>
|
|
401
|
+
<div className={clsx("tree-view-keyboard-example-row", { active: activeNode })}>
|
|
402
|
+
<Text size={ETextSize.B1} tag="span">
|
|
403
|
+
{node.label}
|
|
404
|
+
</Text>
|
|
405
|
+
{isLastNode ? (
|
|
406
|
+
<Caption size={ECaptionSize.C2} tag="span">
|
|
407
|
+
isLastNode
|
|
408
|
+
</Caption>
|
|
409
|
+
) : null}
|
|
410
|
+
</div>
|
|
411
|
+
{node.children ? (
|
|
412
|
+
<TreeView.Group>
|
|
413
|
+
{node.children.map((child) => (
|
|
414
|
+
<TreeNode key={child.id} node={child} />
|
|
415
|
+
))}
|
|
416
|
+
</TreeView.Group>
|
|
417
|
+
) : null}
|
|
418
|
+
</>
|
|
419
|
+
)}
|
|
420
|
+
</TreeView.Node>
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
export const KeyboardNavigation = () => (
|
|
424
|
+
<div className="tree-view-keyboard-example" style={{ maxWidth: 420 }}>
|
|
425
|
+
<Caption size={ECaptionSize.C1} tag="p">
|
|
426
|
+
Поставьте фокус на любой узел и используйте стрелки вверх/вниз.
|
|
427
|
+
</Caption>
|
|
428
|
+
<TreeView aria-label="Tree">
|
|
429
|
+
{nodes.map((node) => (
|
|
430
|
+
<TreeNode key={node.id} node={node} />
|
|
431
|
+
))}
|
|
432
|
+
</TreeView>
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
```
|
|
436
|
+
|