@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.
Files changed (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. 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
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 | Создан документ |