@sberbusiness/triplex-next-mcp-server 0.24.0-beta1
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 +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# @sberbusiness/triplex-next-mcp-server
|
|
2
|
+
|
|
3
|
+
MCP-сервер для дизайн-системы Triplex. Предоставляет AI-агентам документацию компонентов, дизайн-токены и примеры использования через [Model Context Protocol](https://modelcontextprotocol.io/).
|
|
4
|
+
|
|
5
|
+
## Быстрый старт
|
|
6
|
+
|
|
7
|
+
### Подключение к Claude Code
|
|
8
|
+
|
|
9
|
+
Добавьте в `.mcp.json` вашего проекта:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"triplex": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["@sberbusiness/triplex-next-mcp-server"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Подключение к Cursor
|
|
23
|
+
|
|
24
|
+
Добавьте в `.cursor/mcp.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"triplex": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["@sberbusiness/triplex-next-mcp-server"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
После подключения AI-агент получает доступ к документации всех компонентов Triplex без необходимости клонировать репозиторий дизайн-системы.
|
|
38
|
+
|
|
39
|
+
## Доступные tools
|
|
40
|
+
|
|
41
|
+
| Tool | Описание |
|
|
42
|
+
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| `list_components` | Список всех компонентов с кратким описанием и флагом `ai_ready` |
|
|
44
|
+
| `get_component(name)` | Полная документация компонента: props, токены, примеры, ограничения |
|
|
45
|
+
| `get_tokens(component?)` | Дизайн-токены — все или для конкретного компонента |
|
|
46
|
+
| `list_icons(query?, size?, limit?)` | Иконки из `@sberbusiness/icons-next` с фильтрами по подстроке в имени и размеру (защита от галлюцинаций имён) |
|
|
47
|
+
|
|
48
|
+
Флаг `ai_ready: true` означает, что у компонента есть полноценная AI-документация
|
|
49
|
+
(`*-AI.md` с frontmatter, секциями и примерами). Для остальных компонентов сервер
|
|
50
|
+
возвращает минимальный fallback — имя и путь к `stories`.
|
|
51
|
+
|
|
52
|
+
## Примеры использования
|
|
53
|
+
|
|
54
|
+
После подключения сервера AI-агент может отвечать на запросы вроде:
|
|
55
|
+
|
|
56
|
+
- «Какие props есть у компонента Button?»
|
|
57
|
+
- «Покажи пример использования Alert с типом error»
|
|
58
|
+
- «Какие дизайн-токены использует Card?»
|
|
59
|
+
- «Какой компонент подойдёт для отображения уведомлений?»
|
|
60
|
+
- «Найди иконку со стрелкой размером 16px»
|
|
61
|
+
|
|
62
|
+
## Разработка
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Установка зависимостей
|
|
66
|
+
npm install
|
|
67
|
+
|
|
68
|
+
# Сборка
|
|
69
|
+
npm run build
|
|
70
|
+
|
|
71
|
+
# Сборка в watch-режиме
|
|
72
|
+
npm run dev
|
|
73
|
+
|
|
74
|
+
# Запуск сервера
|
|
75
|
+
npm run start
|
|
76
|
+
|
|
77
|
+
# Отладка через MCP Inspector
|
|
78
|
+
npm run inspect
|
|
79
|
+
|
|
80
|
+
# Обновить bundle/ из последнего релиза triplex-next
|
|
81
|
+
npm run fetch-bundle
|
|
82
|
+
|
|
83
|
+
# Обновить bundle/icons.json из @sberbusiness/icons-next
|
|
84
|
+
npm run fetch-icons
|
|
85
|
+
|
|
86
|
+
# Проверка типов
|
|
87
|
+
npm run typecheck
|
|
88
|
+
|
|
89
|
+
# Тесты
|
|
90
|
+
npm run test
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Источник данных
|
|
94
|
+
|
|
95
|
+
Сервер читает документацию из дерева `bundle/` (авто-генерируется, руками
|
|
96
|
+
не правим):
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
bundle/
|
|
100
|
+
manifest.json — schemaVersion, triplexVersion, списки компонентов,
|
|
101
|
+
гайдов и release notes
|
|
102
|
+
components/ — *-AI.md по AI-ready компонентам
|
|
103
|
+
guides/ — docs/ai/*.md (context, codestyle, tests, …)
|
|
104
|
+
release-notes/ — stories/release-notes/v1/*.mdx
|
|
105
|
+
icons.json — список иконок @sberbusiness/icons-next для list_icons
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Дерево генерируется командой `npm run fetch-bundle`: она скачивает
|
|
109
|
+
`mcp-data.json` c последнего GitHub Release [triplex-next](https://github.com/SberBusiness/triplex-next)
|
|
110
|
+
(или из локального файла через `--file <path>`) и раскладывает
|
|
111
|
+
содержимое в поддиректории выше. Транзиентный `mcp-data.json` удаляется
|
|
112
|
+
после раскладки.
|
|
113
|
+
|
|
114
|
+
В triplex-next bundle собирается скриптом `scripts/generateMcpData.ts`
|
|
115
|
+
(npm-команда `generateMcpData`) и автоматически прикладывается к релиз-ассетам
|
|
116
|
+
через `.github/workflows/release.yml`.
|
|
117
|
+
|
|
118
|
+
`bundle/icons.json` генерируется отдельно — командой `npm run fetch-icons`,
|
|
119
|
+
которая тянет `@sberbusiness/icons-next` с jsdelivr и собирает список имён
|
|
120
|
+
экспортов с размерами.
|
|
121
|
+
|
|
122
|
+
## Лицензия
|
|
123
|
+
|
|
124
|
+
MIT
|
package/bundle/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: AbstractTree
|
|
3
|
+
category: Utilities
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [TreeView, CollapsableTree, CheckboxTree]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# AbstractTree
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Внутренняя утилита: дженерик-класс `AbstractTreeNode<T>` и функция обхода
|
|
17
|
+
`traverseAbstractTree` для построения произвольных древовидных структур данных.
|
|
18
|
+
|
|
19
|
+
Используется как базовый класс для специализированных нод (например,
|
|
20
|
+
`TreeViewAbstractNode` в `src/components/TreeView/`). НЕ является React-компонентом,
|
|
21
|
+
не рендерит UI и не экспортируется из публичного API библиотеки — только для
|
|
22
|
+
внутреннего переиспользования между компонентами дерева.
|
|
23
|
+
|
|
24
|
+
Используй когда: нужна модель узла дерева с API добавления/удаления/обхода детей
|
|
25
|
+
внутри компонента, работающего с иерархическими данными.
|
|
26
|
+
Не используй когда: нужен просто массив. Не импортируй из приложений-потребителей —
|
|
27
|
+
это internal API без гарантий стабильности.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Файловая структура
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
src/components/AbstractTree/
|
|
35
|
+
└── AbstractTreeNode.ts # Класс AbstractTreeNode<T> + функция traverseAbstractTree
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Особенности:
|
|
39
|
+
- Нет `index.ts` (barrel) — импорт только по прямому пути `./AbstractTreeNode`.
|
|
40
|
+
- Нет TSX, LESS, stories, тестов — это чистая TypeScript-утилита.
|
|
41
|
+
- Зависимость от внешней библиотеки: `lodash-es` (`isEqual` для сравнения нод).
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Публичный API модуля
|
|
46
|
+
|
|
47
|
+
### Класс `AbstractTreeNode<T extends AbstractTreeNode<T>>`
|
|
48
|
+
|
|
49
|
+
Дженерик-класс с рекурсивным типом — `T` должен расширять сам `AbstractTreeNode<T>`.
|
|
50
|
+
Это позволяет наследникам возвращать из методов конкретный подтип, а не базовый.
|
|
51
|
+
|
|
52
|
+
| Метод | Сигнатура | Описание |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `getChildren` | `() => T[]` | Возвращает массив дочерних нод (ссылку на приватный массив). |
|
|
55
|
+
| `addChild` | `(node: T, prevNode?: T, nextNode?: T) => void` | Добавляет ноду. По умолчанию — в конец. С `prevNode` — после указанной. С `nextNode` — перед указанной. Автоматически выставляет `parent` у добавляемой ноды. |
|
|
56
|
+
| `removeChild` | `(node: T) => void` | Удаляет дочернюю ноду по `isEqual`. Сбрасывает её `parent` в `null`. No-op, если не найдена. |
|
|
57
|
+
| `setChildren` | `(nodes: T[]) => void` | Полностью заменяет детей. У старых детей `parent` обнуляется, у новых — выставляется на `this`. |
|
|
58
|
+
| `getParent` | `() => T \| null` | Возвращает родительскую ноду. `null` для корневой. |
|
|
59
|
+
| `setParent` | `(node: T \| null) => void` | Устанавливает родителя. Обычно вызывается внутри `addChild`/`removeChild`/`setChildren`, но экспонирован публично (используется в `traverse` и в наследниках). |
|
|
60
|
+
|
|
61
|
+
Приватные поля:
|
|
62
|
+
- `children: T[] = []`
|
|
63
|
+
- `parent: T \| null = null`
|
|
64
|
+
|
|
65
|
+
### Функция `traverseAbstractTree<VisionNode>(node, callback)`
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
function traverseAbstractTree<VisionNode extends AbstractTreeNode<VisionNode>>(
|
|
69
|
+
node: VisionNode,
|
|
70
|
+
callback: (node: VisionNode) => boolean,
|
|
71
|
+
): void
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Обходит дерево в глубину сверху вниз (pre-order DFS), начиная с переданной
|
|
75
|
+
ноды. На каждой ноде вызывает `callback`.
|
|
76
|
+
|
|
77
|
+
- Если `callback` возвращает `false` — обход поддерева текущей ноды прекращается
|
|
78
|
+
(её дочерние ноды не будут посещены). Это **не** прерывает весь обход — только
|
|
79
|
+
спуск в текущую ветку.
|
|
80
|
+
- Если `callback` возвращает `true` — обход продолжается рекурсивно по детям.
|
|
81
|
+
|
|
82
|
+
Пример паттерна "найти и выйти" в TreeView — `callback` сохраняет найденную ноду
|
|
83
|
+
в замыкание и возвращает `false` для остановки спуска:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
let result;
|
|
87
|
+
traverseAbstractTree(rootNode, (n) => {
|
|
88
|
+
if (n.getId() === nodeId) {
|
|
89
|
+
result = n;
|
|
90
|
+
return false; // прервать ветку
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Ключевые особенности реализации
|
|
99
|
+
|
|
100
|
+
### Сравнение нод через `lodash.isEqual`
|
|
101
|
+
|
|
102
|
+
`addChild`, `removeChild` находят позицию по `isEqual(n, prevNode/nextNode/node)`
|
|
103
|
+
— глубокое рекурсивное сравнение, а не по идентичности ссылок (`===`).
|
|
104
|
+
|
|
105
|
+
**Последствие:** если у двух нод идентичная структура данных (одинаковые id, одинаковые
|
|
106
|
+
дети, одинаковый parent в момент сравнения), они будут считаться равными и `removeChild`
|
|
107
|
+
удалит первую совпавшую. В текущем использовании (TreeView) это нивелируется тем, что
|
|
108
|
+
наследник `TreeViewAbstractNode` имеет уникальный `id`, но для новых наследников
|
|
109
|
+
стоит учитывать эту семантику.
|
|
110
|
+
|
|
111
|
+
### Public `setParent`
|
|
112
|
+
|
|
113
|
+
`setParent` помечен как public, хотя логически это "приватный" метод внутри графа
|
|
114
|
+
связей. Это сделано потому, что `addChild`/`setChildren` вызывают `node.setParent(this)`
|
|
115
|
+
на чужом инстансе — TypeScript не разрешает это для private-методов без `@ts-ignore`.
|
|
116
|
+
В файле есть два `@ts-ignore` именно для этой ситуации — это осознанный компромисс,
|
|
117
|
+
не удалять.
|
|
118
|
+
|
|
119
|
+
### Методы как arrow-свойства
|
|
120
|
+
|
|
121
|
+
Все методы объявлены как arrow-функции-свойства (`public addChild = (...) => {}`),
|
|
122
|
+
а не как обычные методы класса. Это захватывает `this` в замыкание, и экземпляры
|
|
123
|
+
можно безопасно передавать в callback'и без потери контекста (`node.addChild`
|
|
124
|
+
без `.bind(this)` работает корректно).
|
|
125
|
+
|
|
126
|
+
**Последствие:** каждый инстанс имеет свою копию функций (не в prototype). Для деревьев
|
|
127
|
+
с тысячами нод это заметный оверхед по памяти. Учитывай при профилировании.
|
|
128
|
+
|
|
129
|
+
### Порядок вставки при `addChild`
|
|
130
|
+
|
|
131
|
+
Приоритет аргументов: `prevNode` проверяется **раньше** `nextNode`. Если переданы
|
|
132
|
+
оба — `nextNode` игнорируется, вставка произойдёт после `prevNode`.
|
|
133
|
+
|
|
134
|
+
### `setChildren` не очищает переданные ноды
|
|
135
|
+
|
|
136
|
+
Если переданная в `setChildren` нода уже имеет `parent` в другом дереве — её старый
|
|
137
|
+
`parent` НЕ будет знать, что она ушла (его `children` по-прежнему содержит ссылку).
|
|
138
|
+
Перемещение нод между деревьями требует ручного вызова `removeChild` на старом
|
|
139
|
+
родителе.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Инварианты
|
|
144
|
+
|
|
145
|
+
- **Рекурсивный дженерик `T extends AbstractTreeNode<T>`** — не упрощать до
|
|
146
|
+
`AbstractTreeNode` или `any`. Это обеспечивает типобезопасность наследников
|
|
147
|
+
(например, `TreeViewAbstractNode.getChildren()` возвращает `TreeViewAbstractNode[]`,
|
|
148
|
+
а не базовый тип).
|
|
149
|
+
- **`lodash-es` как источник `isEqual`** — не заменять на самописное сравнение или
|
|
150
|
+
shallow `===`, это меняет контракт `removeChild`/`addChild`.
|
|
151
|
+
- **`@ts-ignore` на вызовах `setParent` чужого инстанса** — не удалять, не заменять
|
|
152
|
+
на `as any` (см. раздел выше о `public setParent`).
|
|
153
|
+
- **Pre-order DFS обхода** — порядок посещения узлов в `traverseAbstractTree`
|
|
154
|
+
является контрактом (TreeView полагается на него при поиске активной ноды и
|
|
155
|
+
клавиатурной навигации). Не менять на BFS или post-order.
|
|
156
|
+
- **Возврат `false` прерывает только текущее поддерево**, а не весь обход —
|
|
157
|
+
этот контракт используется для "find and stop descent" в `TreeViewAbstractNodeUtils`.
|
|
158
|
+
- **Отсутствие `index.ts`** — не добавлять. Модуль не предназначен для публичного
|
|
159
|
+
экспорта из библиотеки, только для внутренних импортов через прямой путь.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Accessibility
|
|
164
|
+
|
|
165
|
+
N/A — утилитарный модуль данных, не рендерит DOM.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Дизайн-токены
|
|
170
|
+
|
|
171
|
+
Нет — у модуля нет стилей.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Связанные компоненты
|
|
176
|
+
|
|
177
|
+
- `TreeView` (`src/components/TreeView/TreeViewAbstractNode.tsx`) — наследуется от
|
|
178
|
+
`AbstractTreeNode`, добавляет поля `id`, `active`, `opened`, `tabIndex`.
|
|
179
|
+
- `TreeView` (`src/components/TreeView/TreeViewAbstractNodeUtils.ts`) — использует
|
|
180
|
+
`traverseAbstractTree` для поиска активной ноды, получения ноды по `id`,
|
|
181
|
+
сброса флагов активности.
|
|
182
|
+
- Потенциальные будущие потребители: `CollapsableTree`, `CheckboxTree`,
|
|
183
|
+
`CheckboxTreeExtended`. На момент документа они реализованы иначе (не через
|
|
184
|
+
`AbstractTreeNode`), но могут быть переведены на эту модель.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Stories
|
|
189
|
+
|
|
190
|
+
Нет — утилитарный модуль без UI не имеет stories.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## История изменений
|
|
195
|
+
|
|
196
|
+
| Дата | Изменение |
|
|
197
|
+
|---|---|
|
|
198
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: AccordionBase
|
|
3
|
+
category: Internals
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [ExpandAnimation, CollapsableTree, Spoiler]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# AccordionBase
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Внутренний безстилевой базовый компонент раскрывающегося блока (аккордеон). Реализует
|
|
17
|
+
логику controlled/uncontrolled состояния, анимацию раскрытия через `ExpandAnimation`,
|
|
18
|
+
генерацию уникального id и `aria-controls`/`aria-expanded` атрибутов. Визуальное
|
|
19
|
+
оформление и вёрстка header/body целиком делегируются потребителю через render-props
|
|
20
|
+
(`renderHeader`, `renderBody`).
|
|
21
|
+
|
|
22
|
+
Используется как низкоуровневый примитив: на его основе построены `CollapsableTree`
|
|
23
|
+
(древовидный UI с раскрывающимися нодами). Папка `protected/` и отсутствие экспорта
|
|
24
|
+
из публичного barrel — явный сигнал, что это internal API.
|
|
25
|
+
|
|
26
|
+
Используй когда: строишь новый высокоуровневый раскрывающийся компонент (например,
|
|
27
|
+
другой вариант tree / spoiler), и хочешь переиспользовать логику состояния + анимацию.
|
|
28
|
+
Не используй когда: нужен готовый аккордеон с визуалом — это НЕ пользовательский
|
|
29
|
+
компонент, импортируй уровнем выше (`CollapsableTreeNode`, `Spoiler`).
|
|
30
|
+
Не импортируй из приложений-потребителей — internal API без гарантий стабильности.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Файловая структура
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
src/components/AccordionBase/
|
|
38
|
+
├── types.ts # IAccordionBaseProps, IAccordionBodyProvideProps, IAccordionHeaderProvideProps
|
|
39
|
+
└── protected/
|
|
40
|
+
└── AccordionBase.tsx # Class-component React.Component (не forwardRef, не функциональный)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Особенности:
|
|
44
|
+
- Нет `index.ts` (barrel) — импорты только по прямым путям.
|
|
45
|
+
- Нет LESS — модуль без собственных стилей.
|
|
46
|
+
- Нет stories, нет тестов — тестируется косвенно через `CollapsableTree`.
|
|
47
|
+
- Папка `protected/` — конвенция для internal-компонентов, не попадающих в публичный API.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Варианты и props
|
|
52
|
+
|
|
53
|
+
### `IAccordionBaseProps`
|
|
54
|
+
|
|
55
|
+
| Prop | Тип | Обязателен | Описание |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `renderHeader` | `(props: IAccordionHeaderProvideProps) => JSX.Element` | Да | Рендер-функция видимой (всегда видимой) части. Получает `isOpen`, `toggle`, `animating`, `ariaAttributes`. |
|
|
58
|
+
| `renderBody` | `(props: IAccordionBodyProvideProps) => React.ReactNode` | Да | Рендер-функция сворачиваемого контента. Оборачивается в `ExpandAnimation`. |
|
|
59
|
+
| `isOpen` | `boolean` | Нет | **Наличие prop = controlled mode.** Если передан — состояние управляется снаружи. Если `undefined` — uncontrolled, состояние держит сам AccordionBase. |
|
|
60
|
+
| `toggle` | `(isOpen: boolean) => void` | Нет | Для controlled mode: вызывается при клике, потребитель должен обновить `isOpen`. |
|
|
61
|
+
| `onToggle` | `(isOpen: boolean) => void` | Нет | Вызывается **после окончания анимации** (не при клике). Для side-эффектов, не связанных с управлением состоянием. |
|
|
62
|
+
| `expandAnimationClassName` | `string` | Нет | Пробрасывается в `ExpandAnimation` (`className` на контейнер с animated max-height). |
|
|
63
|
+
| `children` | `never` | — | Явно запрещены через `never` — только render-props. |
|
|
64
|
+
|
|
65
|
+
### `IAccordionHeaderProvideProps` (передаётся в `renderHeader`)
|
|
66
|
+
|
|
67
|
+
| Поле | Тип | Описание |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `isOpen` | `boolean` | Текущее состояние (учитывает controlled/uncontrolled). |
|
|
70
|
+
| `toggle` | `() => void` | Вызов для переключения. Потребитель должен повесить на клик/keypress. |
|
|
71
|
+
| `animating` | `boolean` | `true` во время анимации раскрытия/сворачивания. |
|
|
72
|
+
| `ariaAttributes` | `TAriaHTMLAttributes` | Готовые aria-атрибуты для header: `controls` (id body) и `expanded` (строка "true"/"false"). Потребитель обязан применить их к интерактивному элементу. |
|
|
73
|
+
|
|
74
|
+
### `IAccordionBodyProvideProps` (передаётся в `renderBody`)
|
|
75
|
+
|
|
76
|
+
| Поле | Тип | Описание |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `isOpen` | `boolean` | Текущее состояние. |
|
|
79
|
+
| `toggle` | `() => void` | Доступен и в body — например, чтобы закрыть изнутри. |
|
|
80
|
+
| `animating` | `boolean` | `true` во время анимации. Полезно для условного размонтирования контента. |
|
|
81
|
+
|
|
82
|
+
**В body `ariaAttributes` не передаётся** — aria-id ставится на контейнер
|
|
83
|
+
`ExpandAnimation` автоматически (атрибут `id={instanceId}`), связь `aria-controls` →
|
|
84
|
+
`id` работает через него.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Ключевые особенности реализации
|
|
89
|
+
|
|
90
|
+
### Controlled/uncontrolled mode
|
|
91
|
+
|
|
92
|
+
Определяется **один раз в конструкторе** по `typeof props.isOpen !== "undefined"`
|
|
93
|
+
и сохраняется в `state.controlled`. Это значит: **если потребитель начал с `isOpen=undefined`,
|
|
94
|
+
а потом передал `true` — переход в controlled mode НЕ произойдёт**. Следствие: не меняй
|
|
95
|
+
управляющий контракт в рантайме, фиксируй его при первом рендере.
|
|
96
|
+
|
|
97
|
+
### Класс-компонент, не forwardRef
|
|
98
|
+
|
|
99
|
+
`AccordionBase extends React.Component` — единственный случай class-компонента среди
|
|
100
|
+
документированных базовых примитивов. Не переписывать на функциональный компонент без
|
|
101
|
+
согласования: наследники (например, `CollapsableTreeNode`) тоже class-компоненты и
|
|
102
|
+
полагаются на lifecycle-методы (`componentDidUpdate`).
|
|
103
|
+
|
|
104
|
+
**`forwardRef` здесь не применяется** — это внутренний API, и ref на корневой узел
|
|
105
|
+
не имеет смысла (render возвращает фрагмент `<>{header}{body}</>`, корневого DOM-элемента
|
|
106
|
+
нет).
|
|
107
|
+
|
|
108
|
+
### `uniqueId` из `lodash-es`
|
|
109
|
+
|
|
110
|
+
`instanceId = \`AccordionBase-${uniqueId()}\`` — генерируется один раз при создании.
|
|
111
|
+
Используется для связи `aria-controls` (на header) → `id` (на body-контейнере). Не
|
|
112
|
+
заменять на `React.useId` — компонент классовый, и любой переход на `useId` требует
|
|
113
|
+
перевода на функциональный компонент целиком.
|
|
114
|
+
|
|
115
|
+
### `animating` не делает два разных состояния
|
|
116
|
+
|
|
117
|
+
`animating` устанавливается в `true` при смене `isOpen` (в `componentDidUpdate` для
|
|
118
|
+
controlled, в `toggle` для uncontrolled) и сбрасывается в `false` в коллбеке
|
|
119
|
+
`ExpandAnimation.onEnd`. Между двумя этими событиями `onToggle` НЕ вызывается —
|
|
120
|
+
он стреляет только в `setState-callback` после сброса `animating`. Значит: `onToggle`
|
|
121
|
+
— сигнал "анимация завершена", не "клик произошёл".
|
|
122
|
+
|
|
123
|
+
### `expandAnimationClassName` и закомментированные классы
|
|
124
|
+
|
|
125
|
+
В `renderBody` есть закомментированный блок со старыми глобальными классами
|
|
126
|
+
(`cssClass[globalExpandAnimationAnimating]`, `cssClass[globalExpandAnimationOpened]`).
|
|
127
|
+
Это artifact миграции со старой LESS-системы — не расширять, не включать обратно
|
|
128
|
+
без согласования, сейчас дополнительные классы отсутствуют намеренно.
|
|
129
|
+
|
|
130
|
+
### Render возвращает Fragment
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
return (
|
|
134
|
+
<>
|
|
135
|
+
{header}
|
|
136
|
+
{body}
|
|
137
|
+
</>
|
|
138
|
+
);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Нет обёртывающего DOM-узла. Потребитель renderHeader/renderBody сам решает, нужен
|
|
142
|
+
ли корневой контейнер. Это делает компонент максимально неинвазивным для вёрстки
|
|
143
|
+
потребителя.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Инварианты
|
|
148
|
+
|
|
149
|
+
- **Class-компонент на `React.Component`** — не переписывать на функциональный без
|
|
150
|
+
согласования. Наследники завязаны на lifecycle-методы.
|
|
151
|
+
- **`displayName = "AccordionBase"`** — публичное имя для React DevTools, не
|
|
152
|
+
переименовывать (используется при дебаге деревьев).
|
|
153
|
+
- **`controlled` вычисляется в конструкторе и не меняется** — не добавлять логику
|
|
154
|
+
переключения режимов в рантайме.
|
|
155
|
+
- **`onToggle` стреляет после анимации, `toggle` — при клике** — не менять семантику,
|
|
156
|
+
наследники полагаются на неё (`CollapsableTreeNode` использует `onToggle` для
|
|
157
|
+
синхронизации состояния дерева).
|
|
158
|
+
- **`children?: never`** в `IAccordionBaseProps` — не превращать в `children: ReactNode`.
|
|
159
|
+
Компонент намеренно render-props, не children-as-function.
|
|
160
|
+
- **`ariaAttributes` для header** — не изменять набор передаваемых полей (`controls`,
|
|
161
|
+
`expanded`), потребители (`CollapsableTreeNode`, `Spoiler`) раскладывают их
|
|
162
|
+
спредом на свой `<button>`.
|
|
163
|
+
- **Отсутствие `index.ts` и `protected/` в пути** — сигнал internal API. Не
|
|
164
|
+
добавлять barrel, не выносить из `protected/` без обсуждения архитектуры
|
|
165
|
+
публичного экспорта.
|
|
166
|
+
- **Зависимость от `ExpandAnimation`** — AccordionBase не реализует анимацию сам,
|
|
167
|
+
всегда делегирует. Не дублировать max-height логику здесь.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Accessibility
|
|
172
|
+
|
|
173
|
+
AccordionBase сам по себе не ставит ARIA-роли и не обрабатывает клавиатуру —
|
|
174
|
+
это задача потребителя `renderHeader`. Что AccordionBase даёт:
|
|
175
|
+
|
|
176
|
+
- **`ariaAttributes.controls`** — уникальный id (`AccordionBase-{n}`), связывающий
|
|
177
|
+
header с body. Потребитель **обязан** применить к header-элементу:
|
|
178
|
+
`<button aria-controls={ariaAttributes.controls} aria-expanded={ariaAttributes.expanded}>`.
|
|
179
|
+
- **`ariaAttributes.expanded`** — строка `"true"` / `"false"` (не boolean, уже
|
|
180
|
+
приведено через `.toString()`).
|
|
181
|
+
- **`id` на body-контейнере** — автоматически применяется к `ExpandAnimation`,
|
|
182
|
+
чтобы `aria-controls` указывал на валидный элемент.
|
|
183
|
+
|
|
184
|
+
**Что обязан сделать потребитель:**
|
|
185
|
+
- Использовать семантический интерактивный элемент в `renderHeader` (`<button>`,
|
|
186
|
+
не `<div onClick>`), чтобы клавиатура и screen reader работали из коробки.
|
|
187
|
+
- Повесить `toggle` на `onClick` (и, при необходимости, на `onKeyDown` для кастомных
|
|
188
|
+
триггеров).
|
|
189
|
+
- При необходимости передать `role="region"` и `aria-labelledby` в body (AccordionBase
|
|
190
|
+
не делает этого автоматически).
|
|
191
|
+
|
|
192
|
+
**Что НЕ делает AccordionBase:** управление фокусом, обработка клавиатурных событий,
|
|
193
|
+
`aria-disabled`, `role` — всё это задача потребителя.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Дизайн-токены
|
|
198
|
+
|
|
199
|
+
Нет — компонент без собственных стилей.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Связанные компоненты
|
|
204
|
+
|
|
205
|
+
- `ExpandAnimation` (`src/components/ExpandAnimation`) — оборачивает body, реализует
|
|
206
|
+
анимацию max-height. AccordionBase использует напрямую, без возможности замены.
|
|
207
|
+
- `CollapsableTree` / `CollapsableTreeNode` (`src/components/CollapsableTree`) —
|
|
208
|
+
главный потребитель. Оборачивает AccordionBase в узел TreeView с клавиатурной
|
|
209
|
+
навигацией и интеграцией в дерево.
|
|
210
|
+
- `Spoiler` (`src/components/Spoiler`) — концептуально близкий компонент (раскрывающийся
|
|
211
|
+
блок), в текущей реализации не использует AccordionBase, но мог бы.
|
|
212
|
+
- `lodash-es` (`uniqueId`) — внешняя зависимость для генерации instanceId.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Stories
|
|
217
|
+
|
|
218
|
+
Нет — internal-компонент без UI, покрывается stories/тестами потребителей
|
|
219
|
+
(`CollapsableTree`).
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## История изменений
|
|
224
|
+
|
|
225
|
+
| Дата | Изменение |
|
|
226
|
+
|---|---|
|
|
227
|
+
| 2026-04-18 | Создан документ |
|