@sberbusiness/triplex-next-mcp-server 1.45.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 (60) 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/CardAction.md +615 -0
  5. package/bundle/components/CardStatic.md +402 -0
  6. package/bundle/components/CheckboxTree.md +157 -215
  7. package/bundle/components/CheckboxTreeExtended.md +7 -7
  8. package/bundle/components/Chip.md +7 -6
  9. package/bundle/components/ChipGroup.md +1 -1
  10. package/bundle/components/ChipMonthYearPicker.md +344 -0
  11. package/bundle/components/ChipMultiselect.md +1251 -0
  12. package/bundle/components/ChipOptions.md +329 -0
  13. package/bundle/components/ChipSort.md +333 -0
  14. package/bundle/components/ChipSuggest.md +588 -0
  15. package/bundle/components/CollapsibleTree.md +8 -4
  16. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  17. package/bundle/components/Confirm.md +346 -281
  18. package/bundle/components/DateField.md +222 -303
  19. package/bundle/components/DatePickerExtended.md +3 -1
  20. package/bundle/components/DateRange.md +351 -210
  21. package/bundle/components/FooterPage.md +1 -0
  22. package/bundle/components/FormField.md +14 -5
  23. package/bundle/components/FormGroup.md +3 -2
  24. package/bundle/components/HelpBox.md +7 -1
  25. package/bundle/components/IslandAccordion.md +443 -152
  26. package/bundle/components/IslandWidget.md +622 -157
  27. package/bundle/components/LightBox.md +8 -0
  28. package/bundle/components/ListMaster.md +2 -2
  29. package/bundle/components/ListTableItem.md +6 -6
  30. package/bundle/components/MaskedField.md +2 -1
  31. package/bundle/components/MonthYearField.md +18 -5
  32. package/bundle/components/Slider.md +389 -97
  33. package/bundle/components/SliderRange.md +486 -105
  34. package/bundle/components/StatusTracker.md +386 -94
  35. package/bundle/components/Stepper.md +355 -102
  36. package/bundle/components/StepperExtended.md +633 -0
  37. package/bundle/components/SuggestField.md +1049 -96
  38. package/bundle/components/SwipeableArea.md +7 -7
  39. package/bundle/components/Tabs.md +265 -92
  40. package/bundle/components/TabsExtended.md +18 -18
  41. package/bundle/components/TabsLine.md +301 -127
  42. package/bundle/components/TagColor.md +187 -66
  43. package/bundle/components/TagGroup.md +184 -48
  44. package/bundle/components/TextareaField.md +278 -70
  45. package/bundle/components/TopOverlay.md +17 -1
  46. package/bundle/components/TreeView.md +16 -7
  47. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  48. package/bundle/components/UploadZone.md +318 -0
  49. package/bundle/guides/codestyle.md +16 -1
  50. package/bundle/guides/commits.md +47 -1
  51. package/bundle/guides/tests.md +23 -2
  52. package/bundle/manifest.json +92 -24
  53. package/bundle/release-notes/1.46.0.mdx +59 -0
  54. package/bundle/release-notes/1.47.0.mdx +122 -0
  55. package/bundle/release-notes/1.48.0.mdx +68 -0
  56. package/bundle/release-notes/1.49.0.mdx +7 -0
  57. package/bundle/tokens.json +3 -3
  58. package/dist/index.js +220 -6
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  ---
2
2
  component: Confirm
3
- category: Overlays
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Island, LightBox, SideOverlay, Button, TriggerClickOnKeyDownEvent, Typography]
3
+ category: Confirm
4
+ related: [Island, Button, Title, Text, TriggerClickOnKeyDownEvent]
7
5
  tokens: []
8
- stories: ""
6
+ stories: stories/Confirm/Confirm.stories.tsx
9
7
  version: "1.0"
10
8
  ---
11
9
 
@@ -13,331 +11,398 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Модальное подтверждение (диалог), встраиваемое **внутрь** уже открытого
17
- `LightBox` или `SideOverlay`. Не содержит собственного бэкдропа/портала —
18
- это компонент-контент, который рендерится поверх родительского overlay'а
19
- и ограничивает свою максимальную ширину так, чтобы визуально помещаться
20
- в контейнер родителя.
14
+ Предупреждение о закрытии лайтбокса или его боковой панели: карточка `Island` типа `TYPE_1`
15
+ с ролью `dialog` и `aria-modal="true"`, внутри которой лежит текст (`Confirm.Content`), кнопки
16
+ действий (`Confirm.Controls`) и кнопка-крестик (`Confirm.Close`). Собственного состояния нет —
17
+ компонент ничего не открывает и не закрывает, обработчики кнопок задаёт потребитель.
21
18
 
22
- Внутри — `Island type=TYPE_1` с `role="dialog" aria-modal="true"`, секция
23
- `Island.Body` с контентом (Title, SubTitle, Controls) и абсолютно
24
- позиционированная кнопка закрытия (справа сверху).
19
+ Используй когда: действие пользователя приведёт к потере данных (закрытие формы, выход из
20
+ мастера) и его нужно подтвердить прямо поверх контента — как правило, внутри `TopOverlay`
21
+ (он же `LightBox.TopOverlay`), который и обеспечивает маску, выезд панели и ловушку фокуса.
25
22
 
