@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.48.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.
Files changed (48) hide show
  1. package/README.md +7 -7
  2. package/bundle/components/AmountField.md +222 -323
  3. package/bundle/components/Avatar.md +138 -146
  4. package/bundle/components/CardStatic.md +402 -0
  5. package/bundle/components/CheckboxTree.md +157 -215
  6. package/bundle/components/CheckboxTreeExtended.md +7 -7
  7. package/bundle/components/ChipGroup.md +1 -1
  8. package/bundle/components/ChipMonthYearPicker.md +4 -4
  9. package/bundle/components/ChipSuggest.md +7 -6
  10. package/bundle/components/CollapsibleTree.md +8 -4
  11. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  12. package/bundle/components/Confirm.md +346 -281
  13. package/bundle/components/DateRange.md +351 -210
  14. package/bundle/components/FooterPage.md +1 -0
  15. package/bundle/components/FormField.md +11 -4
  16. package/bundle/components/FormGroup.md +3 -2
  17. package/bundle/components/LightBox.md +8 -0
  18. package/bundle/components/ListMaster.md +2 -2
  19. package/bundle/components/ListTableItem.md +6 -6
  20. package/bundle/components/MonthYearField.md +15 -4
  21. package/bundle/components/Slider.md +389 -97
  22. package/bundle/components/SliderRange.md +486 -105
  23. package/bundle/components/StatusTracker.md +386 -94
  24. package/bundle/components/Stepper.md +355 -102
  25. package/bundle/components/StepperExtended.md +633 -0
  26. package/bundle/components/SuggestField.md +1049 -96
  27. package/bundle/components/SwipeableArea.md +7 -7
  28. package/bundle/components/Tabs.md +265 -92
  29. package/bundle/components/TabsExtended.md +18 -18
  30. package/bundle/components/TabsLine.md +301 -127
  31. package/bundle/components/TagColor.md +187 -66
  32. package/bundle/components/TagGroup.md +184 -48
  33. package/bundle/components/TextareaField.md +278 -70
  34. package/bundle/components/TopOverlay.md +17 -1
  35. package/bundle/components/TreeView.md +16 -7
  36. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  37. package/bundle/components/UploadZone.md +318 -0
  38. package/bundle/guides/codestyle.md +16 -1
  39. package/bundle/guides/commits.md +47 -1
  40. package/bundle/guides/tests.md +23 -2
  41. package/bundle/manifest.json +48 -21
  42. package/bundle/release-notes/1.47.0.mdx +115 -0
  43. package/bundle/release-notes/1.48.0.mdx +68 -0
  44. package/bundle/release-notes/1.49.0.mdx +7 -0
  45. package/bundle/tokens.json +3 -3
  46. package/dist/index.js +220 -6
  47. package/dist/index.js.map +1 -1
  48. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: CheckboxTree
3
3
  category: Inputs
4
- figma-node: ""
5
- figma-file: ""
6
- related: [CheckboxTreeExtended, Checkbox, CheckboxYGroup, CheckboxXGroup]
4
+ related: [CheckboxTreeExtended, Checkbox]
7
5
  tokens: []
8
6
  stories: stories/CheckboxTree/CheckboxTree.stories.tsx
9
7
  version: "1.0"
@@ -13,263 +11,206 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- "Готовый" компонент дерева чекбоксов с автоматической логикой
17
- checked/bulk-состояний: при клике на родителя все потомки чекаются/
18
- разчекаются; при частичном выборе потомков родитель получает `bulk: true`
19
- (состояние "indeterminate").
14
+ Дерево чекбоксов, которое строится из массива данных и само связывает состояние
15
+ родителей и потомков. Обёртка над `CheckboxTreeExtended`: разметку узлов
16
+ компонент формирует сам, потребитель передаёт только описание дерева
17
+ (`ICheckboxTreeCheckboxData[]`) и получает в `onChange` новый массив с
18
+ пересчитанными флагами `checked` и `bulk`.
20
19
 
21
- Является **тонкой data-driven обёрткой** над `CheckboxTreeExtended`:
22
- потребитель передаёт плоское описание дерева (`ICheckboxTreeCheckboxData[]`),
23
- компонент сам рендерит `CheckboxTreeExtended.Node` + `.Checkbox`, управляет
24
- состоянием и отдаёт обновлённый массив в `onChange`.
20
+ Каскадная логика — единственное, что компонент добавляет к
21
+ `CheckboxTreeExtended`:
25
22
 
26
- Используй когда: данные дерева — линейные/сериализуемые (например, из
27
- бэкенда), нужна стандартная семантика bulk/чек-каскада, не нужны кастомные
28
- UI-вставки в узлы.
29
- Не используй когда: нужен кастомный рендер узла (заголовки, иконки,
30
- доп. контент) — бери `CheckboxTreeExtended` напрямую; нужны 1–2 независимых
31
- чекбокса (бери `Checkbox` / `CheckboxYGroup`).
23
+ - выбор узла выбирает всех его потомков, снятие — снимает всех;
24
+ - если выбрана только часть потомков, родитель становится выбранным частично
25
+ (`bulk`) и рисует bulk-иконку вместо галочки;
26
+ - клик по частично выбранному узлу выбирает поддерево целиком, а не снимает
27
+ выбор;
28
+ - флаги пересчитываются снизу вверх по всему дереву на каждое изменение.
32
29
 
