@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.48.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +4 -4
- package/bundle/components/ChipSuggest.md +7 -6
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +11 -4
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MonthYearField.md +15 -4
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +48 -21
- package/bundle/release-notes/1.47.0.mdx +115 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: Confirm
|
|
3
|
-
category:
|
|
4
|
-
|
|
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
|
-
`
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
в контейнер родителя.
|
|
14
|
+
Предупреждение о закрытии лайтбокса или его боковой панели: карточка `Island` типа `TYPE_1`
|
|
15
|
+
с ролью `dialog` и `aria-modal="true"`, внутри которой лежит текст (`Confirm.Content`), кнопки
|
|
16
|
+
действий (`Confirm.Controls`) и кнопка-крестик (`Confirm.Close`). Собственного состояния нет —
|
|
17
|
+
компонент ничего не открывает и не закрывает, обработчики кнопок задаёт потребитель.
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
Используй когда: действие пользователя приведёт к потере данных (закрытие формы, выход из
|
|
20
|
+
мастера) и его нужно подтвердить прямо поверх контента — как правило, внутри `TopOverlay`
|
|
21
|
+
(он же `LightBox.TopOverlay`), который и обеспечивает маску, выезд панели и ловушку фокуса.
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
поверх
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
###
|
|
38
|
+
### Опциональные props
|
|
101
39
|
|
|
102
|
-
| Prop | Тип |
|
|
40
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
103
41
|
|---|---|---|---|
|
|
104
|
-
| `
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
`
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
105
|
+
---
|
|
163
106
|
|
|
164
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
179
|
-
обновить и enum, и map, и добавить `.isInSideOverlayXL` в LESS.
|
|
136
|
+
---
|
|
180
137
|
|
|
181
|
-
|
|
138
|
+
## Accessibility
|
|
182
139
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
`position: absolute` на ближайшем предке. Island type=TYPE_1
|
|
219
|
-
создаёт этот контекст. Если оборачивать Confirm в произвольный
|
|
220
|
-
контейнер без position — кнопка "улетит" к ближайшему.
|
|
157
|
+
## Связанные компоненты
|
|
221
158
|
|
|
222
|
-
###
|
|
159
|
+
### Контракты (в `related`)
|
|
223
160
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
###
|
|
171
|
+
### Части Confirm (своего AI.md не имеют)
|
|
229
172
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
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
|
-
|
|
238
|
-
визуальный блок. Вся пластика — от Island. Размеры — от контейнера.
|
|
178
|
+
### Контейнеры, в которых открывают Confirm (в `related` не входят — связи по коду нет)
|
|
239
179
|
|
|
240
|
-
|
|
180
|
+
- `TopOverlay` (он же `LightBox.TopOverlay`) — штатное место Confirm: маска поверх контента
|
|
181
|
+
лайтбокса и выезжающая сверху панель с ловушкой фокуса. Значение `parentComponent` выбирается
|
|
182
|
+
по тому, в чём открыт сам лайтбокс.
|
|
183
|
+
- `LightBox` и `LightBoxSideOverlay` — задают ширину, под которую подстраивается Confirm через
|
|
184
|
+
`parentComponent`.
|
|
185
|
+
- `ModalWindow` — направление из «Не используй когда»: самостоятельный модальный диалог поверх
|
|
186
|
+
страницы.
|
|
241
187
|
|
|
242
|
-
|
|
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
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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
|
-
##
|
|
208
|
+
## История изменений
|
|
277
209
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
330
|
-
`LightBox`/`SideOverlay`, где Confirm появляется как подтверждение
|
|
331
|
-
закрытия.
|
|
290
|
+
### Example
|
|
332
291
|
|
|
333
|
-
|
|
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 | Создан документ |
|