26
- Используй когда: нужно подтвердить действие (закрыть/удалить/отменить)
27
- поверх уже открытого `LightBox` или `SideOverlay`.
28
- Не используй как самостоятельный модал (он не создаёт свой overlay/backdrop)
29
- — для автономных диалогов бери `ModalWindow` или используй `LightBox`
30
- c `Confirm` внутри.
31
-
32
- ---
33
-
34
- ## Файловая структура
35
-
36
- ```text
37
- src/components/Confirm/
38
- ├── Confirm.tsx # FC-обёртка над Island, compound FC (Close/Content/Controls)
39
- ├── enums.ts # EConfirmParentComponent
40
- ├── index.ts # Barrel: Confirm + enums
41
- ├── components/
42
- │ ├── ConfirmClose.tsx # FC, условное оборачивание в TriggerClickOnKeyDownEvent (Esc)
43
- │ ├── ConfirmCloseButton.tsx # forwardRef, Button с иконкой крестика
44
- │ ├── ConfirmContent.tsx # FC, compound с Title/SubTitle
45
- │ ├── ConfirmContentTitle.tsx # FC-обёртка Typography Title
46
- │ └── ConfirmContentSubTitle.tsx # FC-обёртка Typography Text
47
- └── styles/Confirm.module.less # Max-width по parentComponent, позиционирование кнопки закрытия
48
- ```
49
-
50
- Особенности:
51
- - **Root — FC, не forwardRef.** Ref-форвардинг не реализован (известный
52
- AI-ready debt, характерен для compound-обёрток). Ref, если нужен
53
- родителю, идёт через `...htmlDivAttributes` в `Island`.
54
- - **Compound через прямое присваивание:** `Confirm.Close = ConfirmClose;`
55
- `Confirm.Content = ConfirmContent;` `Confirm.Controls = ConfirmControls;`
56
- Тип `IConfirmFC extends React.FC<IConfirmProps>`.
57
- - **ConfirmContent — тоже compound** (Title/SubTitle) через тот же паттерн.
58
- - **Нет `__tests__` и нет `stories`** — документируется косвенно через
59
- потребителей `LightBox`/`SideOverlay`.
60
- - **Нет собственных дизайн-токенов** — все цвета/радиусы берутся из
61
- `Island` (type=TYPE_1); ширина — из LESS-переменных родительского
62
- overlay (`@sideOverlaySMWidth` etc.).
23
+ Не используй когда:
24
+ - Диалог нужен поверх обычной страницы, а не внутри лайтбокса — возьми `ModalWindow`: он сам
25
+ рендерится в портал, блокирует скролл и держит фокус. Confirm ничего из этого не делает.
26
+ - Нужен полноэкранный сценарий с навигацией — возьми `LightBox`.
27
+ - Нужно уведомление без выбора действия — возьми компоненты уведомлений (`AlertContext`,
28
+ `Notification`).
63
29
 
64
30
  ---
65
31
 
66
32
  ## Варианты и props
67
33
 
68
- ### Root: `IConfirmProps extends IIslandProps`
69
-
70
- | Prop | Тип | Default | Описание |
71
- |---|---|---|---|
72
- | `parentComponent` | `EConfirmParentComponent` | `LIGHTBOX` | Определяет max-width: в `LightBox` — как у контента LightBox, в `SideOverlay` — как у соответствующей ширины боковой панели. |
73
- | `children` | `ReactNode` | — | Обычно `Confirm.Close` + `Confirm.Content` + `Confirm.Controls`. |
74
- | `className` | `string` | — | Добавляется через `clsx` к root-классу `.confirm` и parent-класс map. |
75
- | `...IIslandProps` | — | — | Все пропсы `Island` (`id`, `onClick`, `data-tx` и др.). |
76
-
77
- `role="dialog"` и `aria-modal="true"` проставлены жёстко в реализации и
78
- не могут быть перебиты через `htmlDivAttributes` (spread идёт до них
79
- — **неверно**: в реализации spread идёт **после** `role` и `aria-modal`,
80
- значит пропс потребителя **перебьёт** дефолт). Если нужно — не
81
- перебивать, семантика диалога обязательна.
82
-
83
- ### `EConfirmParentComponent` (enum string)
84
-
85
- ```ts
86
- enum EConfirmParentComponent {
87
- LIGHTBOX = "lightBox",
88
- SIDE_OVERLAY_SM = "sideOverlaySM",
89
- SIDE_OVERLAY_MD = "sideOverlayMD",
90
- SIDE_OVERLAY_LG = "sideOverlayLG",
91
- }
92
- ```
34
+ ### Обязательные props
93
35
 
94
- Соответствие → LESS-классы:
95
- - `LIGHTBOX` → `.isInLightBox` → `calc(var(--lightBox-content-max-width) - @page-padding-desktop-x*2)`
96
- - `SIDE_OVERLAY_SM` → `.isInSideOverlaySM` → `calc(@sideOverlaySMWidth - @page-padding-desktop-x*2)`
97
- - `SIDE_OVERLAY_MD` → `.isInSideOverlayMD` → `calc(@sideOverlayMDWidth - @page-padding-desktop-x*2)`
98
- - `SIDE_OVERLAY_LG` → `.isInSideOverlayLG` → `calc(@sideOverlayLGWidth - @page-padding-desktop-x*2)`
36
+ Обязательных props нет — `<Confirm>` рендерится с шириной под контент лайтбокса.
99
37
 
100
- ### Compound: `Confirm.Close` — `IConfirmCloseProps extends IConfirmCloseButtonProps`
38
+ ### Опциональные props
101
39
 
102
- | Prop | Тип | Обязателен | Описание |
40
+ | Prop | Тип | По умолчанию | Описание |
103
41
  |---|---|---|---|