33
- ---
34
-
35
- ## Файловая структура
36
-
37
- ```text
38
- src/components/CheckboxTree/
39
- ├── CheckboxTree.tsx # React.FC обёртка над CheckboxTreeExtended
40
- ├── types.ts # ICheckboxTreeCheckboxData
41
- ├── utils.ts # traverseCheckboxes, checkParentCheckboxes, checkChildrenCheckboxes
42
- ├── index.ts # Barrel: компонент + types + utils
43
- └── __tests__/
44
- ```
30
+ Используй когда: есть готовая иерархия значений (категории, права доступа,
31
+ фильтры) и нужна стандартная логика «родитель ↔ потомки» без ручного
32
+ пересчёта.
45
33
 
46
- Особенности:
47
- - **Нет своих стилей** — весь визуальный вид из `CheckboxTreeExtended` и его
48
- внутреннего `Checkbox`.
49
- - **Нет `forwardRef`** — FC без ref-форвардинга. Это осознанное упрощение
50
- (потребитель обычно работает с самими `checkboxes` через onChange, не с DOM).
51
- Если нужен ref — переключаться на `CheckboxTreeExtended`.
52
- - **Utils экспортируются через barrel** (`traverseCheckboxes`,
53
- `checkParentCheckboxes`, `checkChildrenCheckboxes`) — намеренно, потребитель
54
- может использовать их для предварительной обработки данных или тестов.
34
+ Не используй когда: нужна своя структура данных, произвольный контент рядом с
35
+ чекбоксом, ленивая загрузка ветки или другая логика связи родителя и потомков —
36
+ бери `CheckboxTreeExtended` и описывай узлы вручную. Если иерархии нет вовсе,
37
+ достаточно `CheckboxYGroup`.
55
38
 
56
39
  ---
57
40
 
58
41
  ## Варианты и props
59
42
 
60
- ### `ICheckboxTreeProps extends Omit<ICheckboxTreeExtendedProps, "children" | "onChange">`
61
-
62
- Наследует от `CheckboxTreeExtended` всё, кроме `children` (ставится из
63
- `checkboxes`) и `onChange` (своя сигнатура).
64
-
65
- | Prop | Тип | Обязателен | Default | Описание |
66
- |---|---|---|---|---|
67
- | `checkboxes` | `ICheckboxTreeCheckboxData[]` | Да | — | Плоский массив (каждый элемент может иметь `children`) — описание дерева. |
68
- | `onChange` | `(options: ICheckboxTreeCheckboxData[]) => void` | Да | — | Получает **новую копию** массива после каскадной обработки. Потребитель должен сохранить её в свой state. |
69
- | `size` | `EComponentSize` | Нет | `MD` | Размер чекбоксов (SM/MD/LG). Форвардится в CheckboxTreeExtended. |
70
- | `children` | `never` | — | — | **Запрещён** через `never` — компонент сам генерирует DOM. |
43
+ `ICheckboxTreeProps` наследует `ICheckboxTreeExtendedProps` без `children` и
44
+ `onChange`, поэтому тип разрешает и остальные атрибуты `<ul>` (`className`,
45
+ `id`, `data-*`). Собственными компонент считает только `checkboxes`, `onChange`
46
+ и `size`, всё остальное уходит через `...rest` на корневой `<ul role="tree">`.
47
+ `className` при этом складывается с внутренними классами, а не заменяет их.
48
+
49
+ | Prop | Тип | По умолчанию | Описание |
50
+ |---|---|---|---|
51
+ | `checkboxes` | `ICheckboxTreeCheckboxData[]` | — | Описание дерева. Вложенность задаётся полем `children` элементов. Обязателен |
52
+ | `onChange` | `(options: ICheckboxTreeCheckboxData[]) => void` | — | Вызывается после каждого клика по чекбоксу — с новым массивом верхнего уровня и уже пересчитанными флагами. Обязателен |
53
+ | `size` | `EComponentSize` | `EComponentSize.MD` | Размер чекбоксов и отступов веток. Раздаётся через контекст `CheckboxTreeExtended`, в DOM не попадает. В мобильном представлении всегда `MD` |
54
+ | `children` | `never` | — | Запрещён типом: разметку узлов формирует сам компонент |
55
+ | `ref` | `React.Ref<HTMLUListElement>` | — | Указывает на корневой `<ul role="tree">` |
56
+ | остальные | `React.HTMLAttributes<HTMLUListElement>` | — | Прокидываются на корневой `<ul>` как есть |
71
57
 
72
58
  ### `ICheckboxTreeCheckboxData`
73
59
 
