@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1

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 (74) hide show
  1. package/README.md +1 -0
  2. package/bundle/components/AlertProcess.md +6 -3
  3. package/bundle/components/AlertProcessSpoiler.md +216 -0
  4. package/bundle/components/Badge.md +4 -3
  5. package/bundle/components/ButtonDropdown.md +5 -3
  6. package/bundle/components/Carousel.md +671 -0
  7. package/bundle/components/CarouselExtended.md +7 -5
  8. package/bundle/components/CarouselIndicators.md +666 -0
  9. package/bundle/components/CarouselItem.md +579 -0
  10. package/bundle/components/CarouselNextButton.md +633 -0
  11. package/bundle/components/CarouselPrevButton.md +633 -0
  12. package/bundle/components/CarouselViewport.md +631 -0
  13. package/bundle/components/CheckboxTreeExtended.md +21 -7
  14. package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
  15. package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
  16. package/bundle/components/Chip.md +1 -0
  17. package/bundle/components/ChipDatePicker.md +332 -0
  18. package/bundle/components/ChipDatePickerTarget.md +316 -0
  19. package/bundle/components/ChipMonthYearPicker.md +4 -3
  20. package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
  21. package/bundle/components/ChipMultiselect.md +63 -73
  22. package/bundle/components/ChipSelect.md +385 -0
  23. package/bundle/components/ChipSelectTarget.md +388 -0
  24. package/bundle/components/ChipSuggest.md +10 -16
  25. package/bundle/components/ChipSuggestTarget.md +506 -0
  26. package/bundle/components/CollapsibleTree.md +2 -1
  27. package/bundle/components/CollapsibleTreeExtended.md +2 -1
  28. package/bundle/components/DateField.md +4 -2
  29. package/bundle/components/DatePickerExtended.md +16 -14
  30. package/bundle/components/DateRange.md +17 -10
  31. package/bundle/components/Dropdown.md +36 -63
  32. package/bundle/components/EmptyView.md +9 -4
  33. package/bundle/components/ExpandAnimation.md +9 -5
  34. package/bundle/components/FormField.md +1 -1
  35. package/bundle/components/ImageGallery.md +19 -52
  36. package/bundle/components/ImageGalleryExtended.md +123 -139
  37. package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
  38. package/bundle/components/LoaderSmall.md +5 -4
  39. package/bundle/components/MonthYearRange.md +334 -0
  40. package/bundle/components/MultiselectField.md +81 -143
  41. package/bundle/components/Notification.md +2 -1
  42. package/bundle/components/NotificationGrouped.md +174 -0
  43. package/bundle/components/NumberField.md +259 -116
  44. package/bundle/components/OrderedList.md +149 -112
  45. package/bundle/components/Overlay.md +3 -2
  46. package/bundle/components/PageIndicators.md +306 -0
  47. package/bundle/components/Pagination.md +81 -3
  48. package/bundle/components/PaginationExtended.md +160 -0
  49. package/bundle/components/Portal.md +5 -2
  50. package/bundle/components/Puller.md +375 -0
  51. package/bundle/components/SMSField.md +277 -102
  52. package/bundle/components/SelectExtendedField.md +11 -7
  53. package/bundle/components/SelectField.md +7 -4
  54. package/bundle/components/SmartSpot.md +259 -0
  55. package/bundle/components/Step.md +2 -1
  56. package/bundle/components/Stepper.md +7 -7
  57. package/bundle/components/Suggest.md +7 -6
  58. package/bundle/components/SuggestField.md +8 -6
  59. package/bundle/components/TableBasic.md +12 -14
  60. package/bundle/components/Tooltip.md +9 -8
  61. package/bundle/guides/commits.md +4 -1
  62. package/bundle/guides/context.md +5 -5
  63. package/bundle/guides/stories.md +29 -21
  64. package/bundle/manifest.json +145 -13
  65. package/bundle/release-notes/1.50.0.mdx +4 -0
  66. package/bundle/release-notes/1.51.0.mdx +57 -0
  67. package/bundle/release-notes/1.52.0.mdx +55 -0
  68. package/bundle/release-notes/1.53.0.mdx +60 -0
  69. package/bundle/release-notes/1.54.0.mdx +45 -0
  70. package/bundle/release-notes/1.55.0.mdx +7 -0
  71. package/bundle/tokens.json +377 -222
  72. package/dist/index.js +39 -1
  73. package/dist/index.js.map +1 -1
  74. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: CheckboxTreeExtended
3
3
  category: Inputs
4
- related: [CheckboxTree, CollapsibleTreeExtended, Checkbox]
4
+ related: [CheckboxTreeExtendedCheckbox, CheckboxTreeExtendedNode, CheckboxTree, CollapsibleTreeExtended, Checkbox]
5
5
  tokens:
6
6
  - Checkbox.BorderColor_Focus
7
7
  stories: stories/CheckboxTreeExtended/CheckboxTreeExtended.stories.tsx