104
- | `clickByEsc` | `boolean` | Да | Если `true` — оборачивает кнопку в `TriggerClickOnKeyDownEvent` с `ESCAPE`, клик кнопки симулируется при нажатии Esc. |
105
- | `...IConfirmCloseButtonProps` | — | — | Все пропсы кнопки, кроме `theme/size/icon` (зафиксированы). |
106
-
107
- **`IConfirmCloseButtonProps extends Omit<TButtonProps, "theme" \| "size" \| "icon">`:**
108
-
109
- - `theme` зафиксирован в `EButtonTheme.SECONDARY`
110
- - `size` зафиксирован в `EComponentSize.MD`
111
- - `icon` зафиксирован в `<CrossStrokeSrvIcon20 paletteIndex={0} />`
112
- - `title` по умолчанию `"Закрыть"`
113
- - `className` объединяется с `.confirmCloseButton` (абсолютное позиционирование top/right: 24px; 16px на `< screen-sm-max`).
114
-
115
- ### Compound: `Confirm.Content` — `IConfirmContentProps extends HTMLAttributes<HTMLDivElement>`
116
-
117
- Обычный `<div className={clsx(className, styles.confirmContent)}>` с
118
- `margin-bottom: 24px` (16px на мобильных) и `padding-right: 56px`
119
- (место под кнопку закрытия).
120
-
121
- Compound-подкомпоненты:
122
-
123
- **`Confirm.Content.Title` — `IConfirmContentTitleProps extends TTitleProps<"h1">`:**
124
- - По умолчанию `size={ETitleSize.H3}`
125
- - `margin-bottom: 12px` через `.confirmContentTitle`
126
-
127
- **`Confirm.Content.SubTitle` — `IConfirmContentSubTitleProps extends TTextProps<"div">`:**
128
- - По умолчанию `size={ETextSize.B2}`
129
- - Рендерит `<Text tag="div" />`
130
-
131
- ### Compound: `Confirm.Controls` — `IConfirmControlsProps extends HTMLAttributes<HTMLDivElement>`
132
-
133
- Обычный `<div className={clsx(className, styles.confirmControls)}>` —
134
- стилизован `white-space: nowrap` (чтобы кнопки действий не переносились).
135
- Внутри обычно два `Button` (подтвердить / отменить).
42
+ | `parentComponent` | `EConfirmParentComponent` | `EConfirmParentComponent.LIGHTBOX` | Компонент, внутри которого открыт Confirm. Влияет только на максимальную ширину |
43
+
44
+ `IConfirmProps extends IIslandProps`, поэтому доступны и props `Island` (`type`, `size`,
45
+ `loading`, `loaderScreenProps`, `withoutPaddings`) и все атрибуты `<div>`. `type` компонент
46
+ задаёт сам (`EIslandType.TYPE_1`) до spread'а — потребитель может его переопределить, но это
47
+ выход за пределы дизайна компонента.
48
+
49
+ ### Максимальная ширина (`EConfirmParentComponent`)
50
+
51
+ Класс ширины — единственное, на что влияет `parentComponent`. Из ширины родителя вычитаются
52
+ горизонтальные отступы страницы (`@page-padding-desktop-x` × 2 = 64px):
53
+
54
+ | Значение | Родитель | Max-width |
55
+ |---|---|---|
56
+ | `LIGHTBOX` | `LightBox` | `--lightBox-content-max-width` − 64px (вне лайтбокса fallback 864px → 800px) |
57
+ | `SIDE_OVERLAY_SM` | `LightBoxSideOverlay` SM | 520px |
58
+ | `SIDE_OVERLAY_MD` | `LightBoxSideOverlay` MD | 680px |
59
+ | `SIDE_OVERLAY_LG` | `LightBoxSideOverlay` LG | 800px |
60
+
61
+ `--lightBox-content-max-width` выставляет сам `LightBox` в зависимости от своего размера
62
+ (664 / 864 / 1064 / 1264px), поэтому вариант `LIGHTBOX` подстраивается под лайтбокс, а не
63
+ фиксирует число. Вне лайтбокса используется fallback-значение 864px. Карточка центрируется
64
+ (`margin: 0 auto`) и растягивается до максимума (`flex-grow: 1`).
65
+
66
+ ### Составные части
67
+
68
+ | Часть | Она же | Что делает |
69
+ |---|---|---|
70
+ | `Confirm.Content` | `ConfirmContent` | `<div>`-обёртка текста: отступ 24px до кнопок и `padding-right: 56px` — место под крестик |
71
+ | `Confirm.Content.Title` | `ConfirmContentTitle` | `Title` с `size=H3` по умолчанию (тег `<h3>`) и отступом 12px до подзаголовка |
72
+ | `Confirm.Content.SubTitle` | `ConfirmContentSubTitle` | `Text` с `size=B2` и `tag="div"` по умолчанию (тег можно переопределить) |
73
+ | `Confirm.Controls` | `ConfirmControls` | `<div>` с `white-space: nowrap` — кнопки не переносятся на новую строку. Отступ между кнопками даёт сам `Button` (12px у соседних кнопок) |
74
+ | `Confirm.Close` | `ConfirmClose` | Кнопка-крестик, абсолютно спозиционированная в правом верхнем углу; при `clickByEsc` нажатие Esc вызывает click по ней |
75
+
76
+ Все части необязательны и порядок в разметке произвольный: крестик позиционируется абсолютно,
77
+ остальные части идут потоком. Типичный порядок — `Content` → `Controls` → `Close`.
78
+
79
+ ### Закрытие по Esc (`Confirm.Close`)
80
+
81
+ `clickByEsc` — обязательный prop `Confirm.Close`. При `true` компонент оборачивается в
82
+ `TriggerClickOnKeyDownEvent`, который слушает `keydown` на `window` (через `KeyDownListener`) и
83
+ вызывает `click()` по кнопке.
84
+
85
+ - Слушатель **глобальный**: он сработает на Esc из любого места страницы, пока `Confirm.Close`
86
+ смонтирован. Поэтому передавай `clickByEsc` флагом видимости предупреждения
87
+ (`clickByEsc={topOverlayOpened}`), иначе Esc будет закрывать невидимый Confirm и конфликтовать
88
+ с Esc родительского `LightBox`.
89
+ - Клик выполняется, только если кнопка видима: `TriggerClickOnKeyDownEvent` проверяет
90
+ `offsetParent !== null`. В jsdom `offsetParent` всегда `null`, поэтому в unit-тестах его
91
+ мокают (`__tests__/ConfirmClose.test.tsx`).
92
+ - Совпадение клавиши проверяется по `event.keyCode` (`EVENT_KEY_CODES.ESCAPE`), а не по `key`.
136
93
 
