@sberbusiness/triplex-next-mcp-server 1.43.0-beta → 1.45.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/README.md +7 -6
- package/bundle/components/AlertContext.md +11 -8
- package/bundle/components/AlertProcess.md +16 -12
- package/bundle/components/Badge.md +6 -3
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/Button.md +46 -43
- package/bundle/components/ButtonDropdownExtended.md +3 -2
- package/bundle/components/ButtonIcon.md +7 -4
- package/bundle/components/Calendar.md +62 -59
- package/bundle/components/Caption.md +26 -23
- package/bundle/components/Checkbox.md +32 -29
- package/bundle/components/CheckboxTreeExtended.md +9 -5
- package/bundle/components/Chip.md +44 -41
- package/bundle/components/CodeText.md +26 -23
- package/bundle/components/Col.md +1 -2
- package/bundle/components/CollapsibleTree.md +8 -5
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/Dropdown.md +33 -28
- package/bundle/components/EmptyView.md +12 -5
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/FooterPage.md +6 -2
- package/bundle/components/FormField.md +44 -41
- package/bundle/components/FormGroup.md +192 -52
- package/bundle/components/Header.md +491 -211
- package/bundle/components/HeaderPage.md +6 -2
- package/bundle/components/ImageGallery.md +33 -34
- package/bundle/components/ImageGalleryExtended.md +33 -34
- package/bundle/components/Island.md +52 -14
- package/bundle/components/LightBox.md +25 -18
- package/bundle/components/Link.md +211 -96
- package/bundle/components/ListActionItem.md +7 -4
- package/bundle/components/ListItem.md +11 -8
- package/bundle/components/ListItemControlsButton.md +16 -13
- package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
- package/bundle/components/ListItemSelectable.md +7 -4
- package/bundle/components/ListMasterFooter.md +8 -5
- package/bundle/components/ListMasterHeader.md +8 -5
- package/bundle/components/ListSortableItem.md +11 -8
- package/bundle/components/ListTableItem.md +7 -4
- package/bundle/components/LoaderMiddle.md +7 -4
- package/bundle/components/LoaderScreen.md +11 -8
- package/bundle/components/LoaderSmall.md +7 -4
- package/bundle/components/Marker.md +184 -69
- package/bundle/components/MarkerStatus.md +206 -88
- package/bundle/components/MaskedField.md +2 -4
- package/bundle/components/MasterTable.md +602 -0
- package/bundle/components/ModalWindow.md +7 -4
- package/bundle/components/MultiselectField.md +2183 -132
- package/bundle/components/Notification.md +12 -9
- package/bundle/components/Overlay.md +33 -5
- package/bundle/components/Page.md +2 -2
- package/bundle/components/Pagination.md +27 -21
- package/bundle/components/Row.md +1 -1
- package/bundle/components/SelectExtendedField.md +3 -3
- package/bundle/components/SelectField.md +466 -148
- package/bundle/components/SliderExtended.md +39 -36
- package/bundle/components/SmallInput.md +194 -51
- package/bundle/components/Suggest.md +535 -99
- package/bundle/components/SwipeableArea.md +428 -79
- package/bundle/components/TableBasic.md +2302 -0
- package/bundle/components/TabsExtended.md +24 -21
- package/bundle/components/Tag.md +263 -78
- package/bundle/components/Text.md +28 -25
- package/bundle/components/TextField.md +3 -4
- package/bundle/components/ThemeProvider.md +50 -1
- package/bundle/components/Title.md +26 -23
- package/bundle/components/Tooltip.md +24 -21
- package/bundle/components/TopOverlay.md +368 -89
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/guides/codestyle.md +3 -0
- package/bundle/guides/commits.md +106 -26
- package/bundle/guides/context.md +84 -12
- package/bundle/guides/guidelines.md +321 -0
- package/bundle/guides/template.md +22 -12
- package/bundle/manifest.json +54 -20
- package/bundle/release-notes/1.44.0.mdx +113 -0
- package/bundle/release-notes/1.45.0.mdx +124 -0
- package/bundle/release-notes/1.46.0.mdx +7 -0
- package/bundle/tokens.json +6362 -0
- package/dist/index.js +177 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: TreeView
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [AbstractTree]
|
|
3
|
+
category: TreeView
|
|
4
|
+
related: [TreeViewNode, CollapsibleTreeExtended, CollapsibleTree]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/TreeView/TreeView.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
@@ -13,212 +11,391 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
(заголовок, иконка, custom-обёртка — на потребителе).
|
|
14
|
+
Каркас визуального дерева: семантическая разметка (`<ul role="tree">`,
|
|
15
|
+
`<li role="treeitem">`, `aria-expanded`), построение абстрактного дерева нод по
|
|
16
|
+
JSX-разметке, управление `tabIndex` и перемещение активной ноды стрелками
|
|
17
|
+
вверх/вниз. Собственного оформления у компонента нет — разметку узла целиком
|
|
18
|
+
рисует потребитель через render-функцию `children`.
|
|
22
19
|
|
|
23
|
-
Используй когда:
|
|
24
|
-
|
|
25
|
-
`hasChildNodes`/`openedNode`/`setOpenedNode` из render-prop'а.
|
|
20
|
+
Используй когда: нужен только каркас дерева (семантика, регистрация нод,
|
|
21
|
+
навигация стрелками), а весь UI узла — свой.
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
src/components/TreeView/
|
|
33
|
-
├── TreeView.tsx # class TreeView (Component) + ITreeViewComposition
|
|
34
|
-
├── TreeViewContext.tsx # ITreeViewContext + withTreeViewContext HOC
|
|
35
|
-
├── TreeViewAbstractNode.tsx # AbstractTreeNode + active/opened/tabIndex
|
|
36
|
-
├── TreeViewAbstractNodeUtils.ts # навигация по абстрактному дереву
|
|
37
|
-
├── components/
|
|
38
|
-
│ ├── TreeViewNode.tsx # class TreeViewNodeWithContext (Component) + withTreeViewContext
|
|
39
|
-
│ └── TreeViewGroup.tsx # FC, <ul role="group">
|
|
40
|
-
└── styles/TreeView.module.less
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
**Примечание:** в директории нет `index.ts` — импортировать из `./TreeView`,
|
|
44
|
-
`./components/TreeViewNode`, `./components/TreeViewGroup` напрямую (или из
|
|
45
|
-
точки экспорта дизайн-системы, если она настроена в `src/components/index.ts`).
|
|
23
|
+
Не используй когда:
|
|
24
|
+
- нужно типовое дерево «папка/файл» с шевроном, hover и focus-visible —
|
|
25
|
+
бери `CollapsibleTree`;
|
|
26
|
+
- нужен свой UI узла, но с анимацией раскрытия и controlled/uncontrolled
|
|
27
|
+
состоянием — бери `CollapsibleTreeExtended`.
|
|
46
28
|
|
|
47
29
|
---
|
|
48
30
|
|
|
49
|
-
##
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
<TreeView>
|
|
53
|
-
<TreeView.Node id="1" prevNodeId={undefined} nextNodeId="2">
|
|
54
|
-
{({ activeNode, openedNode, hasChildNodes, isLastNode, setOpenedNode }) => (
|
|
55
|
-
<>
|
|
56
|
-
<button onClick={() => setOpenedNode(!openedNode)}>
|
|
57
|
-
{hasChildNodes ? (openedNode ? "▾" : "▸") : "•"} Node 1
|
|
58
|
-
</button>
|
|
59
|
-
{openedNode && (
|
|
60
|
-
<TreeView.Group>
|
|
61
|
-
<TreeView.Node id="1.1">{/* ... */}</TreeView.Node>
|
|
62
|
-
</TreeView.Group>
|
|
63
|
-
)}
|
|
64
|
-
</>
|
|
65
|
-
)}
|
|
66
|
-
</TreeView.Node>
|
|
67
|
-
<TreeView.Node id="2" prevNodeId="1">{/* ... */}</TreeView.Node>
|
|
68
|
-
</TreeView>
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Публичный API
|
|
74
|
-
|
|
75
|
-
### `TreeView` (class component)
|
|
31
|
+
## Варианты и props
|
|
76
32
|
|
|
77
|
-
|
|
78
|
-
|---|---|---|
|
|
79
|
-
| `children` | `ReactNode` | Дерево из `TreeView.Node` / `TreeView.Group`. |
|
|
80
|
-
| `...HTMLAttributes<HTMLUListElement>` | — | На корневой `<ul role="tree">`. |
|
|
81
|
-
|
|
82
|
-
Композиция: `TreeView.Node = TreeViewNode`, `TreeView.Group = TreeViewGroup`.
|
|
33
|
+
### `TreeView`
|
|
83
34
|
|
|
84
|
-
|
|
35
|
+
Class-компонент. Собственных props, кроме `children`, нет — остальное это
|
|
36
|
+
стандартные атрибуты `<ul>`.
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| Prop | Тип | Описание |
|
|
91
|
-
|---|---|---|
|
|
92
|
-
| `id` | `string` | **Обязателен**. Уникальный id ноды. |
|
|
93
|
-
| `children` | `(props: ITreeViewNodeProvideProps) => JSX.Element` | Render-prop — потребитель рисует содержимое. |
|
|
94
|
-
| `opened?` | `boolean` | Внешнее управление раскрытием. Синхронизируется через `componentDidUpdate`. |
|
|
95
|
-
| `prevNodeId?` | `string` | Id соседа слева (для корректной вставки в абстрактное дерево). |
|
|
96
|
-
| `nextNodeId?` | `string` | Id соседа справа. |
|
|
97
|
-
| `...HTMLAttributes<HTMLElement>` | — | На `<li role="treeitem">`. |
|
|
38
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| `children` | `React.ReactNode` | — | Ноды дерева: `TreeView.Node` напрямую или компоненты-обёртки над ним |
|
|
41
|
+
| `...HTMLUListElementAttributes` | — | — | Все стандартные атрибуты `<ul>`; `className` мерджится с собственным классом |
|
|
98
42
|
|
|
99
|
-
|
|
43
|
+
Статические свойства: `TreeView.Node` (= `TreeViewNode`),
|
|
44
|
+
`TreeView.Group` (= `TreeViewGroup`).
|
|
100
45
|
|
|
101
|
-
|
|
102
|
-
|---|---|---|
|
|
103
|
-
| `activeNode` | `boolean` | Нода активна для keyboard-навигации. |
|
|
104
|
-
| `openedNode` | `boolean` | Раскрыта. |
|
|
105
|
-
| `hasChildNodes` | `boolean` | Есть вложенные `TreeView.Node`. |
|
|
106
|
-
| `isLastNode` | `boolean` | Последняя в полном обходе дерева. |
|
|
107
|
-
| `setOpenedNode` | `(opened: boolean) => void` | Переключение opened. |
|
|
108
|
-
|
|
109
|
-
### `TreeView.Group` (`ITreeViewGroupProps`)
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
extends HTMLAttributes<HTMLUListElement>
|
|
113
|
-
```
|
|
46
|
+
### `TreeView.Node` (`TreeViewNode`)
|
|
114
47
|
|
|
115
|
-
|
|
48
|
+
Узел дерева со своим API: `id`, `opened`, `prevNodeId` / `nextNodeId`,
|
|
49
|
+
render-prop `children` с пятью provide-props, контракт по `focus` / `blur` и
|
|
50
|
+
`aria-expanded`. Props, инварианты, порядок регистрации нод и правила
|
|
51
|
+
раскрытия описаны в отдельном документе —
|
|
52
|
+
[`TreeViewNode-ai.md`](./TreeViewNode-ai.md).
|
|
116
53
|
|
|
117
|
-
### `
|
|
54
|
+
### `TreeView.Group` (`TreeViewGroup`)
|
|
118
55
|
|
|
119
|
-
|
|
120
|
-
|
|
56
|
+
`<ul role="group">` — обёртка вложенных нод. `forwardRef` на `<ul>`,
|
|
57
|
+
`className` мерджится, остальные атрибуты пробрасываются. Собственных props нет.
|
|
121
58
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Расширяет `AbstractTreeNode<TreeViewAbstractNode>`:
|
|
125
|
-
|
|
126
|
-
| Метод | Назначение |
|
|
127
|
-
|---|---|
|
|
128
|
-
| `getId()` / id фиксирован в конструкторе | — |
|
|
129
|
-
| `getActive()` / `setActive(boolean)` | Активность для keyboard. |
|
|
130
|
-
| `getOpened()` / `setOpened(boolean)` | Раскрытие. |
|
|
131
|
-
| `getTabIndex()` / `setTabIndex(n)` | Управление roving tabindex (0 для первой ноды первого уровня, -1 — для остальных). |
|
|
59
|
+
---
|
|
132
60
|
|
|
133
|
-
|
|
61
|
+
## Дизайн-токены
|
|
134
62
|
|
|
135
|
-
|
|
136
|
-
`
|
|
63
|
+
Собственных CSS-переменных компонент не определяет. `TreeView.module.less`
|
|
64
|
+
задаёт только сброс отступов и маркеров списка плюс `outline: none` на `<li>`
|
|
65
|
+
(focus-стиль рисует потребитель).
|
|
137
66
|
|
|
138
67
|
---
|
|
139
68
|
|
|
140
|
-
##
|
|
69
|
+
## Инварианты
|
|
141
70
|
|
|
142
|
-
|
|
71
|
+
- **`TreeView` и `TreeViewNodeWithContext` — class-компоненты.** Это legacy,
|
|
72
|
+
зафиксированный публичным API: `ref` на `TreeView` отдаёт инстанс класса.
|
|
73
|
+
Перевод на функциональные компоненты и добавление `forwardRef` — breaking
|
|
74
|
+
change поведения `ref`. Не мигрировать без отдельного решения мейнтейнера.
|
|
75
|
+
- **Barrel `src/components/TreeView/index.ts`** реэкспортирует модули семейства
|
|
76
|
+
целиком (`export * from ...`), поэтому публично всё, что они экспортируют:
|
|
77
|
+
`TreeView` и `ITreeViewProps`, `TreeViewGroup` и `ITreeViewGroupProps`, а от
|
|
78
|
+
ноды — состав, перечисленный в
|
|
79
|
+
[TreeViewNode-ai.md](./TreeViewNode-ai.md) (там же и класс
|
|
80
|
+
`TreeViewNodeWithContext`). Состав и имена менять нельзя — от них зависят
|
|
81
|
+
`CollapsibleTreeExtended` и `CollapsibleTree`.
|
|
82
|
+
- **`TreeViewContext` не экспортируется из barrel**, но `ITreeViewContext`
|
|
83
|
+
транзитивно виден через `ITreeViewNodePropsWithContext`. Поле `updateCount`
|
|
84
|
+
из контекста убирать нельзя без breaking change типа.
|
|
85
|
+
- **Абстрактное дерево мутируемое.** `TreeViewAbstractNode` меняется на месте,
|
|
86
|
+
поэтому смена контекста сигнализируется числом `updateCount` в состоянии
|
|
87
|
+
`TreeView`. Любая мутация дерева обязана сопровождаться его инкрементом,
|
|
88
|
+
иначе ноды отрендерят устаревшие `hasChildNodes` / `isLastNode`.
|
|
89
|
+
- **Слушатель `keydown` висит на `window`**, а не на корневом `<ul>`: активная
|
|
90
|
+
нода не обязана иметь DOM-фокус. Подписка живёт ровно столько, сколько в дереве
|
|
91
|
+
есть активная нода: появляется на `focus` узла и снимается, как только фокус
|
|
92
|
+
ушёл из дерева. Пока активной ноды нет, дерево не обрабатывает стрелки и не
|
|
93
|
+
отменяет скролл страницы.
|
|
94
|
+
- **`role="tree"` / `role="treeitem"` / `role="group"` и `aria-expanded`**
|
|
95
|
+
выставляются после `{...props}` — семантика дерева часть контракта компонента,
|
|
96
|
+
потребитель её не переопределяет. `tabIndex` остаётся до спреда: roving
|
|
97
|
+
tabindex — поведение, а не семантика, и его потребитель при желании может
|
|
98
|
+
задать сам.
|
|
143
99
|
|
|
144
|
-
|
|
145
|
-
- **constructor:** создаёт `TreeViewAbstractNode({id})`, вызывает `treeViewContext.setOpenedNode(...)` + `registerNode(node, parentNode, prevNode, nextNode)`.
|
|
146
|
-
- **componentDidUpdate:** при изменении `opened` props синхронизирует с абстрактной нодой.
|
|
147
|
-
- **componentWillUnmount:** `treeViewContext.removeNode(node)`.
|
|
100
|
+
---
|
|
148
101
|
|
|
149
|
-
|
|
102
|
+
## Accessibility
|
|
150
103
|
|
|
151
|
-
|
|
152
|
-
|
|
104
|
+
Ориентир — WAI-ARIA Authoring Practices,
|
|
105
|
+
[treeview-2a](https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html).
|
|
106
|
+
|
|
107
|
+
- Корень — `<ul role="tree">`, узел — `<li role="treeitem">` с `aria-expanded`,
|
|
108
|
+
вложенная группа — `<ul role="group">` (`TreeView.Group`).
|
|
109
|
+
- **Roving tabindex:** первая нода первого уровня получает `tabIndex={0}`,
|
|
110
|
+
остальные — `tabIndex={-1}`. Пересчитывается при регистрации и удалении нод,
|
|
111
|
+
то есть в дерево можно попасть одним Tab.
|
|
112
|
+
- **Клавиатура:** `ArrowDown` / `ArrowUp` (по `event.keyCode`) переносят
|
|
113
|
+
активную ноду на следующую/предыдущую с `preventDefault` (чтобы не скроллить
|
|
114
|
+
страницу). Обход циклический: после последней ноды активной становится первая.
|
|
115
|
+
Стрелки спускаются только в раскрытые (`opened`) ветки: `ArrowUp` с самой
|
|
116
|
+
первой ноды дерева переходит на последнюю **видимую** ноду — если последняя
|
|
117
|
+
ветка свёрнута, активной становится она сама, а не её скрытый потомок.
|
|
118
|
+
- **Активная нода ≠ DOM-фокус.** Активной нода становится по `focus`
|
|
119
|
+
(`focusin` всплывает до `<li>`, дальше `stopPropagation`), а стрелки двигают
|
|
120
|
+
только флаг активности в абстрактном дереве — DOM-фокус остаётся на исходном
|
|
121
|
+
узле. Подсветку рисует потребитель по provide-prop `activeNode`. `blur`
|
|
122
|
+
снимает активность.
|
|
123
|
+
- Раскрытие/сворачивание по клавиатуре (`ArrowRight`/`ArrowLeft`, `Home`/`End`)
|
|
124
|
+
компонент **не** реализует — это на потребителе.
|
|
125
|
+
- Текстовые подписи и `aria-label` компонент не хардкодит: библиотека
|
|
126
|
+
мультиязычная, всю разметку узла даёт потребитель.
|
|
153
127
|
|
|
154
|
-
|
|
128
|
+
---
|
|
155
129
|
|
|
156
|
-
|
|
157
|
-
- ArrowDown → `setActiveNextNode` + `preventDefault` + триггер ререндера.
|
|
158
|
-
- ArrowUp → `setActivePrevNode`.
|
|
159
|
-
- Срабатывает только если есть активная нода (`getActiveNode != undefined`).
|
|
130
|
+
## Связанные компоненты
|
|
160
131
|
|
|
161
|
-
|
|
132
|
+
- `TreeViewNode` (`TreeView.Node`) — узел дерева, единственная часть семейства
|
|
133
|
+
со своим API: render-prop `children`, регистрация в абстрактном дереве,
|
|
134
|
+
focus/blur-контракт. См. [`TreeViewNode-ai.md`](./TreeViewNode-ai.md).
|
|
135
|
+
- `CollapsibleTreeExtended` (`src/components/CollapsibleTreeExtended`) — обёртка
|
|
136
|
+
над `TreeView`, добавляющая каждой ноде accordion-логику: анимацию раскрытия
|
|
137
|
+
и controlled/uncontrolled состояние.
|
|
138
|
+
- `CollapsibleTree` (`src/components/CollapsibleTree`) — готовое дерево
|
|
139
|
+
«папка/файл» поверх `CollapsibleTreeExtended`. Закрывает большинство
|
|
140
|
+
прикладных случаев.
|
|
141
|
+
- `AbstractTreeNode` (`src/components/AbstractTree`) — базовая реализация
|
|
142
|
+
абстрактного дерева, от которой наследуется `TreeViewAbstractNode`, и функция
|
|
143
|
+
обхода `traverseAbstractTree`.
|
|
162
144
|
|
|
163
|
-
|
|
164
|
-
- `onFocus` (event.stopPropagation, чтобы родитель не получил bubble) → `setActiveNode(node, true)`.
|
|
165
|
-
- `onBlur` → `setActiveNode(node, false)`.
|
|
145
|
+
---
|
|
166
146
|
|
|
167
|
-
|
|
147
|
+
## Stories
|
|
168
148
|
|
|
169
|
-
|
|
170
|
-
`
|
|
171
|
-
`abstractNode` — мутируемый объект (изменения не вызывают триггер
|
|
172
|
-
самостоятельно).
|
|
149
|
+
Основные истории: `stories/TreeView/TreeView.stories.tsx`
|
|
150
|
+
Файлы примеров: `stories/TreeView/examples/`
|
|
173
151
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
152
|
+
| Story | Example file | Что демонстрирует |
|
|
153
|
+
|---|---|---|
|
|
154
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль `defaultOpened` и глубины вложенности |
|
|
155
|
+
| `Default` | `Default.tsx` | Минимальное дерево: плоский список `TreeView.Node` |
|
|
156
|
+
| `Nested` | `Nested.tsx` | Вложенность через `TreeView.Group`, раскрытие через prop `opened`, `hasChildNodes` |
|
|
157
|
+
| `Keyboard navigation` | `KeyboardNavigation.tsx` | Перемещение `activeNode` стрелками, подсветка активной ноды, `isLastNode` |
|
|
158
|
+
| `VisualTests` | `VisualTests.tsx` | Свёрнутое и раскрытое дерево в одном кадре, активная нода через `play` |
|
|
177
159
|
|
|
178
160
|
---
|
|
179
161
|
|
|
180
|
-
##
|
|
162
|
+
## История изменений
|
|
181
163
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
-
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
- **Mutable `abstractNode`** — изменения требуют ручного `setState({updateCount})` для ререндера. Не доставать ссылку из contextValue для дальнейшей мутации без `setState`.
|
|
189
|
-
- **`event.stopPropagation` в onFocus/onBlur ноды** — обязательно, чтобы родительский `<li>` не получил bubble.
|
|
190
|
-
- **Композиция через `TreeView.Node = TreeViewNode`** — статические свойства класса.
|
|
191
|
-
- **Опечатка в `prevCount++` (vs `++prevCount`)** — намеренно или нет, не править без анализа поведения.
|
|
192
|
-
- **Нет `index.ts`** в директории — импорты идут напрямую из файлов компонентов.
|
|
164
|
+
| Дата | Изменение |
|
|
165
|
+
|---|---|
|
|
166
|
+
| 2026-08-26 | Описание `TreeView.Node` вынесено в отдельный `TreeViewNode-ai.md`, здесь оставлена ссылка |
|
|
167
|
+
| 2026-08-25 | Слушатель `keydown` подписан только пока в дереве есть активная нода: после ухода фокуса из дерева стрелки снова достаются странице. `role` и `aria-expanded` перенесены после `{...props}` — потребитель больше не может их переопределить |
|
|
168
|
+
| 2026-08-24 | `ArrowUp` с первой ноды дерева больше не заходит внутрь свёрнутой последней ветки — активной становится последняя видимая нода (`TreeViewAbstractNodeUtils.getPrevNode`) |
|
|
169
|
+
| 2026-08-21 | Создан документ. AI-рефакторинг: починен счётчик `updateCount` (постфиксный инкремент не увеличивал значение), удалён мёртвый код, добавлены unit-тесты и stories. `TreeView.Group` получил `forwardRef` и перестал затирать собственный класс переданным `className`; после удаления ноды соседние ноды теперь перерисовываются (актуальные `hasChildNodes` / `isLastNode`) |
|
|
193
170
|
|
|
194
171
|
---
|
|
195
172
|
|
|
196
|
-
##
|
|
173
|
+
## Примеры использования
|
|
197
174
|
|
|
198
|
-
|
|
199
|
-
- `role="tree"` на корне, `role="treeitem"` на `<li>`, `role="group"` на `<ul>`.
|
|
200
|
-
- `aria-expanded={openedNode}` на каждом `<li>`.
|
|
201
|
-
- Roving tabindex (только первая нода первого уровня в tab-стопе).
|
|
202
|
-
- ArrowDown/ArrowUp — навигация по дереву (только при наличии активной ноды).
|
|
203
|
-
- ArrowLeft/ArrowRight (свернуть/раскрыть) — НЕ реализованы; должен делать потребитель в render-prop'е через `setOpenedNode`.
|
|
175
|
+
### Default
|
|
204
176
|
|
|
205
|
-
|
|
177
|
+
`stories/TreeView/examples/Default.tsx`
|
|
206
178
|
|
|
207
|
-
|
|
179
|
+
```tsx
|
|
180
|
+
import React from "react";
|
|
181
|
+
import { ETextSize, Text, TreeView } from "@sberbusiness/triplex-next";
|
|
182
|
+
import "./Default.less";
|
|
183
|
+
|
|
184
|
+
const items = [
|
|
185
|
+
{ id: "item-1", label: "Item text" },
|
|
186
|
+
{ id: "item-2", label: "Item text" },
|
|
187
|
+
{ id: "item-3", label: "Item text" },
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
export const Default = () => (
|
|
191
|
+
<div className="tree-view-default-example" style={{ maxWidth: 420 }}>
|
|
192
|
+
<TreeView aria-label="Tree">
|
|
193
|
+
{items.map((item) => (
|
|
194
|
+
<TreeView.Node key={item.id} id={item.id}>
|
|
195
|
+
{() => (
|
|
196
|
+
<div className="tree-view-default-example-row">
|
|
197
|
+
<Text size={ETextSize.B1} tag="span">
|
|
198
|
+
{item.label}
|
|
199
|
+
</Text>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
</TreeView.Node>
|
|
203
|
+
))}
|
|
204
|
+
</TreeView>
|
|
205
|
+
</div>
|
|
206
|
+
);
|
|
207
|
+
```
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
- Любой потребитель — рисует UI ноды (заголовок, кнопка, иконка expand) через render-prop.
|
|
209
|
+
### Nested
|
|
211
210
|
|
|
212
|
-
|
|
211
|
+
`stories/TreeView/examples/Nested.tsx`
|
|
213
212
|
|
|
214
|
-
|
|
213
|
+
```tsx
|
|
214
|
+
import React from "react";
|
|
215
|
+
import clsx from "clsx";
|
|
216
|
+
import { CaretrightStrokeSrvIcon24 } from "@sberbusiness/icons-next";
|
|
217
|
+
import { ETextSize, Text, TreeView } from "@sberbusiness/triplex-next";
|
|
218
|
+
import "./Nested.less";
|
|
219
|
+
|
|
220
|
+
interface INodeData {
|
|
221
|
+
id: string;
|
|
222
|
+
label: string;
|
|
223
|
+
children?: INodeData[];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const nodes: INodeData[] = [
|
|
227
|
+
{
|
|
228
|
+
id: "folder-1",
|
|
229
|
+
label: "Folder text",
|
|
230
|
+
children: [
|
|
231
|
+
{ id: "file-1-1", label: "File text" },
|
|
232
|
+
{ id: "file-1-2", label: "File text" },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "folder-2",
|
|
237
|
+
label: "Folder text",
|
|
238
|
+
children: [
|
|
239
|
+
{
|
|
240
|
+
id: "folder-2-1",
|
|
241
|
+
label: "Folder text",
|
|
242
|
+
children: [{ id: "file-2-1-1", label: "File text" }],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
{ id: "file-3", label: "File text" },
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
interface ITreeNodeProps {
|
|
250
|
+
node: INodeData;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const TreeNode = ({ node }: ITreeNodeProps) => {
|
|
254
|
+
const [opened, setOpened] = React.useState(false);
|
|
255
|
+
const branch = Boolean(node.children?.length);
|
|
256
|
+
|
|
257
|
+
const toggle = () => setOpened((prevOpened) => !prevOpened);
|
|
258
|
+
|
|
259
|
+
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
|
260
|
+
// Клик по вложенной ноде не должен раскрывать родительскую.
|
|
261
|
+
event.stopPropagation();
|
|
262
|
+
|
|
263
|
+
if (branch) {
|
|
264
|
+
toggle();
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
|
|
269
|
+
if (event.key !== "Enter" && event.key !== " ") {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
event.preventDefault();
|
|
274
|
+
event.stopPropagation();
|
|
275
|
+
|
|
276
|
+
if (branch) {
|
|
277
|
+
toggle();
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<TreeView.Node id={node.id} opened={opened} onClick={handleClick} onKeyDown={handleKeyDown}>
|
|
283
|
+
{({ hasChildNodes, openedNode }) => (
|
|
284
|
+
<>
|
|
285
|
+
<div className={clsx("tree-view-nested-example-row", { branch })}>
|
|
286
|
+
{hasChildNodes ? (
|
|
287
|
+
<CaretrightStrokeSrvIcon24
|
|
288
|
+
paletteIndex={5}
|
|
289
|
+
className={clsx("tree-view-nested-example-chevron", { opened: openedNode })}
|
|
290
|
+
aria-hidden
|
|
291
|
+
/>
|
|
292
|
+
) : (
|
|
293
|
+
<span className="tree-view-nested-example-chevron-placeholder" />
|
|
294
|
+
)}
|
|
295
|
+
<Text size={ETextSize.B1} tag="span">
|
|
296
|
+
{node.label}
|
|
297
|
+
</Text>
|
|
298
|
+
</div>
|
|
299
|
+
{node.children ? (
|
|
300
|
+
// Группа остаётся смонтированной: так вложенные ноды зарегистрированы в дереве
|
|
301
|
+
// и родитель знает про hasChildNodes даже в свёрнутом состоянии.
|
|
302
|
+
<TreeView.Group className={clsx("tree-view-nested-example-group", { collapsed: !openedNode })}>
|
|
303
|
+
{node.children.map((child) => (
|
|
304
|
+
<TreeNode key={child.id} node={child} />
|
|
305
|
+
))}
|
|
306
|
+
</TreeView.Group>
|
|
307
|
+
) : null}
|
|
308
|
+
</>
|
|
309
|
+
)}
|
|
310
|
+
</TreeView.Node>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export const Nested = () => (
|
|
315
|
+
<div className="tree-view-nested-example" style={{ maxWidth: 420 }}>
|
|
316
|
+
<TreeView aria-label="Tree">
|
|
317
|
+
{nodes.map((node) => (
|
|
318
|
+
<TreeNode key={node.id} node={node} />
|
|
319
|
+
))}
|
|
320
|
+
</TreeView>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
```
|
|
215
324
|
|
|
216
|
-
|
|
325
|
+
### Keyboard navigation
|
|
217
326
|
|
|
218
|
-
|
|
327
|
+
`stories/TreeView/examples/KeyboardNavigation.tsx`
|
|
219
328
|
|
|
220
|
-
|
|
329
|
+
```tsx
|
|
330
|
+
import React from "react";
|
|
331
|
+
import clsx from "clsx";
|
|
332
|
+
import { ECaptionSize, ETextSize, Caption, Text, TreeView } from "@sberbusiness/triplex-next";
|
|
333
|
+
import "./KeyboardNavigation.less";
|
|
334
|
+
|
|
335
|
+
interface INodeData {
|
|
336
|
+
id: string;
|
|
337
|
+
label: string;
|
|
338
|
+
children?: INodeData[];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const nodes: INodeData[] = [
|
|
342
|
+
{
|
|
343
|
+
id: "folder-1",
|
|
344
|
+
label: "Folder text",
|
|
345
|
+
children: [
|
|
346
|
+
{ id: "file-1-1", label: "File text" },
|
|
347
|
+
{ id: "file-1-2", label: "File text" },
|
|
348
|
+
],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "folder-2",
|
|
352
|
+
label: "Folder text",
|
|
353
|
+
children: [{ id: "file-2-1", label: "File text" }],
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
interface ITreeNodeProps {
|
|
358
|
+
node: INodeData;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const TreeNode = ({ node }: ITreeNodeProps) => (
|
|
362
|
+
// Ветки раскрыты сразу: стрелки спускаются только в раскрытые ноды.
|
|
363
|
+
<TreeView.Node id={node.id} opened>
|
|
364
|
+
{({ activeNode, isLastNode }) => (
|
|
365
|
+
<>
|
|
366
|
+
<div className={clsx("tree-view-keyboard-example-row", { active: activeNode })}>
|
|
367
|
+
<Text size={ETextSize.B1} tag="span">
|
|
368
|
+
{node.label}
|
|
369
|
+
</Text>
|
|
370
|
+
{isLastNode ? (
|
|
371
|
+
<Caption size={ECaptionSize.C2} tag="span">
|
|
372
|
+
isLastNode
|
|
373
|
+
</Caption>
|
|
374
|
+
) : null}
|
|
375
|
+
</div>
|
|
376
|
+
{node.children ? (
|
|
377
|
+
<TreeView.Group>
|
|
378
|
+
{node.children.map((child) => (
|
|
379
|
+
<TreeNode key={child.id} node={child} />
|
|
380
|
+
))}
|
|
381
|
+
</TreeView.Group>
|
|
382
|
+
) : null}
|
|
383
|
+
</>
|
|
384
|
+
)}
|
|
385
|
+
</TreeView.Node>
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
export const KeyboardNavigation = () => (
|
|
389
|
+
<div className="tree-view-keyboard-example" style={{ maxWidth: 420 }}>
|
|
390
|
+
<Caption size={ECaptionSize.C1} tag="p">
|
|
391
|
+
Поставьте фокус на любой узел и используйте стрелки вверх/вниз.
|
|
392
|
+
</Caption>
|
|
393
|
+
<TreeView aria-label="Tree">
|
|
394
|
+
{nodes.map((node) => (
|
|
395
|
+
<TreeNode key={node.id} node={node} />
|
|
396
|
+
))}
|
|
397
|
+
</TreeView>
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
```
|
|
221
401
|
|
|
222
|
-
| Дата | Изменение |
|
|
223
|
-
|---|---|
|
|
224
|
-
| 2026-04-18 | Создан документ |
|