@@ -49,9 +49,11 @@ version: "1.0"
49
49
  ### `CheckboxTreeExtended.Node`
50
50
 
51
51
  Наследует `ICollapsibleTreeExtendedNodeProps` без `children`, `renderBody`,
52
- `renderHeader` — заголовок и тело узла формирует сам компонент. Тип props
53
- экспортируется как `ICheckboxTreeExtendedNodeProps` — им типизируют
54
- собственную обёртку узла.
52
+ `renderHeader` — заголовок и тело узла формирует сам компонент. Компонент
53
+ `CheckboxTreeExtendedNode` и типы
54
+ `ICheckboxTreeExtendedNodeProps`, `ICheckboxTreeExtendedCheckboxProvideProps`
55
+ экспортируются через публичный barrel. Типом props типизируют собственную
56
+ обёртку узла. Полный контракт — в `CheckboxTreeExtendedNode-ai.md`.
55
57
 
56
58
  | Prop | Тип | По умолчанию | Описание |
57
59
  |---|---|---|---|
@@ -83,6 +85,8 @@ render-функции узла.
83
85
 
84
86
  `size`, переданный явно, перекрывает размер из контекста — так сделано намеренно
85
87
  (spread props идёт после), но в дереве это не нужно и ломает единый ритм.
88
+ Подробный контракт фокуса, `labelAttributes` и наследуемых props описан в
89
+ [CheckboxTreeExtendedCheckbox-ai.md](./CheckboxTreeExtendedCheckbox-ai.md).
86
90
 
87
91
  ---
88
92
 
@@ -117,7 +121,7 @@ Checkbox.BorderColor_Focus
117
121
  - **`ref` указывает на корневой `<ul role="tree">`.** Компонент обёрнут
118
122
  `forwardRef`, ссылка уходит в `CollapsibleTreeExtended` и дальше в `TreeView`.
119
123
  У `.Node` и `.Checkbox` своего `forwardRef` по-прежнему нет: `.Node` —
120
- class-компонент, `.Checkbox` управляет фокусом сам.
124
+ функциональный компонент без внешнего ref, `.Checkbox` управляет фокусом сам.
121
125
  - **Публичный API** — имена `ICheckboxTreeExtendedProps`,
122
126
  `ICheckboxTreeExtendedFC` (и deprecated-алиас `ICheckboxTreeExtendedSFC`),
123
127
  `ICheckboxTreeExtendedCheckboxProvideProps`,
@@ -156,8 +160,9 @@ Checkbox.BorderColor_Focus
156
160
 
157
161
  ## Accessibility
158
162
 
159
- - Семантику и клавиатурную навигацию (стрелки, Home/End, перемещение по узлам)
160
- обеспечивает `TreeView`. Компонент добавляет к ней только передачу фокуса.
163
+ - Семантику и перемещение по узлам клавишами `ArrowDown` / `ArrowUp`
164
+ обеспечивает `TreeView`. `Home` / `End` не реализованы. Компонент добавляет
165
+ к навигации только передачу фокуса.
161
166
  - **Фокус при навигации с клавиатуры.** Узел сообщает в render-функцию
162
167
  `active`; `CheckboxTreeExtended.Checkbox` по этому флагу вызывает
163
168
  `focus()` на `<input>`. Правило ровно одно: фокус берётся, когда `active`
@@ -189,6 +194,12 @@ Checkbox.BorderColor_Focus
189
194
 
190
195
  ## Связанные компоненты
191
196
 
197
+ - `CheckboxTreeExtendedCheckbox` — публичная часть `CheckboxTreeExtended.Checkbox`:
198
+ связывает базовый чекбокс с размером дерева и фокусом активного узла.
199
+ Подробности — в `CheckboxTreeExtendedCheckbox-ai.md`.
200
+ - `CheckboxTreeExtendedNode` (`CheckboxTreeExtended.Node`) — узел дерева с
201
+ render-функцией чекбокса, вложенностью и передачей активности из `TreeView`.
202
+ Подробный контракт — в `CheckboxTreeExtendedNode-ai.md`.
192
203
  - `CheckboxTree` (`src/components/CheckboxTree`) — обёртка верхнего уровня:
193
204
  принимает массив `ICheckboxTreeCheckboxData` и сама пересчитывает `checked` /
194
205
  `bulk` вверх и вниз по дереву. Если своя структура данных не нужна — бери её.
@@ -212,6 +223,7 @@ Checkbox.BorderColor_Focus
212
223
  | `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `size` |
213
224
  | `Default` | `DefaultExample.tsx` | Дерево из двух веток с вложенным узлом, состояние чекбоксов в `useState` потребителя |
214
225
  | `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG рядом |
226
+ | `VisualTests` | `VisualTests.tsx` | Состояния `.Checkbox` в дереве: checked, bulk, disabled и hover на SM / MD / LG, клавиатурный фокус на SM; скрыта из docs и сайдбара |
215
227
 