74
- Единственный тип данных дерева:
75
-
76
- ```ts
77
- interface ICheckboxTreeCheckboxData {
78
- id: string; // уникальный — ключ и aria-поддержка
79
- label: React.ReactNode; // подпись
80
- checked: boolean; // текущее состояние
81
- bulk?: boolean; // "indeterminate", рассчитывается автоматически
82
- children?: ICheckboxTreeCheckboxData[]; // рекурсивно
83
- }
84
- ```
85
-
86
- **`bulk` — вычисляется компонентом**, потребитель не должен устанавливать
87
- его руками. На входе можно передать `false`/`undefined`; после onChange
88
- массив вернётся с актуальным bulk.
89
-
90
- ---
91
-
92
- ## Ключевые особенности реализации
93
-
94
- ### Мутация массива на месте
95
-
96
- ```ts
97
- checkbox.checked = checkbox.bulk ? true : checked;
98
- checkChildrenCheckboxes(checkbox);
99
- traverseCheckboxes(checkboxes, checkParentCheckboxes);
100
- onChange([...checkboxes]);
101
- ```
102
-
103
- Компонент **мутирует** исходный `checkboxes` массив (меняет `checked`/`bulk`
104
- на узлах) и только на outer-level передаёт новую ссылку через спред. Это
105
- значит:
106
- - Потребитель НЕ должен передавать readonly/immutable данные (Object.freeze
107
- сломает).
108
- - Потребитель должен либо хранить в state `useState` (React сравнит ссылку
109
- верхнего уровня), либо всегда клонировать перед передачей.
110
- - Внутренние референсы не меняются — потомки, которые хранят ссылку на
111
- конкретный узел, увидят обновлённое значение.
112
-
113
- Это архитектурный компромисс ради простоты алгоритма. **Не рефакторить
114
- на immutable-подход без серьёзного повода** — сломает внешний контракт.
115
-
116
- ### `bulk → checked = true` при клике на bulk-узел
117
-
118
- ```ts
119
- checkbox.checked = checkbox.bulk ? true : checked;
120
- ```
121
-
122
- Клик по узлу в `bulk`-состоянии (частично выбран) делает **всех** потомков
123
- `checked: true`, не снимает выбор. Это соответствует стандартной UX
124
- tri-state чекбокса: indeterminate → всё выбрано (не → ничего).
125
-
126
- ### `checkChildrenCheckboxes` — рекурсивный каскад вниз
127
-
128
- ```ts
129
- checkbox.children.forEach((c) => {
130
- c.checked = checkbox.checked;
131
- checkChildrenCheckboxes(c);
132
- });
133
- ```
60
+ Описание одного узла. Поля `checked` и `bulk` у узлов с потомками
61
+ пересчитываются компонентом — задавать их вручную имеет смысл только как
62
+ начальное состояние.
134
63
 
135
- После клика на узел все его потомки получают то же `checked`-значение.
136
- `bulk` у потомков НЕ сбрасывается в этой функции — его потом пересчитает
137
- `checkParentCheckboxes` при обходе снизу-вверх.
138
-
139
- ### `checkParentCheckboxes` — каскад вверх (post-order обход)
140
-
141
- ```ts
142
- if (c.children) traverseCheckboxes(c.children, cb);
143
- cb(c);
144
- ```
145
-
146
- `traverseCheckboxes` идёт **post-order** (сначала потомки, потом узел) —
147
- это ключевое: чтобы `checkParentCheckboxes` к моменту обработки узла видел
148
- актуальное состояние потомков.
149
-
150
- Логика узла:
151
- - Все потомки checked → `checked: true`, `bulk: true` только если у кого-то
152
- был bulk (не обычный полный чек).
153
- - Часть потомков checked → `checked: true, bulk: true`.
154
- - Никого не checked → `checked: false`.
155
-
156
- **Нюанс:** "все checked" с хотя бы одним bulk → родитель тоже bulk. То есть
157
- bulk распространяется вверх, даже если с точки зрения текущего уровня всё
158
- выбрано.
159
-
160
- ### `onChange([...checkboxes])` — shallow clone
161
-
162
- ```ts
163
- onChange([...checkboxes]);
164
- ```
64
+ | Поле | Тип | Описание |
65
+ |---|---|---|
66
+ | `id` | `string` | Идентификатор узла, уникальный в пределах всего дерева |
67
+ | `label` | `React.ReactNode` | Видимая подпись чекбокса. За счёт вложенности `<input>` в корневой `<label>` она же становится доступным именем |
68
+ | `checked` | `boolean` | Признак выбора |
69
+ | `bulk` | `boolean` | Признак частичного выбора: bulk-иконка вместо галочки |
70
+ | `children` | `ICheckboxTreeCheckboxData[]` | Вложенные узлы |
165
71
 
166
- Только outer array клонируется — узлы внутри те же мутированные объекты.
167
- Это совпадает с тем, что нужно React для перерендера при `useState`
168
- (новая ссылка на массив).
72
+ ### Утилиты пересчёта
169
73
 