137
94
  ---
138
95
 
139
- ## Ключевые особенности реализации
140
-
141
- ### Confirm — это Island type=TYPE_1
142
-
143
- ```tsx
144
- <Island
145
- type={EIslandType.TYPE_1}
146
- className={clsx(className, styles.confirm, parentComponentToClassNameMap[parentComponent])}
147
- role="dialog"
148
- aria-modal="true"
149
- {...htmlDivAttributes}
150
- >
151
- <Island.Body>{children}</Island.Body>
152
- </Island>
153
- ```
154
-
155
- **Почему Island, а не чистый div:** Island даёт фирменные радиусы,
156
- тени и padding. TYPE_1 — "приподнятая" поверхность. Если захочется
157
- другого типа — это смена визуала Confirm, breaking-change.
96
+ ## Дизайн-токены
158
97
 
159
- **`<Island.Body>` обязателен** — Island ожидает compound-структуру для
160
- padding'а контента. Без Body контент приклеится к границам.
98
+ Собственных токенов у Confirm нет: `styles/Confirm.module.less` задаёт только ширину, отступы и
99
+ позиционирование крестика. Цвета приходят из вложенных компонентов — фон и тень от `Island`
100
+ (`Island.Type1_Background`, `Island.Type1_Shadow`), текст от `Title` / `Text`, кнопка закрытия от
101
+ `Button` (тема `SECONDARY`). Переопределяются они через `ThemeProvider` у соответствующего
102
+ компонента — см. `Island-ai.md`, `Button-ai.md` и `ThemeProvider-ai.md` → «Как переопределять
103
+ токены».
161
104
 
162
- ### `parentComponent` → max-width map
105
+ ---
163
106
 
164
- ```ts
165
- const parentComponentToClassNameMap = {
166
- [EConfirmParentComponent.LIGHTBOX]: styles.isInLightBox,
167
- [EConfirmParentComponent.SIDE_OVERLAY_SM]: styles.isInSideOverlaySM,
168
- [EConfirmParentComponent.SIDE_OVERLAY_MD]: styles.isInSideOverlayMD,
169
- [EConfirmParentComponent.SIDE_OVERLAY_LG]: styles.isInSideOverlayLG,
170
- };
171
- ```
107
+ ## Инварианты
172
108
 
173
- Зачем: Confirm рендерится внутри родительского overlay'а и должен
174
- визуально поместиться в его внутренний контент. `parentComponent`
175
- сообщает компоненту, какой контейнер его содержит, чтобы он сам
176
- подсчитал max-width. Если не проставить — дефолт `LIGHTBOX`.
109
+ - **`forwardRef`** — обязателен у `Confirm` и всех частей. `ref` у `Confirm` идёт на корневой
110
+ `<div>` `Island` (тот же элемент, что получает `className`, `role` и `...rest`), у
111
+ `Confirm.Close` — на `<button>`.
112
+ - **Корневой элемент — `Island`, а не голый `<div>`.** Замена сломает фон, скругление и отступы
113
+ карточки, а также `position: relative` у `Island.Body`, от которого зависит абсолютное
114
+ позиционирование крестика.
115
+ - **`children` кладутся в `Island.Body`.** Крестик позиционируется относительно `Island.Body`,
116
+ поэтому оборачивать содержимое в дополнительный контейнер с `position: relative` нельзя —
117
+ крестик уедет.
118
+ - **`role="dialog"` и `aria-modal="true"` выставляются до spread'а props** — потребитель может
119
+ их переопределить (например на `alertdialog`). Убирать роль из компонента нельзя: на неё
120
+ опираются тесты и accessibility-контракт.
121
+ - **Публичный API:** `Confirm`, `IConfirmProps`, `IConfirmFC`, `EConfirmParentComponent`
122
+ экспортируются из `src/components/Confirm/index.ts`. Значения enum — строки `lightBox`,
123
+ `sideOverlaySM`, `sideOverlayMD`, `sideOverlayLG`. Части доступны только как статики
124
+ (`Confirm.Close` / `.Content` / `.Controls`), из barrel они не экспортируются — их типы
125
+ (`IConfirmCloseProps`, `IConfirmContentProps`, `IConfirmControlsProps` и др.) живут в
126
+ `components/`.
127
+ - **`IConfirmFC` и `IConfirmContentFC` описывают `React.ForwardRefExoticComponent` со
128
+ статиками.** Явная аннотация снимает проверку лишних свойств у `Object.assign`, поэтому при
129
+ добавлении статики интерфейс нужно править синхронно — иначе статика окажется в рантайме, но
130
+ не в публичном типе.
131
+ - **`.isInLightBox` дублирует базовый `.confirm` намеренно** — max-width совпадает, класс
132
+ выставляется для симметрии с остальными значениями enum и читаемости DOM.
133
+ - **`PARENT_COMPONENT_TO_CLASS_NAME_MAP` — внутренняя константа** `Confirm.tsx`, в barrel не
134
+ экспортируется.
177
135
 