216
228
  ---
217
229
 
@@ -224,6 +236,8 @@ Checkbox.BorderColor_Focus
224
236
  | 2026-08-17 | Убран мёртвый селектор `.checkboxTreeCheckbox` из мобильной медиа-выборки (рабочие `> span` и `> svg` оставлены), `.caretIconWrapper:focus` заменён на `:focus-visible` (правки по ревью PR #535) |
225
237
  | 2026-08-17 | `ICheckboxTreeExtendedNodeProps` экспортируется — тип уходит в barrel и становится частью публичного API (решение владельца по ревью PR #535) |
226
238
  | 2026-09-17 | TRI-22: `CheckboxTreeExtended` обёрнут `forwardRef` — `ref` отдаёт корневой `<ul>`; тип переименован в `ICheckboxTreeExtendedFC`, старое имя оставлено deprecated-алиасом |
239
+ | 2026-10-21 | Добавлена ссылка на отдельную документацию `CheckboxTreeExtended.Checkbox` с контрактом фокуса и наследуемых props; добавлена скрытая `VisualTests` с состояниями `.Checkbox` в дереве. |
240
+ | 2026-10-21 | Добавлена ссылка на документацию CheckboxTreeExtendedNode, уточнены его экспорты, отсутствие внешнего ref и поддерживаемые клавиши навигации |
227
241
 
228
242
  ---
229
243
 
@@ -0,0 +1,591 @@
1
+ ---
2
+ component: CheckboxTreeExtendedCheckbox
3
+ category: Inputs
4
+ related: [CheckboxTreeExtended, Checkbox]
5
+ tokens:
6
+ - Checkbox.Background_Default
7
+ - Checkbox.BorderColor_Default
8
+ - Checkbox.Checkmark_Fill_Default
9
+ - Checkbox.Background_Hover
10
+ - Checkbox.BorderColor_Hover
11
+ - Checkbox.Background_Checked_Default
12
+ - Checkbox.BorderColor_Checked_Default
13
+ - Checkbox.Background_Disabled
14
+ - Checkbox.BorderColor_Disabled
15
+ - Checkbox.Background_Checked_Disabled
16
+ - Checkbox.BorderColor_Checked_Disabled
17
+ - Checkbox.Checkmark_Fill_Disabled
18
+ - Checkbox.BorderColor_Focus
19
+ - Checkbox.Background_Checked_Hover
20
+ stories: stories/CheckboxTreeExtended/CheckboxTreeExtended.stories.tsx
21
+ version: "1.0"
22
+ ---
23
+
24
+ # CheckboxTreeExtendedCheckbox
25
+
26
+ ## Назначение
27
+
28
+ Чекбокс узла декларативного `CheckboxTreeExtended`. Оборачивает базовый `Checkbox`,
29
+ добавляет классы дерева, получает размер из контекста и переводит фокус на input
30
+ активного узла. Состоянием выбора и связью родитель ↔ потомки управляет потребитель.
31
+
32
+ Публичное обращение — `CheckboxTreeExtended.Checkbox`. Именованного экспорта
33
+ `CheckboxTreeExtendedCheckbox` из пакета нет; собственный интерфейс props также
34
+ не экспортируется из barrel. Для типизации обёртки можно использовать
35
+ `React.ComponentProps<typeof CheckboxTreeExtended.Checkbox>`.
36
+
37
+ ```tsx
38
+ import React from "react";
39
+ import { CheckboxTreeExtended } from "@sberbusiness/triplex-next";
40
+
41
+ export const TreeWithCheckbox = () => (
42
+ <CheckboxTreeExtended>
43
+ <CheckboxTreeExtended.Node
44
+ id="account"
45
+ checkbox={(props) => (
46
+ <CheckboxTreeExtended.Checkbox {...props} defaultChecked>
47
+ Расчётный счёт
48
+ </CheckboxTreeExtended.Checkbox>
49
+ )}
50
+ />
51
+ </CheckboxTreeExtended>
52
+ );
53
+ ```
54
+
55
+ Используй внутри render-функции `checkbox` узла, разворачивая её аргумент в
56
+ `CheckboxTreeExtended.Checkbox`. Для отдельного флажка вне дерева используй
57
+ `Checkbox`; для готового каскадного выбора по массиву данных — `CheckboxTree`.
58
+
59
+ ---
60
+
61
+ ## Варианты и props
62
+
63
+ Обязательных props нет. Наследует `ICheckboxProps`; сведения ниже описывают
64
+ особенности обёртки, полный набор нативных атрибутов доступен в исходном типе.
65
+
66
+ ### Связь с узлом и размер
67
+
68
+ | Prop | Назначение и ограничения |
69
+ |---|---|
70
+ | `active` | Служебный признак активного узла из render-функции `checkbox`. При монтировании и изменении этого prop запускает проверку фокуса; сам в DOM не передаётся |
71
+ | `opened` | Принимается из той же render-функции, исключается из props input и ни на отрисовку, ни на фокус не влияет |
72
+ | `size` | Обычно берётся из `CheckboxTreeExtendedContext`: по умолчанию `MD`, в дереве — размер родителя. Явный prop перекрывает контекст, включая мобильный `MD`, потому что spread идёт после `size={size}`. Для единого ритма дерева оставляй его родителю |
73
+
74
+ ### Выбор и доступность
75
+
76
+ | Prop | Назначение и ограничения |
77
+ |---|---|
78
+ | `checked`, `defaultChecked`, `onChange` | Нативное controlled/uncontrolled состояние input и событие изменения. Компонент не пересчитывает состояние соседних узлов |
79
+ | `bulk` | Меняет галочку на иконку частичного выбора. Для её видимости нужен `checked`; не задаёт нативный `indeterminate` или `aria-checked="mixed"` |
80
+ | `disabled` | Отключает нативный input и задаёт оформление disabled; браузер не переводит на него фокус |
81
+ | `children` | Видимая подпись и доступное имя чекбокса через вложенный `<label>` |
82
+
83
+ ### Атрибуты и события
84
+
85
+ | Prop | Назначение и ограничения |
86
+ |---|---|
87
+ | `className` | Объединяется с классами базового Checkbox и дерева на корневом `<label>`, не попадает на input |
88
+ | `labelAttributes` | HTML-атрибуты корневого `<label>`. Его `className` сохраняется вместе с внутренними классами. `onFocus` вызывается обёрткой после проверки режима статичности дерева |
89
+ | `name`, `value`, `id`, `aria-*`, `data-*`, `onFocus` и остальные нативные props | Передаются базовому Checkbox на `<input type="checkbox">`; `onFocus` input отличается от `labelAttributes.onFocus` и всплывает к нему |
90
+
91
+ В текущем статичном режиме обёртка сама не останавливает всплытие focus от label:
92
+ сначала выполняется `labelAttributes.onFocus`, затем событие достигает ноды
93
+ дерева, если потребитель не остановил его. При `isStaticCheckboxTreeExtended = false`
94
+ обёртка сначала вызывает `event.stopPropagation()`, затем пользовательский
95
+ `labelAttributes.onFocus`. Не меняй этот порядок при объединении обработчиков.
96
+
97
+ ---
98
+
99
+ ## Дизайн-токены
100
+
101
+ Собственных токенов обёртка не вводит. Цвета и состояния приходят из базового
102
+ `Checkbox.module.less`; дерево добавляет отступы label и мобильную область нажатия.
103
+ Токен фокуса стрелки в общем LESS-файле семейства относится к
104
+ `CheckboxTreeExtendedArrow`, а не к отдельному элементу этой обёртки.
105
+
106
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см.
107
+ `src/components/ThemeProvider/ThemeProvider-ai.md`, раздел «Как переопределять
108
+ токены». Значения по умолчанию — `src/components/DesignTokens/components/Checkbox.ts`.
109
+
110
+ ```text
111
+ Checkbox.Background_Default
112
+ Checkbox.BorderColor_Default
113
+ Checkbox.Checkmark_Fill_Default
114
+ Checkbox.Background_Hover
115
+ Checkbox.BorderColor_Hover
116
+ Checkbox.Background_Checked_Default
117
+ Checkbox.BorderColor_Checked_Default
118
+ Checkbox.Background_Disabled
119
+ Checkbox.BorderColor_Disabled
120
+ Checkbox.Background_Checked_Disabled
121
+ Checkbox.BorderColor_Checked_Disabled
122
+ Checkbox.Checkmark_Fill_Disabled
123
+ Checkbox.BorderColor_Focus
124
+ Checkbox.Background_Checked_Hover
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Инварианты
130
+
131
+ - Публичный путь — `CheckboxTreeExtended.Checkbox`, `displayName` —
132
+ `"CheckboxTreeExtendedCheckbox"`; существующие exports семейства сохраняются.
133
+ - Обёртка остаётся `React.FC` без внешнего `ref`/`forwardRef`: это текущий контракт.
134
+ Внутренний `useRef<HTMLInputElement>` передаётся в `forwardRef` базового Checkbox
135
+ и нужен для управления фокусом. Не подменять его публичным ref без отдельной задачи.
136
+ - Корневой DOM — `<label>` базового Checkbox с вложенным нативным input.
137
+ `className` и `labelAttributes.className` сохраняются на label вместе с классом
138
+ `checkboxTreeCheckboxLabel` и классами базового Checkbox; остальные нативные props — на input.
139
+ - `active` и `opened` не попадают в DOM. `opened` намеренно не используется;
140
+ при статичном дереве Node передаёт `opened: true`.
141
+ - Эффект фокуса зависит только от `active`. Он не различает mount и update:
142
+ если `active` истинно и `document.activeElement` не содержит текущий input,
143
+ вызывается `input.focus()`. При неизменном `active` новый рендер не забирает
144
+ фокус повторно. Не заменять проверку `contains` на сравнение элементов или
145
+ проверку предыдущего `active`: это изменит поведение.
146
+ - Размер из контекста задаётся перед spread нативных props. Явный `size`
147
+ должен сохранять возможность его перекрыть.
148
+ - Ветку `!isStaticCheckboxTreeExtended` в focus-обработчике label не удалять:
149
+ она оставлена для будущего сворачиваемого режима семейства.
150
+ - В заголовке узла нижний margin label обнуляется, а на мобильной ширине
151
+ добавляется padding сверху/снизу по 16 px. Вне `CheckboxTreeExtended.Node`
152
+ эта раскладка не применяется.
153
+
154
+ ---
155
+
156
+ ## Accessibility
157
+
158
+ - Семантику формы и переключение по Space обеспечивает нативный
159
+ `<input type="checkbox">`. `children` дают видимую подпись; без неё передай
160
+ доступное имя, например `aria-label`. Язык выбирает потребитель.
161
+ - Семантика `tree`/`treeitem` и навигация между узлами принадлежат
162
+ `CheckboxTreeExtended` и нижележащему `TreeView`. Обёртка реагирует только на
163
+ переданный `active`, направляя фокус на input.
164
+ - При первом рендере активный чекбокс обычно не получает фокус: активным
165
+ элементом является `<body>`, который содержит input. Если фокус стоит на
166
+ постороннем элементе, активный чекбокс заберёт его уже на mount. Когда
167
+ `active` меняется на `true` после клавиатурной навигации, действует та же проверка.
168
+ - В статичном режиме focus от label всплывает к Node и делает активным
169
+ соответствующий узел. Пользовательский `labelAttributes.onFocus` может
170
+ остановить всплытие; `preventDefault` сам по себе этот контракт не заменяет.
171
+ - Фокусное оформление использует `:focus-visible` базового Checkbox; hover
172
+ включается только на устройствах с `hover: hover`. `bulk` описывает иконку,
173
+ а доступное mixed-состояние автоматически не создаётся.
174
+ - Ограничение начальной разметки вложенных узлов (`li` внутри `li` до регистрации
175
+ группы) описано в `CheckboxTreeExtended-ai.md` и исправляется на уровне `TreeView`.
176
+
177
+ ---
178
+
179
+ ## Связанные компоненты
180
+
181
+ - `CheckboxTreeExtended` — родитель; владеет контекстом размера и предоставляет
182
+ `.Node`, чья render-функция передаёт `active` / `opened` в `.Checkbox`.
183
+ - `Checkbox` — непосредственно рендерится обёрткой; её props наследуют
184
+ `ICheckboxProps`. Отвечает за input, label, состояния и визуальные токены.
185
+
186
+ ---
187
+
188
+ ## Stories
189
+
190
+ Основные копируемые примеры переиспользуются из историй родителя.
191
+
192
+ Основные истории: `stories/CheckboxTreeExtended/CheckboxTreeExtended.stories.tsx`
193
+ Файлы примеров: `stories/CheckboxTreeExtended/examples/`
194
+
195
+ | Story | Example file | Что демонстрирует |
196
+ |---|---|---|
197
+ | `Playground` | `PlaygroundExample.tsx` | Размер родительского дерева |
198
+ | `Default` | `DefaultExample.tsx` | Публичное обращение `.Checkbox`, передача provide-props и controlled-состояние потребителя |
199
+ | `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG с подписями и единым размером дерева |
200
+ | `VisualTests` | `VisualTests.tsx` | SM / MD / LG в checked, checked bulk и disabled, реальный клавиатурный фокус в дереве (только SM — фокус в документе один) и hover выбранного input; мобильная раскладка |
201
+
202
+ Композиционный пример: `stories/Chips/ChipMultiselect.stories.tsx`
203
+ Файлы примеров: `stories/Chips/examples/ChipMultiselect/`
204
+
205
+ | Story | Example file | Что демонстрирует |
206
+ |---|---|---|
207
+ | `WithCheckboxTree` | `WithCheckboxTreeExample.tsx` | Дерево внутри ChipMultiselect, пересчёт checked/bulk и фильтрация на стороне потребителя; исключён из скриншотов |
208
+
209
+ `Playground` и `VisualTests` исключаются из MCP bundle. Focus/hover-состояния
210
+ проверяются после play и перемонтирования истории на двух viewport: xs / xl.
211
+
212
+ ---
213
+
214
+ ## История изменений
215
+
216
+ | Дата | Изменение |
217
+ |---|---|
218
+ | 2026-10-21 | Создан документ: описаны публичный compound-путь, наследуемые props, размер из контекста, точный эффект фокуса и порядок label onFocus. Добавлены проверки недостающих контрактов и визуальных состояний без изменения production-кода. |
219
+
220
+ ---
221
+
222
+ ## Примеры использования
223
+
224
+ ### Default
225
+
226
+ `stories/CheckboxTreeExtended/examples/DefaultExample.tsx`
227
+
228
+ ```tsx
229
+ import React from "react";
230
+ import { CheckboxTreeExtended, EComponentSize } from "@sberbusiness/triplex-next";
231
+
232
+ type CheckboxNode = { id: string; label: string; checked: boolean; bulk?: boolean; children?: CheckboxNode[] };
233
+
234
+ const INITIAL: CheckboxNode[] = [
235
+ { id: "1", label: "Группа 1", checked: false, children: [{ id: "1-1", label: "Значение 1-1", checked: false }] },
236
+ { id: "2", label: "Группа 2", checked: false },
237
+ ];
238
+
239
+ export const DefaultExample = ({ size = EComponentSize.MD }: { size?: EComponentSize }) => {
240
+ const [checkboxes, setCheckboxes] = React.useState<CheckboxNode[]>(INITIAL);
241
+
242
+ const onChange = (target: CheckboxNode) => (event: React.ChangeEvent<HTMLInputElement>) => {
243
+ target.checked = event.target.checked;
244
+ setCheckboxes([...checkboxes]);
245
+ };
246
+
247
+ const renderNode = (node: CheckboxNode) => (
248
+ <CheckboxTreeExtended.Node
249
+ id={node.id}
250
+ key={node.id}
251
+ checkbox={(props) => (
252
+ <CheckboxTreeExtended.Checkbox
253
+ {...props}
254
+ onChange={onChange(node)}
255
+ bulk={node.bulk}
256
+ checked={node.checked}
257
+ >
258
+ {node.label}
259
+ </CheckboxTreeExtended.Checkbox>
260
+ )}
261
+ >
262
+ {node.children?.map((child) => renderNode(child))}
263
+ </CheckboxTreeExtended.Node>
264
+ );
265
+
266
+ return <CheckboxTreeExtended size={size}>{checkboxes.map((node) => renderNode(node))}</CheckboxTreeExtended>;
267
+ };
268
+ ```
269
+
270
+ ### Sizes
271
+
272
+ `stories/CheckboxTreeExtended/examples/SizesExample.tsx`
273
+
274
+ ```tsx
275
+ import React from "react";
276
+ import { CheckboxTreeExtended, Col, EComponentSize, Row } from "@sberbusiness/triplex-next";
277
+
278
+ type CheckboxNode = { id: string; label: string; checked: boolean; bulk?: boolean; children?: CheckboxNode[] };
279
+
280
+ const INITIAL: CheckboxNode[] = [
281
+ { id: "1", label: "Группа 1", checked: false, children: [{ id: "1-1", label: "Значение 1-1", checked: false }] },
282
+ { id: "2", label: "Группа 2", checked: false },
283
+ ];
284
+
285
+ const TreeBySize = ({ size = EComponentSize.MD }: { size?: EComponentSize }) => {
286
+ const [checkboxes, setCheckboxes] = React.useState<CheckboxNode[]>(INITIAL);
287
+
288
+ const onChange = (target: CheckboxNode) => (event: React.ChangeEvent<HTMLInputElement>) => {
289
+ target.checked = event.target.checked;
290
+ setCheckboxes([...checkboxes]);
291
+ };
292
+
293
+ const renderNode = (node: CheckboxNode) => (
294
+ <CheckboxTreeExtended.Node
295
+ id={node.id}
296
+ key={node.id}
297
+ checkbox={(props) => (
298
+ <CheckboxTreeExtended.Checkbox
299
+ {...props}
300
+ onChange={onChange(node)}
301
+ bulk={node.bulk}
302
+ checked={node.checked}
303
+ >
304
+ {node.label}
305
+ </CheckboxTreeExtended.Checkbox>
306
+ )}
307
+ >
308
+ {node.children?.map((child) => renderNode(child))}
309
+ </CheckboxTreeExtended.Node>
310
+ );
311
+
312
+ return <CheckboxTreeExtended size={size}>{checkboxes.map((node) => renderNode(node))}</CheckboxTreeExtended>;
313
+ };
314
+
315
+ export const SizesExample = () => (
316
+ <Row>
317
+ <Col size={4}>
318
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>SM</div>
319
+ <TreeBySize size={EComponentSize.SM} />
320
+ </Col>
321
+ <Col size={4}>
322
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>MD</div>
323
+ <TreeBySize size={EComponentSize.MD} />
324
+ </Col>
325
+ <Col size={4}>
326
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>LG</div>
327
+ <TreeBySize size={EComponentSize.LG} />
328
+ </Col>
329
+ </Row>
330
+ );
331
+ ```
332
+
333
+ ### WithCheckboxTree
334
+
335
+ `stories/Chips/examples/ChipMultiselect/WithCheckboxTreeExample.tsx`
336
+
337
+ ```tsx
338
+ import React, { useState } from "react";
339
+ import {
340
+ MultiselectField,
341
+ PullerHeader,
342
+ PullerInput,
343
+ PullerClose,
344
+ PullerBody,
345
+ PullerFooter,
346
+ Button,
347
+ EButtonTheme,
348
+ EComponentSize,
349
+ FormField,
350
+ FormFieldLabel,
351
+ FormFieldInput,
352
+ FormFieldPostfix,
353
+ FormFieldClear,
354
+ ChipMultiselect,
355
+ ETextSize,
356
+ Text,
357
+ CheckboxTreeExtended,
358
+ checkChildrenCheckboxes,
359
+ traverseCheckboxes,
360
+ checkParentCheckboxes,
361
+ ICheckboxTreeCheckboxData,
362
+ } from "@sberbusiness/triplex-next";
363
+
364
+ export const WithCheckboxTreeExample = () => {
365
+ const checkboxesInitial = [
366
+ {
367
+ bulk: false,
368
+ checked: false,
369
+ children: [
370
+ {
371
+ bulk: false,
372
+ checked: false,
373
+ children: [
374
+ {
375
+ checked: false,
376
+ id: "multiselect-option-1-1",
377
+ label: "Значение 1-1",
378
+ },
379
+ {
380
+ checked: false,
381
+ id: "multiselect-option-1-2",
382
+ label: "Значение 1-2",
383
+ },
384
+ {
385
+ checked: false,
386
+ id: "multiselect-option-1-3",
387
+ label: "Значение 1-3",
388
+ },
389
+ ],
390
+ id: "multiselect-option-1",
391
+ label: "Группа 1",
392
+ },
393
+ {
394
+ bulk: false,
395
+ checked: false,
396
+ children: [
397
+ {
398
+ checked: false,
399
+ id: "multiselect-option-2-1",
400
+ label: "Значение 2-1",
401
+ },
402
+ {
403
+ checked: false,
404
+ id: "multiselect-option-2-2",
405
+ label: "Значение 2-2",
406
+ },
407
+ ],
408
+ id: "multiselect-option-2",
409
+ label: "Группа 2",
410
+ },
411
+ {
412
+ checked: false,
413
+ id: "multiselect-option-3",
414
+ label: "Значение 3",
415
+ },
416
+ ],
417
+ id: "multiselect-option-0",
418
+ label: "Все",
419
+ },
420
+ ];
421
+
422
+ const [checkboxes, setCheckboxes] = useState(() => structuredClone(checkboxesInitial));
423
+ const [filter, setFilter] = useState("");
424
+ const [filteredCheckboxesId, setFilteredCheckboxesId] = useState<string[]>([]);
425
+
426
+ const handleChange = (checkbox: ICheckboxTreeCheckboxData) => (event: React.ChangeEvent<HTMLInputElement>) => {
427
+ checkbox.checked = checkbox.bulk ? true : event.target.checked;
428
+ checkChildrenCheckboxes(checkbox);
429
+ traverseCheckboxes(checkboxes, checkParentCheckboxes);
430
+ setCheckboxes([...checkboxes]);
431
+ };
432
+
433
+ const renderCheckboxNode = (checkbox: ICheckboxTreeCheckboxData) => {
434
+ if (filter && !filteredCheckboxesId.includes(checkbox.id)) return null;
435
+ return (
436
+ <CheckboxTreeExtended.Node
437
+ key={checkbox.id}
438
+ id={checkbox.id}
439
+ checkbox={(props) => (
440
+ <CheckboxTreeExtended.Checkbox
441
+ {...props}
442
+ bulk={checkbox.bulk}
443
+ checked={checkbox.checked}
444
+ onChange={handleChange(checkbox)}
445
+ >
446
+ {checkbox.label}
447
+ </CheckboxTreeExtended.Checkbox>
448
+ )}
449
+ >
450
+ {checkbox.children && checkbox.children.map((child) => renderCheckboxNode(child))}
451
+ </CheckboxTreeExtended.Node>
452
+ );
453
+ };
454
+
455
+ const renderDropdownContent = () => {
456
+ const renderCheckboxes = !filter || (filteredCheckboxesId.length && filter);
457
+ return renderCheckboxes ? (
458
+ <CheckboxTreeExtended>{checkboxes.map((checkbox) => renderCheckboxNode(checkbox))}</CheckboxTreeExtended>
459
+ ) : (
460
+ <div className="not-found md">
461
+ <Text size={ETextSize.B3}>Nothing was found</Text>
462
+ </div>
463
+ );
464
+ };
465
+
466
+ const unselectAll = () => {
467
+ const nextCheckboxes = [...checkboxes];
468
+ traverseCheckboxes(nextCheckboxes, (checkbox) => {
469
+ checkbox.checked = false;
470
+ checkbox.bulk = false;
471
+ });
472
+ setCheckboxes(nextCheckboxes);
473
+ };
474
+
475
+ const handleClickClearFilter = () => {
476
+ setFilter("");
477
+ setFilteredCheckboxesId([]);
478
+ unselectAll();
479
+ };
480
+
481
+ const handleClearInput = () => {
482
+ setFilter("");
483
+ };
484
+
485
+ const handleFilterChange = (event: React.ChangeEvent<HTMLInputElement>) => {
486
+ const { value } = event.target;
487
+ const filteredCheckboxes = [...checkboxes];
488
+ const filteredCheckboxesId = new Set<string>();
489
+
490
+ const addMatchedSubtree = (node: ICheckboxTreeCheckboxData) => {
491
+ filteredCheckboxesId.add(node.id);
492
+ node.children?.forEach(addMatchedSubtree);
493
+ };
494
+
495
+ const setFilteredValue = (checkbox: ICheckboxTreeCheckboxData) => {
496
+ if (
497
+ checkbox.label &&
498
+ typeof checkbox.label === "string" &&
499
+ checkbox.label.toLowerCase().includes(value.toLowerCase())
500
+ ) {
501
+ addMatchedSubtree(checkbox);
502
+ } else if (checkbox.children) {
503
+ const intersection = checkbox.children
504
+ .map((item: ICheckboxTreeCheckboxData) => item.id)
505
+ .filter((id) => Array.from(filteredCheckboxesId).includes(id));
506
+
507
+ if (intersection.length) filteredCheckboxesId.add(checkbox.id);
508
+ }
509
+ };
510
+
511
+ traverseCheckboxes(filteredCheckboxes, setFilteredValue);
512
+
513
+ setFilter(value);
514
+ setFilteredCheckboxesId(Array.from(filteredCheckboxesId));
515
+ };
516
+
517
+ const renderDropdown = ({
518
+ opened,
519
+ setOpened,
520
+ targetRef,
521
+ dropdownRef,
522
+ }: {
523
+ opened: boolean;
524
+ setOpened: (opened: boolean) => void;
525
+ targetRef: React.RefObject<HTMLDivElement>;
526
+ dropdownRef: React.RefObject<HTMLDivElement>;
527
+ }) => (
528
+ <MultiselectField.Dropdown
529
+ opened={opened}
530
+ setOpened={setOpened}
531
+ targetRef={targetRef}
532
+ ref={dropdownRef}
533
+ focusTrapProps={{
534
+ focusTrapOptions: { initialFocus: false },
535
+ }}
536
+ pullerProps={{
537
+ children: (
538
+ <>
539
+ <PullerHeader controlButtons={<PullerClose onClick={() => setOpened(false)} />}>
540
+ <PullerInput placeholder="Type to proceed" value={filter} onChange={handleFilterChange} />
541
+ </PullerHeader>
542
+ <PullerBody>{renderDropdownContent()}</PullerBody>
543
+ <PullerFooter>
544
+ <Button
545
+ theme={EButtonTheme.GENERAL}
546
+ size={EComponentSize.SM}
547
+ onClick={() => setOpened(false)}
548
+ >
549
+ Button text
550
+ </Button>
551
+ <Button theme={EButtonTheme.LINK} size={EComponentSize.SM} onClick={handleClickClearFilter}>
552
+ Button link text
553
+ </Button>
554
+ </PullerFooter>
555
+ </>
556
+ ),
557
+ }}
558
+ >
559
+ <MultiselectField.Dropdown.Header>
560
+ <FormField size={EComponentSize.SM}>
561
+ <FormFieldLabel>Type to proceed</FormFieldLabel>
562
+ <FormFieldInput value={filter} onChange={handleFilterChange} />
563
+ <FormFieldPostfix>
564
+ <FormFieldClear onClick={handleClearInput} />
565
+ </FormFieldPostfix>
566
+ </FormField>
567
+ </MultiselectField.Dropdown.Header>
568
+ <MultiselectField.Dropdown.Content>{renderDropdownContent()}</MultiselectField.Dropdown.Content>
569
+ <MultiselectField.Dropdown.Footer>
570
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM} onClick={() => setOpened(false)}>
571
+ Button text
572
+ </Button>
573
+ <Button theme={EButtonTheme.LINK} size={EComponentSize.SM} onClick={handleClickClearFilter}>
574
+ Button link text
575
+ </Button>
576
+ </MultiselectField.Dropdown.Footer>
577
+ </MultiselectField.Dropdown>
578
+ );
579
+
580
+ return (
581
+ <ChipMultiselect
582
+ clearSelected={unselectAll}
583
+ selected={Boolean(checkboxes.filter((checkbox) => checkbox.checked).length)}
584
+ label="Multiselect label"
585
+ >
586
+ {(dropdownProps) => renderDropdown(dropdownProps)}
587
+ </ChipMultiselect>
588
+ );
589
+ };
590
+ ```
591
+