@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.
- package/README.md +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- 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` — заголовок и тело узла формирует сам компонент.
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
- Семантику и
|
|
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
|
+
|