178
- **Если добавить новый parent (например, SIDE_OVERLAY_XL):** нужно
179
- обновить и enum, и map, и добавить `.isInSideOverlayXL` в LESS.
136
+ ---
180
137
 
181
- ### `clickByEsc` → обёртка в `TriggerClickOnKeyDownEvent`
138
+ ## Accessibility
182
139
 
183
- ```tsx
184
- if (clickByEsc) {
185
- return (
186
- <TriggerClickOnKeyDownEvent targetRef={ref} eventKeyCode={EVENT_KEY_CODES.ESCAPE}>
187
- <ConfirmCloseButton {...buttonProps} ref={ref} />
188
- </TriggerClickOnKeyDownEvent>
189
- );
190
- }
191
- return <ConfirmCloseButton {...buttonProps} />;
192
- ```
140
+ - **Роль:** корневой элемент — `role="dialog"` с `aria-modal="true"`. Доступного имени компонент
141
+ сам не проставляет: если у предупреждения есть `Confirm.Content.Title`, свяжи их
142
+ (`aria-labelledby` на `Confirm` и `id` на заголовке) или передай `aria-label` — иначе
143
+ скринридер объявит диалог без названия.
144
+ - **Фокус компонент не трапит и не переводит.** Ловушку фокуса даёт контейнер: `TopOverlay`
145
+ использует `FocusTrapExtended`, `LightBox` — свой focus trap. Отдельно от них Confirm
146
+ доступностью модального диалога не обладает.
147
+ - **Клавиатура:** собственных обработчиков нет, кроме Esc у `Confirm.Close` при `clickByEsc`
148
+ (глобальный слушатель `keydown` на `window`). Кнопки действий — обычные `<button>` из `Button`.
149
+ - **Текст кнопки закрытия задаёт потребитель, и он обязателен.** Дефолта у `Confirm.Close`
150
+ нет: строки на конкретном языке внутри компонентов запрещены. Кнопка иконочная, поэтому
151
+ без `title` (или `aria-label`) у неё не будет доступного имени — передавай его всегда.
152
+ - **Заголовок — `<h3>` по умолчанию** (`Title` берёт тег из `size`). Если по структуре страницы
153
+ нужен другой уровень, передай `tag` в `Confirm.Content.Title`.
193
154
 
194
- **Важно:** `TriggerClickOnKeyDownEvent` слушает Esc на `document` и
195
- программно кликает элемент по `ref`. Не вешает hard-bound listener
196
- на кнопку. Если в родителе уже есть обработчик Escape (LightBox
197
- обычно закрывается по Esc), появятся **конфликты** — Confirm должен
198
- останавливать пропагацию или родитель должен учитывать модальную
199
- вложенность. Это зона ответственности потребителя, не Confirm.
200
-
201
- ### Кнопка закрытия — absolute позиция
202
-
203
- ```less
204
- .confirmCloseButton {
205
- position: absolute;
206
- top: 24px;
207
- right: 24px;
208
- }
209
- @media (max-width: @screen-sm-max) {
210
- .confirmCloseButton {
211
- top: 16px;
212
- right: 16px;
213
- }
214
- }
215
- ```
155
+ ---
216
156
 
217
- **Абсолютное позиционирование** требует `position: relative` или
218
- `position: absolute` на ближайшем предке. Island type=TYPE_1
219
- создаёт этот контекст. Если оборачивать Confirm в произвольный
220
- контейнер без position — кнопка "улетит" к ближайшему.
157
+ ## Связанные компоненты
221
158
 
222
- ### `padding-right: 56px` у Content
159
+ ### Контракты (в `related`)
223
160
 
224
- `.confirmContent { padding-right: 56px; }` — место под кнопку закрытия,
225
- чтобы текст Title не перекрывался с крестиком. Значение: ширина кнопки
226
- (40px для MD) + gap (16px).
161
+ - `Island` — корневая карточка. `IConfirmProps extends IIslandProps`, поэтому добавление prop в
162
+ `Island` расширяет публичный API `Confirm` без правок в его коде. Confirm задаёт
163
+ `type={EIslandType.TYPE_1}` и кладёт `children` в `Island.Body`.
164
+ - `Button` — `ConfirmCloseButton` рендерит `Button` с темой `SECONDARY`, размером `MD` и иконкой
165
+ `CrossStrokeSrvIcon20`; props `theme`, `size` и `icon` в `IConfirmCloseButtonProps` вырезаны
166
+ через `Omit`. Кнопки действий потребитель кладёт в `Confirm.Controls` сам.
167
+ - `Title` — основа `Confirm.Content.Title` (дефолт `size=H3`).
168
+ - `Text` — основа `Confirm.Content.SubTitle` (дефолт `size=B2`, `tag="div"`).
169
+ - `TriggerClickOnKeyDownEvent` — обёртка, дающая `Confirm.Close` реакцию на Esc при `clickByEsc`.
227
170
 