170
- ### render-функция `renderNode` с prev/next linking
74
+ `utils.ts` экспортируется через barrel, поэтому три функции — часть публичного
75
+ API. Они предназначены для случая, когда потребитель считает состояние дерева
76
+ сам; рабочий пример — `stories/Chips/examples/ChipMultiselect/WithCheckboxTreeExample.tsx`,
77
+ где они импортируются из `@sberbusiness/triplex-next` и пересчитывают дерево
78
+ опций внутри `ChipMultiselect`:
171
79
 
172
- ```tsx
173
- <CheckboxTreeExtended.Node
174
- prevNodeId={prevCheckbox?.id}
175
- nextNodeId={nextCheckbox?.id}
176
- />
177
- ```
178
-
179
- CheckboxTreeExtended использует prev/next для клавиатурной навигации
180
- (стрелки вверх/вниз между узлами). CheckboxTree автоматически вычисляет
181
- их из массива. Это значит, что arrow-navigation работает только в пределах
182
- одного parent'а — между siblings. **Не менять порядок вызовов в children**:
183
- первый элемент родителя получает prevNodeId=undefined, и это ожидаемо.
184
-
185
- ### Функциональный чекбокс через render-проп
80
+ | Функция | Что делает |
81
+ |---|---|
82
+ | `traverseCheckboxes(checkboxes, callback)` | Обход в глубину **снизу вверх**: callback вызывается сначала для потомков, затем для родителя |
83
+ | `checkParentCheckboxes(checkbox)` | Пересчитывает `checked` / `bulk` узла по его прямым потомкам. Узел без потомков не трогает |
84
+ | `checkChildrenCheckboxes(checkbox)` | Проставляет `checked` узла всем его потомкам вниз по дереву |
186
85
 
187
- ```tsx
188
- checkbox={(props) => (
189
- <CheckboxTreeExtended.Checkbox {...props} onChange={...} bulk={...} checked={...}>
190
- {checkbox.label}
191
- </CheckboxTreeExtended.Checkbox>
192
- )}
193
- ```
86
+ Порядок обхода в `traverseCheckboxes` — не деталь реализации, а контракт:
87
+ `checkParentCheckboxes` работает корректно только если потомки уже пересчитаны.
194
88
 
195
- Node принимает `checkbox` как функцию, получающую provide-props (id,
196
- aria-атрибуты). CheckboxTree передаёт их наружу и добавляет свои
197
- onChange/bulk/checked. **Не упрощать** — CheckboxTreeExtended полагается
198
- на render-проп для управления фокусом и ARIA.
89
+ ---
199
90
 
200
- ### `onChange={handleChange(checkbox)}` — каррирование
91
+ ## Дизайн-токены
201
92
 
202
- Замыкание на конкретный `checkbox` через первый аргумент `handleChange`.
203
- Позволяет внутри обработчика знать, какой именно узел меняется. Важно:
204
- `checkbox` — ссылка на объект в массиве, который мутирует. Если потребитель
205
- заменит массив на новый — замыкание станет устаревшим, но это решается
206
- новым рендером (новая функция `handleChange` захватит свежий объект).
93
+ Собственных стилей и токенов у компонента нет — он не содержит ни одного
94
+ LESS-модуля. Всё оформление приходит из `CheckboxTreeExtended` (отступы веток,
95
+ общий токен `Checkbox.BorderColor_Focus`) и из `Checkbox` (цвета, размеры,
96
+ состояния). Переопределять их нужно у этих компонентов — см.
97
+ `CheckboxTreeExtended-ai.md` и `Checkbox-ai.md`.
207
98
 
208
99
  ---
209
100
 
210
101
  ## Инварианты
211
102
 
212
- - **`children: never`** — не делать компонент композитным, он data-driven.
213
- Для композитного — `CheckboxTreeExtended`.
214
- - **`checkboxes` мутируется на месте** — не передавать Object.freeze /
215
- immutable. Не рефакторить на immer/immutability без серьёзного повода.
216
- - **`bulk` вычисляется автоматически** — не устанавливать руками во
217
- входных данных (перезатрётся).
218
- - **`id` в каждом узле — уникальный** по всему дереву (не просто в рамках
219
- одного parent'а). Используется как React `key` и для a11y.
220
- - **`size` → CheckboxTreeExtended** — наследуется, значение по умолчанию MD.
221
- - **`onChange` отдаёт новую shallow-copy массива** — не менять на передачу
222
- исходной ссылки (сломает `useState`-сравнение).
223
- - **Post-order обход в `traverseCheckboxes`** — не менять на pre-order,
224
- иначе `checkParentCheckboxes` будет работать на неактуальных данных.
225
- - **`checkbox.checked = checkbox.bulk ? true : checked`** — "bulk → всё в
226
- true". Не инвертировать на "bulk → всё в false" без согласования UX.
103
+ - **Компонент мутирует переданные объекты.** `handleChange` пишет `checked` и
104
+ `bulk` прямо в элементы массива `checkboxes`, а в `onChange` отдаёт поверхностную
105
+ копию массива верхнего уровня (`[...checkboxes]`). Идентичность самих узлов при
106
+ этом сохраняется. На этом держится связка с `useState` у потребителя (новая
107
+ ссылка на массив → перерисовка), и это наблюдаемое поведение: переход на
108
+ иммутабельный пересчёт — breaking change, а не рефакторинг.
109
+ - **`children` запрещён типом** (`children?: never`). Разметка узлов —
110
+ ответственность компонента; своя разметка — это `CheckboxTreeExtended`.
111
+ - **`id` узла обязан быть уникальным во всём дереве.** По `id` строятся
112
+ `prevNodeId` / `nextNodeId`, а по ним `TreeView` ведёт клавиатурную навигацию.
113
+ Дубликаты ломают перемещение по дереву.
114
+ - **Порядок клавиатурного обхода задаётся порядком массива** на каждом уровне:
115
+ соседями узла считаются элементы `checkboxes` (или `children`) по соседним
116
+ индексам.
117
+ - **Дерево всегда полностью раскрыто** — флаг `isStaticCheckboxTreeExtended` в
118
+ `CheckboxTreeExtended` прячет стрелки и раскрывает все узлы. Сворачивания у
119
+ `CheckboxTree` нет по построению.
120
+ - **`ref` указывает на корневой `<ul role="tree">`.** Компонент обёрнут
121
+ `forwardRef`, ссылка проходит насквозь через `CheckboxTreeExtended` →
122
+ `CollapsibleTreeExtended` → `TreeView`. Собственного DOM-узла у `CheckboxTree`
123
+ нет, поэтому другого разумного адресата у `ref` тоже нет.
124
+ - **Публичный API** — имена `ICheckboxTreeProps`, `ICheckboxTreeCheckboxData`,
125
+ функции `traverseCheckboxes`, `checkParentCheckboxes`,
126
+ `checkChildrenCheckboxes` и состав barrel `index.ts` менять нельзя.
127
+ `displayName` — `"CheckboxTree"`.
227
128
 
228
129
  ---
229
130
 
230
- ## Accessibility
231
-
232
- - Всё управление a11y — внутри `CheckboxTreeExtended` (role="tree"/"treeitem",
233
- `aria-checked` (true/false/mixed), клавиатурная навигация).
234
- - `bulk: true` → `aria-checked="mixed"` (indeterminate-состояние) на узле.
235
- - `id` узла используется в `aria-controls` / `headers`-подобных связях —
236
- обязательно уникален.
237
- - Навигация:
238
- - Tab — переход внутри дерева/наружу.
239
- - Space/Enter — toggle чекбокса.
240
- - Arrow Up/Down — между siblings через prev/nextNodeId.
241
- - Если нужна expand/collapse семантика — это задача CheckboxTreeExtended
242
- (через Node's expand-проп, если используется).
131
+ ## Известные ограничения
132
+
133
+ Найдено при AI-рефакторинге (TRI-22). Поведение оставлено как есть — его меняют
134
+ только отдельным решением, это публичный контракт.
135
+
136
+ - **Вход в поддерево с клавиатуры не переводит фокус.** Пока фокус стоит на
137
+ `<li>` ветки, стрелка вниз меняет активный узел на её потомка, но фокус
138
+ остаётся на ветке. Причина — guard в `CheckboxTreeExtendedCheckbox`:
139
+ `if (!document.activeElement?.contains(checkboxNode.current))`. Он защищается
140
+ от лишнего перевода фокуса при клике мышью, но `contains` истинно и для любого
141
+ предка, а `<li>` ветки содержит чекбоксы всех своих потомков. При переходе на
142
+ узел вне текущего поддерева фокус двигается штатно. Лечится на уровне
143
+ `CheckboxTreeExtended` (сравнивать с самим узлом, а не через `contains`) —
144
+ наблюдаемое изменение, отдельное решение и запись в release notes.
145
+ Покрыто тестом `Should move focus between nodes on ArrowDown`, который
146
+ фиксирует текущее поведение, включая это залипание. Когда ограничение
147
+ починят, тест упадёт на трёх «холостых» `ArrowDown` в начале — это ожидаемо,
148
+ его нужно будет переписать под новое поведение.
149
+ - **`bulk` у листа не пересчитывается.** `checkParentCheckboxes` выходит сразу
150
+ для узла без потомков, поэтому флаг остаётся таким, каким его задал
151
+ потребитель. Лист с `bulk: true` невозможно снять кликом: `checked` ставится
152
+ по правилу «клик по частичному выбирает целиком». Задавать `bulk` листу не
153
+ нужно — флаг осмыслен только для узлов с потомками.
154
+ - **`bulk` не сбрасывается, когда все потомки сняты.** В ветке «не выбран ни
155
+ один потомок» пишется только `checked = false`. Визуально это незаметно:
156
+ `Checkbox` показывает иконку (галочку или bulk) только при `checked`, а при
157
+ следующем выборе флаг всё равно пересчитается.
158
+ - **`<li> cannot appear as a descendant of <li>`** — предупреждение React на
159
+ первом рендере вложенных узлов. Приходит из `TreeView` и общее для всего
160
+ семейства (`CollapsibleTree`, `CollapsibleTreeExtended`, `CheckboxTreeExtended`),
161
+ лечится только на уровне `TreeView`.
243
162
 
244
163
  ---
245
164
 
246
- ## Дизайн-токены
165
+ ## Accessibility
247
166
 
248
- Нет своих. Всё через `CheckboxTreeExtended` и внутренний `Checkbox`.
167
+ - Семантику (`<ul role="tree">`, `<li>`, группы) и клавиатурную навигацию
168
+ обеспечивает `TreeView` через `CheckboxTreeExtended`. `CheckboxTree` добавляет
169
+ к этому только связывание соседних узлов: каждому `CheckboxTreeExtended.Node`
170
+ передаются `prevNodeId` и `nextNodeId` соседей по массиву.
171
+ - Фокус при навигации с клавиатуры забирает чекбокс активного узла — это делает
172
+ `CheckboxTreeExtended.Checkbox`, подробности в `CheckboxTreeExtended-ai.md` →
173
+ «Accessibility».
174
+ - Подпись чекбокса — поле `label`. Связь подписи и поля структурная: в
175
+ `Checkbox` корневой элемент — `<label>`, а `<input type="checkbox">` лежит
176
+ внутри него (`htmlFor` / `id` не используются), поэтому связь работает и при
177
+ пустом `label` — только доступного имени у такого чекбокса не будет.
178
+ Собственных текстов компонент не содержит: библиотека мультиязычная, и
179
+ доступное имя для узла без видимой подписи передаёт потребитель через `label`.
180
+ - Частичный выбор кодируется только визуально (bulk-иконка). `aria-checked="mixed"`
181
+ компонент не выставляет — под капотом нативный `<input type="checkbox">` без
182
+ `indeterminate`. Если полумерное состояние нужно ассистивным технологиям,
183
+ это отдельная доработка `Checkbox`.
249
184
 
250
185
  ---
251
186
 
252
187
  ## Связанные компоненты
253
188
 
254
- - `CheckboxTreeExtended` (`src/components/CheckboxTreeExtended`) — нижний
255
- уровень, composable, с полным контролем рендера узлов.
256
- - `Checkbox` (`src/components/Checkbox`) — одиночный чекбокс.
257
- - `CheckboxYGroup` / `CheckboxXGroup` — группы плоских чекбоксов (без
258
- иерархии).
259
- - `EComponentSize` — общий enum SM/MD/LG.
189
+ - `CheckboxTreeExtended` (`src/components/CheckboxTreeExtended`) — нижний слой и
190
+ одновременно альтернатива. `ICheckboxTreeProps` наследует его props, а
191
+ `CheckboxTree` рендерит `CheckboxTreeExtended`, `.Node` и `.Checkbox`. Бери
192
+ его, когда нужна своя разметка узлов или своя логика связи родителя и
193
+ потомков.
194
+ - `Checkbox` (`src/components/Checkbox`) — чекбокс каждого узла. Отсюда приходят
195
+ все токены, размеры и семантика `bulk` (иконка вместо галочки, видимость по
196
+ `checked`).
197
+ - `CheckboxYGroup` (`src/components/Checkbox`) — плоская вертикальная группа
198
+ чекбоксов. Подходит, когда иерархии нет и каскадный пересчёт не нужен.
199
+ - `MultiselectField` и `ChipMultiselect` — типовые потребители: показывают
200
+ `CheckboxTree` как дерево опций внутри выпадающего блока (см. их stories).
260
201
 
261
202
  ---
262
203
 
263
204
  ## Stories
264
205
 
265
- `stories/CheckboxTree/CheckboxTree.stories.tsx`
206
+ Основные истории: `stories/CheckboxTree/CheckboxTree.stories.tsx`
266
207
  Файлы примеров: `stories/CheckboxTree/examples/`
267
208
 
268
209
  | Story | Example file | Что демонстрирует |
269
210
  |---|---|---|
270
- | `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `size` + управляемое дерево |
271
- | `Default` | `DefaultExample.tsx` | Базовое дерево с одним уровнем вложенности |
272
- | `Sizes` | `SizesExample.tsx` | Все три размера чекбоксов |
211
+ | `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `size` на дереве из трёх веток |
212
+ | `Default` | `DefaultExample.tsx` | Минимальный сценарий: массив в `useState` потребителя, `onChange` передаётся сеттером |
213
+ | `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG рядом |
273
214
 
274
215
  ---
275
216
 
@@ -277,7 +218,8 @@ onChange/bulk/checked. **Не упрощать** — CheckboxTreeExtended пол
277
218
 
278
219
  | Дата | Изменение |
279
220
  |---|---|
280
- | 2026-04-18 | Создан документ |
221
+ | 2026-09-17 | TRI-22: добавлен `forwardRef` на корневой `<ul>` (насквозь через всё семейство Tree), прокидываются html-атрибуты, узел с `children: []` больше не становится выбранным. В «Известные ограничения» добавлено залипание фокуса при входе в поддерево |
222
+ | 2026-09-14 | Создан документ. Проведён AI-рефакторинг (TRI-22): JSDoc на props и полях `ICheckboxTreeCheckboxData`, убраны `eslint-disable` в `utils.ts`, рендер узлов вынесен в общий `renderNodes`; добавлены unit-тесты на утилиты и на каскадный пересчёт |
281
223
 
282
224
  ---
283
225
 
@@ -114,14 +114,13 @@ Checkbox.BorderColor_Focus
114
114
  отрисовывается. Файл и обе ветки кода намеренно оставлены — по TODO флаг
115
115
  снимут, когда сворачиваемое дерево станет стандартом. Не удаляй ветки
116
116
  `!isStaticCheckboxTreeExtended` как «мёртвый код».
117
- - **`forwardRef` нет ни у одной части** — `CheckboxTreeExtended`,
118
- `.Node` и `.Checkbox` объявлены как `React.FC`. Это осознанно и повторяет
119
- `CollapsibleTreeExtended`: под капотом class-компоненты `TreeView` /
120
- `TreeViewNode` с собственной работой с DOM. Добавление `forwardRef` меняет
121
- форму экспортируемого `ICheckboxTreeExtendedSFC` — это отдельное решение
122
- мейнтейнера, а не рефакторинг.
117
+ - **`ref` указывает на корневой `<ul role="tree">`.** Компонент обёрнут
118
+ `forwardRef`, ссылка уходит в `CollapsibleTreeExtended` и дальше в `TreeView`.
119
+ У `.Node` и `.Checkbox` своего `forwardRef` по-прежнему нет: `.Node` —
120
+ class-компонент, `.Checkbox` управляет фокусом сам.
123
121
  - **Публичный API** — имена `ICheckboxTreeExtendedProps`,
124
- `ICheckboxTreeExtendedSFC`, `ICheckboxTreeExtendedCheckboxProvideProps`,
122
+ `ICheckboxTreeExtendedFC` (и deprecated-алиас `ICheckboxTreeExtendedSFC`),
123
+ `ICheckboxTreeExtendedCheckboxProvideProps`,
125
124
  `ICheckboxTreeExtendedNodeProps`,
126
125
  статические `Checkbox` / `Node` и barrel-экспорты `index.ts` менять нельзя:
127
126
  на них опирается `CheckboxTree`, а также stories `Chips` и `MultiselectField`.
@@ -224,6 +223,7 @@ Checkbox.BorderColor_Focus
224
223
  | 2026-08-17 | `className` у `CheckboxTreeExtended.Node` больше не затирает собственный класс узла — классы мерджатся через `clsx` (правка по ревью PR #535) |
225
224
  | 2026-08-17 | Убран мёртвый селектор `.checkboxTreeCheckbox` из мобильной медиа-выборки (рабочие `> span` и `> svg` оставлены), `.caretIconWrapper:focus` заменён на `:focus-visible` (правки по ревью PR #535) |
226
225
  | 2026-08-17 | `ICheckboxTreeExtendedNodeProps` экспортируется — тип уходит в barrel и становится частью публичного API (решение владельца по ревью PR #535) |
226
+ | 2026-09-17 | TRI-22: `CheckboxTreeExtended` обёрнут `forwardRef` — `ref` отдаёт корневой `<ul>`; тип переименован в `ICheckboxTreeExtendedFC`, старое имя оставлено deprecated-алиасом |
227
227
 
228
228
  ---
229
229
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: ChipGroup
3
3
  category: Chips
4
- related: [Chip]
4
+ related: [Chip, TagGroup]
5
5
  tokens: []
6
6
  stories: stories/Chips/ChipGroup.stories.tsx
7
7
  version: "1.0"
@@ -216,7 +216,7 @@ export const DefaultExample = () => {
216
216
  <ChipMonthYearPicker
217
217
  value={value}
218
218
  label="MonthYear label"
219
- placeholder="мм.гггг"
219
+ placeholder="Select to proceed"
220
220
  onChange={setValue}
221
221
  alignment={EDropdownAlignment.LEFT}
222
222
  size={EComponentSize.MD}
@@ -248,7 +248,7 @@ const TypeItem = ({ type, label }: TypeItemProps) => {
248
248
  <ChipMonthYearPicker
249
249
  value={value}
250
250
  label="MonthYear label"
251
- placeholder="мм.гггг"
251
+ placeholder="Select to proceed"
252
252
  clearButtonProps={{ "aria-label": "Очистить" }}
253
253
  type={type}
254
254
  alignment={EDropdownAlignment.LEFT}
@@ -296,7 +296,7 @@ const SizeItem = ({ size, label }: SizeItemProps) => {
296
296
  alignment={EDropdownAlignment.LEFT}
297
297
  value={value}
298
298
  label="MonthYear label"
299
- placeholder="мм.гггг"
299
+ placeholder="Select to proceed"
300
300
  clearButtonProps={{ "aria-label": "Очистить" }}
301
301
  onChange={setValue}
302
302
  />
@@ -332,7 +332,7 @@ export const WithCustomDisplayedValueExample = () => {
332
332
  <ChipMonthYearPicker
333
333
  value={value}
334
334
  label="MonthYear label"
335
- placeholder="мм.гггг"
335
+ placeholder="Select to proceed"
336
336
  onChange={setValue}
337
337
  alignment={EDropdownAlignment.LEFT}
338
338
  size={EComponentSize.MD}
@@ -133,12 +133,13 @@ version: "1.0"
133
133
  переименование файла в папке `Suggest` ломает мобильный режим `ChipSuggest`.
134
134
  - Корневой `<div>` всегда получает класс `chipGroupItem` — на нём держатся отступы внутри
135
135
  `ChipGroup`.
136
- - `setForwardedRef` из `ChipSuggest/utils.ts` — внутренний хелпер, из barrel не
137
- экспортируется и в публичные `src/utils` не переносится. Это **не** конечное
138
- состояние: точно такая же функция уже лежит в `src/components/List/utils.ts`, и та же
139
- пятистрочка инлайнится ещё в полутора десятках компонентов (включая `Suggest.tsx`).
140
- Консолидация во внутренний shared-модуль — отдельная задача; не плоди третью копию,
141
- а при случае сошлись на неё.
136
+ - `setForwardedRef` живёт в `src/helpers/setForwardedRef.ts` — внутренний хелпер,
137
+ общий для `AmountField`, `ChipSuggest`, `FormField`, `List` и `SliderExtended`.
138
+ Из barrel не экспортируется и в публичные `src/utils` не переносится. Импортируется
139
+ относительным путём (`../../../helpers/setForwardedRef`), не через алиас
140
+ `@sberbusiness/triplex-next/...` — см. `docs/ai/codestyle.md`. Не плоди локальные копии:
141
+ та же пятистрочка ещё инлайнится в ряде компонентов (включая `Suggest.tsx`) — при
142
+ случае сошлись на общий модуль.
142
143
 
143
144
  ---
144
145
 
@@ -122,10 +122,13 @@ CollapsibleTree.Header_Shadow_Focus
122
122
 
123
123
  ## Инварианты
124
124
 
125
- - **`CollapsibleTree` — FC без `forwardRef`.** Под капотом class-компонент
126
- `TreeView`, ref не пробрасывается осмысленно. Тот же инвариант, что у
127
- `CollapsibleTreeExtended` и `CollapsibleTreeLeafNode` (тоже проксируют в
128
- class-компонент).
125
+ - **`CollapsibleTree` — FC без `forwardRef`.** С 1.47.0 это уже не общий
126
+ инвариант семейства: `CollapsibleTreeExtended` и `TreeView` под ним `ref`
127
+ пробрасывают на корневой `<ul role="tree">`, а `CollapsibleTree` его
128
+ по-прежнему теряет. Прежнее обоснование («под капотом class-компонент
129
+ `TreeView`») больше не действует — это оставшийся долг, а не решение.
130
+ Починка — наблюдаемое изменение публичного API, отдельная задача с записью
131
+ в release notes.
129
132
  - **`CollapsibleTreeNodeHeader` — `forwardRef<HTMLButtonElement>`** — ref
130
133
  идёт на корневой `<button>`, чтобы потребитель мог управлять фокусом
131
134
  заголовка (например, программно фокусировать конкретный узел).
@@ -227,6 +230,7 @@ CollapsibleTree.Header_Shadow_Focus
227
230
  | 2026-07-06 | Шеврон открытого узла теперь указывает вверх (`rotate(-90deg)`), а не вниз |
228
231
  | 2026-07-06 | Добавлено свойство `disabled` у `ICollapsibleTreeNodeBranch` и поддержка пропа `disabled` в `CollapsibleTreeNodeHeader`. Без новых токенов: шеврон приглушается глобальными классами `hoverable disabled` (icons-next), текст — через `type={EFontType.DISABLED}` у лейбла |
229
232
  | 2026-07-14 | Глобальный класс `hoverable` (icons-next) на кнопке заголовка теперь стоит всегда, а не только при `disabled` — для корректной окраски шеврона. Добавлен класс `active` на раскрытый узел с детьми (по паттерну `IconWrapper`/`Button`) |
233
+ | 2026-09-17 | TRI-22: инвариант про отсутствие `forwardRef` переформулирован — `CollapsibleTreeExtended` и `TreeView` под ним теперь пробрасывают `ref` на корневой `<ul>`, поэтому прежнее обоснование не действует, а `CollapsibleTree` остаётся долгом |
230
234
 
231
235
  ---
232
236