228
- ### Zero tokens — всё из родителей
171
+ ### Части Confirm (своего AI.md не имеют)
229
172
 
230
- В `Confirm.module.less` **нет** CSS-переменных вида
231
- `--triplex-next-Confirm-*`. Используются:
232
- - `--lightBox-content-max-width` (токен LightBox)
233
- - `@lightBox-content-max-width-fallback` (LESS-fallback)
234
- - `@sideOverlaySMWidth`, `@sideOverlayMDWidth`, `@sideOverlayLGWidth`
235
- - `@page-padding-desktop-x`, `@screen-sm-max`
173
+ - `ConfirmContent` (`Confirm.Content`) и его части `ConfirmContentTitle` (`.Title`),
174
+ `ConfirmContentSubTitle` (`.SubTitle`).
175
+ - `ConfirmControls` (`Confirm.Controls`).
176
+ - `ConfirmClose` (`Confirm.Close`) и внутренний `ConfirmCloseButton`.
236
177
 
237
- **Это осознанное решение**: Confirm — слой-"контент", не независимый
238
- визуальный блок. Вся пластика — от Island. Размеры — от контейнера.
178
+ ### Контейнеры, в которых открывают Confirm (в `related` не входят — связи по коду нет)
239
179
 
240
- ### `IConfirmFC` — кастомный тип интерфейса
180
+ - `TopOverlay` (он же `LightBox.TopOverlay`) — штатное место Confirm: маска поверх контента
181
+ лайтбокса и выезжающая сверху панель с ловушкой фокуса. Значение `parentComponent` выбирается
182
+ по тому, в чём открыт сам лайтбокс.
183
+ - `LightBox` и `LightBoxSideOverlay` — задают ширину, под которую подстраивается Confirm через
184
+ `parentComponent`.
185
+ - `ModalWindow` — направление из «Не используй когда»: самостоятельный модальный диалог поверх
186
+ страницы.
241
187
 
242
- ```ts
243
- export interface IConfirmFC extends React.FC<IConfirmProps> {
244
- Close: typeof ConfirmClose;
245
- Content: typeof ConfirmContent;
246
- Controls: typeof ConfirmControls;
247
- }
248
- ```
188
+ ---
249
189
 
250
- Аналог Badge / AlertProcess / Chip. TS видит `Confirm.Close` типизированно,
251
- без `as any`.
190
+ ## Stories
252
191
 
253
- ---
192
+ Основные истории: `stories/Confirm/Confirm.stories.tsx`
193
+ Файлы примеров: `stories/Confirm/examples/`
254
194
 
255
- ## Инварианты
195
+ | Story | Example file | Что демонстрирует |
196
+ |---|---|---|
197
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `parentComponent`, наличия крестика и `clickByEsc` |
198
+ | `Default` | `Default.tsx` | Минимальное предупреждение: заголовок, текст, две кнопки, крестик |
199
+ | `Parent components` | `ParentComponents.tsx` | Все значения `EConfirmParentComponent` и разница в максимальной ширине |
200
+ | `Example` | `Example.tsx` | Продуктовый сценарий: Confirm внутри `TopOverlay` подтверждает закрытие `LightBox` |
201
+ | `VisualTests` | `VisualTests.tsx` | Скриншот-кейсы: только заголовок, без крестика, длинный текст с тремя кнопками |
256
202
 
257
- - **`role="dialog" aria-modal="true"`** — обязательны. Не переопределять
258
- через пропсы (spread-order перебьёт, но не надо).
259
- - **`Island.Body` обёртка** внутри Confirm — обязательна. Не превращать
260
- `children` в прямых детей Island.
261
- - **`parentComponentToClassNameMap` и enum синхронны** — при добавлении
262
- нового значения enum обновлять и map, и LESS. Дефолт — `LIGHTBOX`.
263
- - **`Confirm.Close` с `clickByEsc` должен стоять внутри одного Confirm**
264
- — не выносить. `TriggerClickOnKeyDownEvent` работает через document,
265
- Esc сработает где угодно.
266
- - **Кнопка закрытия — absolute относительно Island** — требует
267
- `position: relative` на `.confirm`/Island. Island задаёт это сам.
268
- - **`padding-right: 56px` в `.confirmContent`** — резерв под кнопку.
269
- Если убрать кнопку — уменьшить padding.
270
- - **Нет собственных CSS-токенов** — не добавлять. Все стили делегируются
271
- Island и родительскому overlay.
272
- - **Нет forwardRef на root** — известный debt. Не добавлять точечно.
203
+ `Playground` и `Example` исключены из скриншот-тестов: песочница интерактивна, а лайтбокс в
204
+ `Example` открывается по клику — статичный скриншот показал бы только кнопку.
273
205
 
274
206
  ---
275
207
 
276
- ## Accessibility
208
+ ## История изменений
277
209
 
278
- - **`role="dialog" aria-modal="true"`** — семантика модального диалога.
279
- Screen reader объявит как модал и ограничит чтение его содержимым.
280
- - **Focus management** — **не реализован самим Confirm**. Ожидается,
281
- что родительский overlay (LightBox/SideOverlay) уже управляет focus
282
- trap'ом, и внутри Confirm фокус сам попадает на первый интерактивный
283
- элемент (обычно кнопку закрытия или первый контрол).
284
- - **Esc handling** — через `clickByEsc` и `TriggerClickOnKeyDownEvent`.
285
- Важно: Esc-listener вешается на document, не на Confirm. Если
286
- родитель уже закрывается по Esc — возможен двойной trigger.
287
- - **Label'ы** — Confirm сам `aria-label`/`aria-labelledby` не
288
- проставляет. Если нужно явное имя для screen reader'а — передать
289
- `aria-labelledby` через пропсы с id `Confirm.Content.Title` или
290
- `aria-label` текстом.
291
- - **Кнопка закрытия** имеет `title="Закрыть"` по умолчанию (becomes
292
- tooltip + `aria-label` fallback для `<Button>` — зависит от
293
- реализации Button).
210
+ | Дата | Изменение |
211
+ |---|---|
212
+ | 2026-09-15 | Создан документ. AI-рефакторинг: `Confirm`, `Confirm.Content`, `Confirm.Content.Title`, `Confirm.Content.SubTitle`, `Confirm.Controls` и `Confirm.Close` переведены с `React.FC` на `forwardRef` (статики сохранены через `Object.assign`), JSDoc на props, значениях `EConfirmParentComponent` и компонентах, `className` больше не идёт перед базовыми классами в `clsx`. Добавлены unit-тесты (34) и stories `stories/Confirm/`. Публичный API не изменён. |
294
213
 
295
214
  ---
296
215
 
297
- ## Дизайн-токены
298
-
299
- **Нет собственных.** Визуальные токены наследуются от `Island`
300
- (type=TYPE_1): радиус, фон, тени, внутренние отступы. Layout-переменные
301
- — LESS (`@sideOverlay*Width`, `@page-padding-desktop-x`, `@screen-sm-max`).
216
+ ## Примеры использования
302
217
 
303
- Если нужно кастомизировать Confirm в проекте — переопределять токены
304
- Island или оборачивать в собственный CSS.
218
+ ### Default
305
219
 
306
- ---
220
+ `stories/Confirm/examples/Default.tsx`
307
221
 
308
- ## Связанные компоненты
222
+ ```tsx
223
+ import React from "react";
224
+ import { Button, Confirm, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
225
+
226
+ export const Default = () => (
227
+ <Confirm aria-labelledby="confirm-default-title">
228
+ <Confirm.Content>
229
+ <Confirm.Content.Title id="confirm-default-title">Внимание</Confirm.Content.Title>
230
+ <Confirm.Content.SubTitle>
231
+ Несохранённые данные будут утеряны. Вы уверены, что хотите покинуть форму редактирования?
232
+ </Confirm.Content.SubTitle>
233
+ </Confirm.Content>
234
+
235
+ <Confirm.Controls>
236
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => {}}>
237
+ Отмена
238
+ </Button>
239
+ <Button theme={EButtonTheme.DANGER} size={EComponentSize.MD} onClick={() => {}}>
240
+ Покинуть форму
241
+ </Button>
242
+ </Confirm.Controls>
243
+
244
+ <Confirm.Close title="Закрыть" clickByEsc={false} onClick={() => {}} />
245
+ </Confirm>
246
+ );
247
+ ```
309
248
 
310
- - `Island` (`src/components/Island`) — визуальная основа. Confirm =
311
- `Island type=TYPE_1` + позиционирование + label-подкомпоненты.
312
- - `LightBox` (`src/components/LightBox`) — основной контейнер, в котором
313
- Confirm открывается поверх контента.
314
- - `SideOverlay*` (`src/components/SideOverlay`) — вторичные контейнеры
315
- (SM/MD/LG). Размеры задают ширину Confirm через `parentComponent`.
316
- - `Button` (`src/components/Button`) — кнопка закрытия (SECONDARY, MD,
317
- CrossStrokeSrvIcon20) и кнопки действий в `Confirm.Controls`.
318
- - `TriggerClickOnKeyDownEvent` (`src/components/Triggers`) — механизм
319
- Esc → click (`Confirm.Close` при `clickByEsc`).
320
- - `Typography.Title` / `Typography.Text` — базы для
321
- `Confirm.Content.Title` / `Confirm.Content.SubTitle`.
322
- - `ModalWindow` — автономный диалог (с бэкдропом). Не замена Confirm:
323
- разная семантика слоя.
249
+ ### Parent components
324
250
 
325
- ---
251
+ `stories/Confirm/examples/ParentComponents.tsx`
326
252
 
327
- ## Stories
253
+ ```tsx
254
+ import React from "react";
255
+ import { Button, Confirm, EButtonTheme, EComponentSize, EConfirmParentComponent } from "@sberbusiness/triplex-next";
256
+
257
+ const PARENT_COMPONENTS = Object.values(EConfirmParentComponent);
258
+
259
+ export const ParentComponents = () => (
260
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
261
+ {PARENT_COMPONENTS.map((parentComponent) => (
262
+ <div key={parentComponent}>
263
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{parentComponent}</div>
264
+
265
+ <Confirm parentComponent={parentComponent} aria-labelledby={`confirm-${parentComponent}-title`}>
266
+ <Confirm.Content>
267
+ <Confirm.Content.Title id={`confirm-${parentComponent}-title`}>Внимание</Confirm.Content.Title>
268
+ <Confirm.Content.SubTitle>
269
+ Максимальная ширина предупреждения зависит от компонента, в котором оно открыто.
270
+ </Confirm.Content.SubTitle>
271
+ </Confirm.Content>
272
+
273
+ <Confirm.Controls>
274
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => {}}>
275
+ Отмена
276
+ </Button>
277
+ <Button theme={EButtonTheme.DANGER} size={EComponentSize.MD} onClick={() => {}}>
278
+ Покинуть форму
279
+ </Button>
280
+ </Confirm.Controls>
281
+
282
+ <Confirm.Close title="Закрыть" clickByEsc={false} onClick={() => {}} />
283
+ </Confirm>
284
+ </div>
285
+ ))}
286
+ </div>
287
+ );
288
+ ```
328
289
 
329
- **Нет собственных stories.** Документируется косвенно через сценарии
330
- `LightBox`/`SideOverlay`, где Confirm появляется как подтверждение
331
- закрытия.
290
+ ### Example
332
291
 
333
- Добавлять stories при миграции на AI-ready pattern — stories должны
334
- эмулировать контекст (мок родительского overlay или использование
335
- реального `LightBox`/`SideOverlay` с открытым Confirm внутри).
292
+ `stories/Confirm/examples/Example.tsx`
336
293
 
337
- ---
294
+ ```tsx
295
+ import React, { useState } from "react";
296
+ import {
297
+ Button,
298
+ Confirm,
299
+ EBodyPageType,
300
+ EButtonTheme,
301
+ EComponentSize,
302
+ EFontType,
303
+ EHeaderPageType,
304
+ ETextSize,
305
+ ETitleSize,
306
+ FocusTrapUtils,
307
+ LightBox,
308
+ Page,
309
+ Text,
310
+ Title,
311
+ TopOverlay,
312
+ } from "@sberbusiness/triplex-next";
313
+
314
+ /**
315
+ * Типовой сценарий: закрытие LightBox с несохранёнными данными подтверждается
316
+ * предупреждением в верхней панели.
317
+ */
318
+ export const Example = () => {
319
+ const [lightBoxOpened, setLightBoxOpened] = useState(false);
320
+ const [topOverlayOpened, setTopOverlayOpened] = useState(false);
321
+
322
+ const handleCloseLightBox = () => setTopOverlayOpened(true);
323
+ const handleCancel = () => setTopOverlayOpened(false);
324
+ const handleConfirm = () => {
325
+ setTopOverlayOpened(false);
326
+ setLightBoxOpened(false);
327
+ };
338
328
 
339
- ## История изменений
329
+ return (
330
+ <div>
331
+ <Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => setLightBoxOpened(true)}>
332
+ Открыть LightBox
333
+ </Button>
334
+
335
+ {lightBoxOpened ? (
336
+ <LightBox isLoading={false} isSideOverlayOpened={false} isTopOverlayOpened={topOverlayOpened}>
337
+ <LightBox.Content isLoading={false}>
338
+ <Page>
339
+ <Page.Header type={EHeaderPageType.FIRST} sticky>
340
+ <Page.Header.Title>
341
+ <Page.Header.Title.Content>
342
+ <Title
343
+ tag="h1"
344
+ size={ETitleSize.H1}
345
+ tabIndex={-1}
346
+ // Устанавливает фокус на первый элемент при открытии LightBox.
347
+ {...{ [FocusTrapUtils.firstInteractionElementDataAttr]: true }}
348
+ >
349
+ Форма редактирования
350
+ </Title>
351
+ </Page.Header.Title.Content>
352
+ </Page.Header.Title>
353
+ </Page.Header>
354
+
355
+ <Page.Body type={EBodyPageType.FIRST}>
356
+ <Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
357
+ Закройте лайтбокс крестиком — верхняя панель попросит подтвердить выход.
358
+ </Text>
359
+ </Page.Body>
360
+ </Page>
361
+
362
+ <TopOverlay opened={topOverlayOpened} onOpen={() => {}} onClose={() => {}}>
363
+ <Confirm aria-labelledby="confirm-example-title">
364
+ <Confirm.Content>
365
+ <Confirm.Content.Title id="confirm-example-title">Внимание</Confirm.Content.Title>
366
+ <Confirm.Content.SubTitle>
367
+ Несохранённые данные будут утеряны. Вы уверены, что хотите покинуть форму
368
+ редактирования?
369
+ </Confirm.Content.SubTitle>
370
+ </Confirm.Content>
371
+
372
+ <Confirm.Controls>
373
+ <Button
374
+ theme={EButtonTheme.SECONDARY}
375
+ size={EComponentSize.MD}
376
+ onClick={handleCancel}
377
+ >
378
+ Отмена
379
+ </Button>
380
+ <Button
381
+ theme={EButtonTheme.DANGER}
382
+ size={EComponentSize.MD}
383
+ onClick={handleConfirm}
384
+ >
385
+ Покинуть форму
386
+ </Button>
387
+ </Confirm.Controls>
388
+
389
+ <Confirm.Close
390
+ title="Закрыть"
391
+ // Закрыть по Esc, только пока верхняя панель открыта.
392
+ clickByEsc={topOverlayOpened}
393
+ onClick={handleCancel}
394
+ />
395
+ </Confirm>
396
+ </TopOverlay>
397
+ </LightBox.Content>
398
+
399
+ <LightBox.Controls>
400
+ <LightBox.Controls.Close title="Закрыть" onClick={handleCloseLightBox} />
401
+ </LightBox.Controls>
402
+ </LightBox>
403
+ ) : null}
404
+ </div>
405
+ );
406
+ };
407
+ ```
340
408
 
341
- | Дата | Изменение |
342
- |---|---|
343
- | 2026-04-18 | Создан документ |