@sberbusiness/triplex-next-mcp-server 1.43.0-beta → 1.45.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 -6
- package/bundle/components/AlertContext.md +11 -8
- package/bundle/components/AlertProcess.md +16 -12
- package/bundle/components/Badge.md +6 -3
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/Button.md +46 -43
- package/bundle/components/ButtonDropdownExtended.md +3 -2
- package/bundle/components/ButtonIcon.md +7 -4
- package/bundle/components/Calendar.md +62 -59
- package/bundle/components/Caption.md +26 -23
- package/bundle/components/Checkbox.md +32 -29
- package/bundle/components/CheckboxTreeExtended.md +9 -5
- package/bundle/components/Chip.md +44 -41
- package/bundle/components/CodeText.md +26 -23
- package/bundle/components/Col.md +1 -2
- package/bundle/components/CollapsibleTree.md +8 -5
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/Dropdown.md +33 -28
- package/bundle/components/EmptyView.md +12 -5
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/FooterPage.md +6 -2
- package/bundle/components/FormField.md +44 -41
- package/bundle/components/FormGroup.md +192 -52
- package/bundle/components/Header.md +491 -211
- package/bundle/components/HeaderPage.md +6 -2
- package/bundle/components/ImageGallery.md +33 -34
- package/bundle/components/ImageGalleryExtended.md +33 -34
- package/bundle/components/Island.md +52 -14
- package/bundle/components/LightBox.md +25 -18
- package/bundle/components/Link.md +211 -96
- package/bundle/components/ListActionItem.md +7 -4
- package/bundle/components/ListItem.md +11 -8
- package/bundle/components/ListItemControlsButton.md +16 -13
- package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
- package/bundle/components/ListItemSelectable.md +7 -4
- package/bundle/components/ListMasterFooter.md +8 -5
- package/bundle/components/ListMasterHeader.md +8 -5
- package/bundle/components/ListSortableItem.md +11 -8
- package/bundle/components/ListTableItem.md +7 -4
- package/bundle/components/LoaderMiddle.md +7 -4
- package/bundle/components/LoaderScreen.md +11 -8
- package/bundle/components/LoaderSmall.md +7 -4
- package/bundle/components/Marker.md +184 -69
- package/bundle/components/MarkerStatus.md +206 -88
- package/bundle/components/MaskedField.md +2 -4
- package/bundle/components/MasterTable.md +602 -0
- package/bundle/components/ModalWindow.md +7 -4
- package/bundle/components/MultiselectField.md +2183 -132
- package/bundle/components/Notification.md +12 -9
- package/bundle/components/Overlay.md +33 -5
- package/bundle/components/Page.md +2 -2
- package/bundle/components/Pagination.md +27 -21
- package/bundle/components/Row.md +1 -1
- package/bundle/components/SelectExtendedField.md +3 -3
- package/bundle/components/SelectField.md +466 -148
- package/bundle/components/SliderExtended.md +39 -36
- package/bundle/components/SmallInput.md +194 -51
- package/bundle/components/Suggest.md +535 -99
- package/bundle/components/SwipeableArea.md +428 -79
- package/bundle/components/TableBasic.md +2302 -0
- package/bundle/components/TabsExtended.md +24 -21
- package/bundle/components/Tag.md +263 -78
- package/bundle/components/Text.md +28 -25
- package/bundle/components/TextField.md +3 -4
- package/bundle/components/ThemeProvider.md +50 -1
- package/bundle/components/Title.md +26 -23
- package/bundle/components/Tooltip.md +24 -21
- package/bundle/components/TopOverlay.md +368 -89
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/guides/codestyle.md +3 -0
- package/bundle/guides/commits.md +106 -26
- package/bundle/guides/context.md +84 -12
- package/bundle/guides/guidelines.md +321 -0
- package/bundle/guides/template.md +22 -12
- package/bundle/manifest.json +54 -20
- package/bundle/release-notes/1.44.0.mdx +113 -0
- package/bundle/release-notes/1.45.0.mdx +124 -0
- package/bundle/release-notes/1.46.0.mdx +7 -0
- package/bundle/tokens.json +6362 -0
- package/dist/index.js +177 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: TopOverlay
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
tokens: []
|
|
3
|
+
category: TopOverlay
|
|
4
|
+
related: [LightBox, Overlay]
|
|
5
|
+
tokens:
|
|
6
|
+
- TopOverlay.Background
|
|
8
7
|
stories: stories/TopOverlay/TopOverlay.stories.tsx
|
|
9
8
|
version: "1.0"
|
|
10
9
|
---
|
|
@@ -13,128 +12,189 @@ version: "1.0"
|
|
|
13
12
|
|
|
14
13
|
## Назначение
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Верхняя панель поверх контента `LightBox`: затемняющая маска на всю ширину экрана
|
|
16
|
+
лайтбокса и выезжающая сверху панель с контентом. Компонент — тонкая надстройка над
|
|
17
|
+
`Overlay` с зафиксированным `direction = EOverlayDirection.TOP`, ловушкой фокуса
|
|
18
|
+
(`FocusTrapExtended`) и пересчётом вертикального позиционирования относительно
|
|
19
|
+
экрана лайтбокса.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
прокручиваемого родителя, со встроенной фиксацией фокуса. Для общего
|
|
23
|
-
оверлея — `Overlay` напрямую.
|
|
21
|
+
Реэкспортируется как `LightBox.TopOverlay` — это тот же самый компонент, а не обёртка.
|
|
24
22
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
├── TopOverlay.tsx # FC<ITopOverlayProps>; FocusTrap + Overlay(direction=TOP)
|
|
32
|
-
├── styles/TopOverlay.module.less
|
|
33
|
-
├── __tests__/
|
|
34
|
-
└── index.ts
|
|
35
|
-
```
|
|
23
|
+
Используй когда: внутри `LightBox` нужно перекрыть контент подтверждением или
|
|
24
|
+
коротким сообщением, не закрывая сам лайтбокс (типовой сценарий — `Confirm` при
|
|
25
|
+
попытке выйти из формы с несохранёнными данными).
|
|
26
|
+
Не используй когда: панель нужна сбоку — `LightBox.SideOverlay`; нужен оверлей вне
|
|
27
|
+
`LightBox` с произвольным направлением — `Overlay`; нужно самостоятельное модальное
|
|
28
|
+
окно поверх страницы — `ModalWindow`.
|
|
36
29
|
|
|
37
30
|
---
|
|
38
31
|
|
|
39
|
-
##
|
|
32
|
+
## Варианты и props
|
|
40
33
|
|
|
41
|
-
###
|
|
34
|
+
### Обязательные props
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
| Prop | Тип | Описание |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `opened` | `boolean` | Управляемое состояние панели. Компонент не хранит его сам и не поднимает наружу: `setOpened` в нижележащий `Overlay` передаётся no-op'ом, закрытие инициирует только потребитель |
|
|
46
39
|
|
|
47
|
-
|
|
40
|
+
### Опциональные props
|
|
41
|
+
|
|
42
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
48
43
|
|---|---|---|---|
|
|
49
|
-
| `
|
|
50
|
-
| `onOpen
|
|
51
|
-
| `onClose
|
|
52
|
-
| `focusTrapProps
|
|
53
|
-
| `
|
|
44
|
+
| `children` | `React.ReactNode` | — | Содержимое панели. Обычный `ReactNode`, а не render-функция, как у `Overlay` |
|
|
45
|
+
| `onOpen` | `() => void` | — | Вызывается по завершении анимации открытия, после того как компонент запланировал активацию ловушки фокуса и пересчёт позиции. Это обновления состояния React — к моменту вызова они ещё не обязаны быть применены к DOM. При монтировании сразу с `opened={true}` анимации нет и колбэк приходит на маунте |
|
|
46
|
+
| `onClose` | `() => void` | — | Вызывается по завершении анимации закрытия, после того как компонент запланировал сброс внутреннего состояния и позиционирования. К DOM сброс так же применяется не раньше следующего рендера |
|
|
47
|
+
| `focusTrapProps` | `IFocusTrapExtendedProps` | — | Свойства `FocusTrapExtended`. Мержатся поверх собственных: `focusTrapOptions.preventScroll: true` задаётся по умолчанию и переопределяется из `focusTrapProps.focusTrapOptions`; переданный `active` перекрывает внутреннее управление ловушкой |
|
|
48
|
+
| `className` | `string` | — | Мержится с собственными классами обёртки через `clsx`, не затирает их |
|
|
49
|
+
| `style` | `React.CSSProperties` | — | Уходит на обёртку и мержится с runtime-переменной позиционирования. Собственный `top` в нём передавать не нужно: он перекроет рассчитанную позицию, потому что inline-свойство сильнее класса |
|
|
50
|
+
| `...HTMLDivAttributes` | — | — | Стандартные атрибуты корневого `<div>`-обёртки (кроме `children` и `onClose` — последний занят колбэком жизненного цикла) |
|
|
51
|
+
|
|
52
|
+
Props `opened`, `onOpen` и `onClose` берутся из `IOverlayProps` через `Pick<>` —
|
|
53
|
+
их типы и семантика синхронны с `Overlay`. Остальных props `Overlay`
|
|
54
|
+
(`direction`, `fixed`, `setOpened`, `onOpening`, `onClosing`) у `TopOverlay` нет:
|
|
55
|
+
направление зафиксировано, жизненный цикл управляется внутри.
|
|
56
|
+
|
|
57
|
+
`ref` и остальные DOM-атрибуты адресуют обёртку `.topOverlayWrapper` — элемент,
|
|
58
|
+
который держит `top`, классы `opened` / `closing` и внутри которого рендерится
|
|
59
|
+
`Overlay`. Во вложенный `Overlay` из props потребителя не уходит ничего.
|
|
60
|
+
|
|
61
|
+
### Как работает позиционирование
|
|
62
|
+
|
|
63
|
+
Обёртка панели спозиционирована `absolute` внутри контента лайтбокса, поэтому её
|
|
64
|
+
фактическое положение во вьюпорте зависит от прокрутки родителя — при открытии на
|
|
65
|
+
прокрученном контенте панель уехала бы вверх или вниз от верхней границы экрана.
|
|
66
|
+
Компонент корректирует это инкрементально: читает CSS-переменную
|
|
67
|
+
`--lightBox-screen-top` у обёртки, сравнивает с `getBoundingClientRect().top` и
|
|
68
|
+
публикует разницу в runtime-переменной `--triplex-next-runtime-TopOverlay-Top`
|
|
69
|
+
(`utils.ts` → `getNextTopPosition`). Переменную читает `.topOverlayWrapper` в
|
|
70
|
+
LESS: `top: var(--triplex-next-runtime-TopOverlay-Top, 0)` — inline-стили в
|
|
71
|
+
компонентах запрещены (`docs/ai/codestyle.md`).
|
|
72
|
+
|
|
73
|
+
Пересчёт делается дважды: сразу при переходе `opened` в `true` и повторно в
|
|
74
|
+
`onOpen` — после завершения анимации, когда положение обёртки уже окончательное.
|
|
75
|
+
При закрытии (`onClose`) смещение сбрасывается в «не рассчитано», и переменная со
|
|
76
|
+
слоя снимается — позиция возвращается к дефолту `0` из LESS. Признак «не
|
|
77
|
+
рассчитано» — `undefined`, а не `0`: рассчитанный ноль такой же результат, как
|
|
78
|
+
любой другой, и он тоже публикуется (`0px`).
|
|
79
|
+
|
|
80
|
+
При монтировании сразу с `opened={true}` пересчёт **не делается**: в этот момент
|
|
81
|
+
лайтбокс ещё не на своём месте (у него собственная анимация появления), а
|
|
82
|
+
`getBoundingClientRect()` учитывает `transform` родителей — измерение дало бы
|
|
83
|
+
смещение в никуда (проверено: панель уезжала на `top: -832px`). Позиция остаётся
|
|
84
|
+
заданной стилями (`.topOverlayWrapper { top: 0 }` + `margin-top` панели), а
|
|
85
|
+
коррекция применяется при следующем открытии переходом `false → true`. Ловушка
|
|
86
|
+
фокуса и `onOpen` на маунте при этом отрабатывают штатно.
|
|
54
87
|
|
|
55
88
|
---
|
|
56
89
|
|
|
57
|
-
##
|
|
90
|
+
## Дизайн-токены
|
|
58
91
|
|
|
59
|
-
|
|
92
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
93
|
+
«Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/TopOverlay.ts`.
|
|
60
94
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
| `activeFocusTrap` | `boolean` | Включён ли FocusTrap (включается на open, отключается перед closing). |
|
|
65
|
-
| `overlayWrapperTopPosition` | `number \| string` | Top-смещение wrapper'а (0 по умолчанию). |
|
|
66
|
-
| `prevOpened` | `Ref<boolean>` | Для определения перехода `true → false` / `false → true`. |
|
|
67
|
-
| `overlayWrapperRef` | `Ref<HTMLDivElement>` | Для расчёта `getBoundingClientRect`. |
|
|
68
|
-
|
|
69
|
-
### `updateTopPosition`
|
|
70
|
-
|
|
71
|
-
Вызывается в `handleOpen` и при `false → true`:
|
|
72
|
-
1. Читает `--lightBox-screen-top` CSS-переменную из computed-style wrapper'а.
|
|
73
|
-
2. Если `position.top < 0` (overlay выше viewport'а) — добавляет `Math.abs(position.top) + lightBoxTopPosition`.
|
|
74
|
-
3. Иначе — вычитает `position.top - lightBoxTopPosition`.
|
|
75
|
-
|
|
76
|
-
Цель: компенсировать прокрутку родителя/`LightBox`'а так, чтобы overlay
|
|
77
|
-
встал у видимого верха.
|
|
78
|
-
|
|
79
|
-
### Жизненный цикл
|
|
95
|
+
```text
|
|
96
|
+
TopOverlay.Background
|
|
97
|
+
```
|
|
80
98
|
|
|
81
|
-
|
|
82
|
-
|
|
99
|
+
Токен задаёт цвет маски. Панель намеренно рисуется без фона и тени
|
|
100
|
+
(`background: none`, `box-shadow: none`) — фон даёт контент, который в неё кладут
|
|
101
|
+
(обычно `Confirm`), поэтому токен `Overlay.Panel_Background`
|
|
102
|
+
здесь не действует.
|
|
83
103
|
|
|
84
|
-
|
|
104
|
+
Runtime CSS-переменная (задаётся компонентом через `style`, **не** дизайн-токен,
|
|
105
|
+
в `DesignTokens` не попадает):
|
|
85
106
|
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
<div className={topOverlayWrapper {opened, closing}} ref={...} style={{top: ...}}>
|
|
89
|
-
<Overlay direction={TOP} opened={opened} onOpen={handleOpen} onClose={handleClose} onClosing={handleClosing}>
|
|
90
|
-
{(overlayProps) => (
|
|
91
|
-
<>
|
|
92
|
-
<Overlay.Mask opened={overlayProps.opened} />
|
|
93
|
-
<Overlay.Panel {...overlayProps}>{children}</Overlay.Panel>
|
|
94
|
-
</>
|
|
95
|
-
)}
|
|
96
|
-
</Overlay>
|
|
97
|
-
</div>
|
|
98
|
-
</FocusTrap>
|
|
107
|
+
```
|
|
108
|
+
--triplex-next-runtime-TopOverlay-Top
|
|
99
109
|
```
|
|
100
110
|
|
|
111
|
+
Кроме токена компонент читает служебные CSS-переменные `LightBox`:
|
|
112
|
+
`--lightBox-screen-top` (в TS, для позиционирования), `--lightBox-screen-left` и
|
|
113
|
+
`--lightBox-screen-width` (в LESS, для растяжки маски по экрану лайтбокса).
|
|
114
|
+
|
|
101
115
|
---
|
|
102
116
|
|
|
103
117
|
## Инварианты
|
|
104
118
|
|
|
105
|
-
- **`
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
-
|
|
113
|
-
|
|
119
|
+
- **`forwardRef` обязателен** — target ref'а — обёртка `.topOverlayWrapper`, а не
|
|
120
|
+
вложенный `Overlay`: именно на обёртке живут позиционирование и классы состояния.
|
|
121
|
+
- Публичный API — `opened`, `onOpen`, `onClose`, `focusTrapProps`, `children`,
|
|
122
|
+
DOM-атрибуты обёртки и интерфейс `ITopOverlayProps`. Переименование или изменение
|
|
123
|
+
типов — breaking change.
|
|
124
|
+
- `direction` жёстко задан как `EOverlayDirection.TOP` и не выносится в props —
|
|
125
|
+
на нём держатся стили панели и маски.
|
|
126
|
+
- `setOpened` уходит в `Overlay` no-op'ом: компонент полностью управляемый,
|
|
127
|
+
состояние живёт у потребителя.
|
|
128
|
+
- Монтирование сразу с `opened={true}` — рабочий сценарий: `OverlayBase` в этом
|
|
129
|
+
случае вызывает `onOpen` на маунте, ловушка фокуса активируется. Пересчёт
|
|
130
|
+
позиции на маунте намеренно пропускается (см. «Как работает позиционирование») —
|
|
131
|
+
не возвращать его, иначе панель уезжает за экран.
|
|
132
|
+
- Позиция задаётся runtime-переменной `--triplex-next-runtime-TopOverlay-Top`, а
|
|
133
|
+
не inline-свойством `top`: inline-стили в компонентах запрещены. Не возвращать
|
|
134
|
+
`style={{ top }}` — правило проверяется ревью по `.coderabbit.yaml`.
|
|
135
|
+
- Props потребителя во вложенный `Overlay` не уходят: `className` и `direction`
|
|
136
|
+
задаются ему явно, а `ref`, `className`, `style` и DOM-атрибуты остаются на
|
|
137
|
+
обёртке. Спреда `restProps` в `Overlay` больше нет — не возвращать.
|
|
138
|
+
- Родительскому `LightBox` потребитель обязан передать `isTopOverlayOpened` —
|
|
139
|
+
иначе контролы лайтбокса не приглушатся под открытой панелью.
|
|
140
|
+
- Классы `.topOverlayMask`, `.topOverlayPanel`, `.topOverlayWrapper` и `.topOverlay`
|
|
141
|
+
из `styles/TopOverlay.module.less` проверяются unit-тестами — не переименовывать
|
|
142
|
+
без синхронного обновления тестов.
|
|
143
|
+
- `z-index` панели (`@z-index-step * 5`, 500) выбран так, чтобы панель была выше
|
|
144
|
+
`.lightBoxControls` и лоадера контента лайтбокса. Понижение вернёт баг, при
|
|
145
|
+
котором панель перекрывалась `LoaderScreen`.
|
|
114
146
|
|
|
115
147
|
---
|
|
116
148
|
|
|
117
149
|
## Accessibility
|
|
118
150
|
|
|
119
|
-
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
151
|
+
- Пока панель открыта, фокус удерживается внутри неё: `FocusTrapExtended`
|
|
152
|
+
активируется в `onOpen` (после анимации открытия, а при монтировании сразу
|
|
153
|
+
открытым — на маунте) и деактивируется в `onClosing` (в начале закрытия),
|
|
154
|
+
поэтому возврат фокуса на триггер происходит до того, как панель исчезнет.
|
|
155
|
+
- `focusTrapOptions.preventScroll: true` задаётся по умолчанию — установка фокуса
|
|
156
|
+
внутри панели не прокручивает контент лайтбокса.
|
|
157
|
+
- Роль диалога компонент на себя не берёт: `aria-modal="true"` и `role="dialog"`
|
|
158
|
+
уже стоят на корневом элементе `LightBox`, а панель рендерится внутри него.
|
|
159
|
+
- Маска панели некликабельна — обработчик закрытия по клику вне контента не
|
|
160
|
+
вешается. Закрытие целиком на стороне потребителя: кнопки внутри панели или
|
|
161
|
+
`Confirm.Close` с `clickByEsc`.
|
|
162
|
+
- Компонент не хардкодит тексты и `aria-label` — доступные имена задаёт потребитель
|
|
163
|
+
через содержимое панели.
|
|
123
164
|
|
|
124
165
|
---
|
|
125
166
|
|
|
126
167
|
## Связанные компоненты
|
|
127
168
|
|
|
128
|
-
- `
|
|
129
|
-
|
|
130
|
-
- `
|
|
131
|
-
|
|
169
|
+
- `LightBox` — родитель. Реэкспортирует компонент как `LightBox.TopOverlay`,
|
|
170
|
+
задаёт CSS-переменные экрана, по `isTopOverlayOpened` приглушает свои контролы.
|
|
171
|
+
- `Overlay` — база. `TopOverlay` рендерит `Overlay`, `Overlay.Mask` и
|
|
172
|
+
`Overlay.Panel`, а `ITopOverlayProps` берёт часть полей из `IOverlayProps`
|
|
173
|
+
через `Pick<>`.
|
|
174
|
+
- `FocusTrapExtended` — обёртка над `focus-trap-react`, удерживающая фокус внутри
|
|
175
|
+
открытой панели; настраивается через `focusTrapProps`.
|
|
176
|
+
- `Confirm` — типовое содержимое панели: подтверждение выхода из формы.
|
|
132
177
|
|
|
133
178
|
---
|
|
134
179
|
|
|
135
180
|
## Stories
|
|
136
181
|
|
|
137
182
|
Основные истории: `stories/TopOverlay/TopOverlay.stories.tsx`
|
|
183
|
+
Файлы примеров: `stories/TopOverlay/examples/`
|
|
184
|
+
|
|
185
|
+
| Story | Example file | Что демонстрирует |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `Playground` | `Playground.tsx` | Интерактивное управление `opened` в открытом лайтбоксе |
|
|
188
|
+
| `Default` | `Default.tsx` | Минимальный сценарий: панель открывается кнопкой из шапки лайтбокса |
|
|
189
|
+
| `WithConfirm` | `WithConfirm.tsx` | Production-like сценарий: подтверждение выхода из формы через `Confirm` |
|
|
190
|
+
| `VisualTests` | `VisualTests.tsx` | Открытая панель поверх контента лайтбокса — состояние для скриншот-тестов |
|
|
191
|
+
|
|
192
|
+
Стори `Default` и `WithConfirm` исключены из скриншот-тестов (`testRunner.skip`):
|
|
193
|
+
лайтбокс в них открывается по клику, статичный снимок показал бы только кнопку.
|
|
194
|
+
Визуальное покрытие даёт `VisualTests`, где панель отрисована открытой сразу —
|
|
195
|
+
без `play`-функции, чтобы снимок не зависел от анимации выезда. Заодно эта стори
|
|
196
|
+
сторожит сценарий «смонтирован открытым»: если вернуть пересчёт позиции на маунт,
|
|
197
|
+
её baseline сразу разъедется.
|
|
138
198
|
|
|
139
199
|
---
|
|
140
200
|
|
|
@@ -142,4 +202,223 @@ extends Pick<IOverlayProps, "opened" | "onOpen" | "onClose">
|
|
|
142
202
|
|
|
143
203
|
| Дата | Изменение |
|
|
144
204
|
|---|---|
|
|
145
|
-
| 2026-
|
|
205
|
+
| 2026-08-27 | `TopOverlay` переведён на `forwardRef`, `ITopOverlayProps` расширен атрибутами `<div>`: `ref`, `className`, `style` и DOM-атрибуты адресуют обёртку. Монтирование сразу с `opened={true}` запускает цикл открытия (ловушка фокуса, `onOpen`) — исправлено в `OverlayBase`, см. `Overlay-ai.md`; пересчёт позиции на маунте намеренно не делается. Рассчитанная позиция переехала из inline-`top` в runtime-переменную `--triplex-next-runtime-TopOverlay-Top`. |
|
|
206
|
+
| 2026-08-25 | Создан документ. AI-рефакторинг: позиционирование вынесено в `utils.ts`, добавлен `displayName`, unit-тесты на расчёт позиции и ловушку фокуса; добавлены stories `stories/TopOverlay/` |
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Примеры использования
|
|
211
|
+
|
|
212
|
+
### Default
|
|
213
|
+
|
|
214
|
+
`stories/TopOverlay/examples/Default.tsx`
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import React, { useState } from "react";
|
|
218
|
+
import {
|
|
219
|
+
Button,
|
|
220
|
+
EBodyPageType,
|
|
221
|
+
EButtonTheme,
|
|
222
|
+
EComponentSize,
|
|
223
|
+
EFontType,
|
|
224
|
+
EHeaderPageType,
|
|
225
|
+
ETextSize,
|
|
226
|
+
ETitleSize,
|
|
227
|
+
FocusTrapUtils,
|
|
228
|
+
Gap,
|
|
229
|
+
LightBox,
|
|
230
|
+
Page,
|
|
231
|
+
Text,
|
|
232
|
+
Title,
|
|
233
|
+
TopOverlay,
|
|
234
|
+
} from "@sberbusiness/triplex-next";
|
|
235
|
+
|
|
236
|
+
export const Default = () => {
|
|
237
|
+
const [lightBoxOpened, setLightBoxOpened] = useState(false);
|
|
238
|
+
const [topOverlayOpened, setTopOverlayOpened] = useState(false);
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div>
|
|
242
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => setLightBoxOpened(true)}>
|
|
243
|
+
Открыть LightBox
|
|
244
|
+
</Button>
|
|
245
|
+
|
|
246
|
+
{lightBoxOpened ? (
|
|
247
|
+
<LightBox isLoading={false} isSideOverlayOpened={false} isTopOverlayOpened={topOverlayOpened}>
|
|
248
|
+
<LightBox.Content isLoading={false}>
|
|
249
|
+
<Page>
|
|
250
|
+
<Page.Header type={EHeaderPageType.FIRST} sticky>
|
|
251
|
+
<Page.Header.Title>
|
|
252
|
+
<Page.Header.Title.Content>
|
|
253
|
+
<Title
|
|
254
|
+
tag="h1"
|
|
255
|
+
size={ETitleSize.H1}
|
|
256
|
+
tabIndex={-1}
|
|
257
|
+
// Устанавливает фокус на первый элемент при открытии LightBox.
|
|
258
|
+
{...{ [FocusTrapUtils.firstInteractionElementDataAttr]: true }}
|
|
259
|
+
>
|
|
260
|
+
Заявка на кредит
|
|
261
|
+
</Title>
|
|
262
|
+
</Page.Header.Title.Content>
|
|
263
|
+
<Page.Header.Title.Controls>
|
|
264
|
+
<Button
|
|
265
|
+
theme={EButtonTheme.GENERAL}
|
|
266
|
+
size={EComponentSize.MD}
|
|
267
|
+
onClick={() => setTopOverlayOpened(true)}
|
|
268
|
+
>
|
|
269
|
+
Открыть верхнюю панель
|
|
270
|
+
</Button>
|
|
271
|
+
</Page.Header.Title.Controls>
|
|
272
|
+
</Page.Header.Title>
|
|
273
|
+
</Page.Header>
|
|
274
|
+
|
|
275
|
+
<Page.Body type={EBodyPageType.FIRST}>
|
|
276
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
277
|
+
Верхняя панель выезжает поверх контента лайтбокса и затемняет его маской.
|
|
278
|
+
</Text>
|
|
279
|
+
</Page.Body>
|
|
280
|
+
</Page>
|
|
281
|
+
|
|
282
|
+
<TopOverlay opened={topOverlayOpened} onOpen={() => {}} onClose={() => {}}>
|
|
283
|
+
<div style={{ padding: 24, textAlign: "center" }}>
|
|
284
|
+
<Title tag="h2" size={ETitleSize.H2}>
|
|
285
|
+
Верхняя панель
|
|
286
|
+
</Title>
|
|
287
|
+
<Gap size={16} />
|
|
288
|
+
<Button
|
|
289
|
+
theme={EButtonTheme.SECONDARY}
|
|
290
|
+
size={EComponentSize.MD}
|
|
291
|
+
onClick={() => setTopOverlayOpened(false)}
|
|
292
|
+
>
|
|
293
|
+
Закрыть панель
|
|
294
|
+
</Button>
|
|
295
|
+
</div>
|
|
296
|
+
</TopOverlay>
|
|
297
|
+
</LightBox.Content>
|
|
298
|
+
|
|
299
|
+
<LightBox.Controls>
|
|
300
|
+
<LightBox.Controls.Close title="Закрыть" onClick={() => setLightBoxOpened(false)} />
|
|
301
|
+
</LightBox.Controls>
|
|
302
|
+
</LightBox>
|
|
303
|
+
) : null}
|
|
304
|
+
</div>
|
|
305
|
+
);
|
|
306
|
+
};
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### WithConfirm
|
|
310
|
+
|
|
311
|
+
`stories/TopOverlay/examples/WithConfirm.tsx`
|
|
312
|
+
|
|
313
|
+
```tsx
|
|
314
|
+
import React, { useState } from "react";
|
|
315
|
+
import {
|
|
316
|
+
Button,
|
|
317
|
+
Confirm,
|
|
318
|
+
EBodyPageType,
|
|
319
|
+
EButtonTheme,
|
|
320
|
+
EComponentSize,
|
|
321
|
+
EFontType,
|
|
322
|
+
EHeaderPageType,
|
|
323
|
+
ETextSize,
|
|
324
|
+
ETitleSize,
|
|
325
|
+
FocusTrapUtils,
|
|
326
|
+
LightBox,
|
|
327
|
+
Page,
|
|
328
|
+
Text,
|
|
329
|
+
Title,
|
|
330
|
+
TopOverlay,
|
|
331
|
+
} from "@sberbusiness/triplex-next";
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Типовой сценарий: закрытие LightBox с несохранёнными данными подтверждается в верхней панели.
|
|
335
|
+
*/
|
|
336
|
+
export const WithConfirm = () => {
|
|
337
|
+
const [lightBoxOpened, setLightBoxOpened] = useState(false);
|
|
338
|
+
const [topOverlayOpened, setTopOverlayOpened] = useState(false);
|
|
339
|
+
|
|
340
|
+
const handleCloseLightBox = () => setTopOverlayOpened(true);
|
|
341
|
+
const handleCancel = () => setTopOverlayOpened(false);
|
|
342
|
+
const handleConfirm = () => {
|
|
343
|
+
setTopOverlayOpened(false);
|
|
344
|
+
setLightBoxOpened(false);
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<div>
|
|
349
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} onClick={() => setLightBoxOpened(true)}>
|
|
350
|
+
Открыть LightBox
|
|
351
|
+
</Button>
|
|
352
|
+
|
|
353
|
+
{lightBoxOpened ? (
|
|
354
|
+
<LightBox isLoading={false} isSideOverlayOpened={false} isTopOverlayOpened={topOverlayOpened}>
|
|
355
|
+
<LightBox.Content isLoading={false}>
|
|
356
|
+
<Page>
|
|
357
|
+
<Page.Header type={EHeaderPageType.FIRST} sticky>
|
|
358
|
+
<Page.Header.Title>
|
|
359
|
+
<Page.Header.Title.Content>
|
|
360
|
+
<Title
|
|
361
|
+
tag="h1"
|
|
362
|
+
size={ETitleSize.H1}
|
|
363
|
+
tabIndex={-1}
|
|
364
|
+
// Устанавливает фокус на первый элемент при открытии LightBox.
|
|
365
|
+
{...{ [FocusTrapUtils.firstInteractionElementDataAttr]: true }}
|
|
366
|
+
>
|
|
367
|
+
Форма редактирования
|
|
368
|
+
</Title>
|
|
369
|
+
</Page.Header.Title.Content>
|
|
370
|
+
</Page.Header.Title>
|
|
371
|
+
</Page.Header>
|
|
372
|
+
|
|
373
|
+
<Page.Body type={EBodyPageType.FIRST}>
|
|
374
|
+
<Text tag="div" size={ETextSize.B3} type={EFontType.SECONDARY}>
|
|
375
|
+
Закройте лайтбокс крестиком — верхняя панель попросит подтвердить выход.
|
|
376
|
+
</Text>
|
|
377
|
+
</Page.Body>
|
|
378
|
+
</Page>
|
|
379
|
+
|
|
380
|
+
<TopOverlay opened={topOverlayOpened} onOpen={() => {}} onClose={() => {}}>
|
|
381
|
+
<Confirm>
|
|
382
|
+
<Confirm.Content>
|
|
383
|
+
<Confirm.Content.Title>Внимание</Confirm.Content.Title>
|
|
384
|
+
<Confirm.Content.SubTitle>
|
|
385
|
+
Несохранённые данные будут утеряны. Вы уверены, что хотите покинуть форму
|
|
386
|
+
редактирования?
|
|
387
|
+
</Confirm.Content.SubTitle>
|
|
388
|
+
</Confirm.Content>
|
|
389
|
+
<Confirm.Controls>
|
|
390
|
+
<Button
|
|
391
|
+
theme={EButtonTheme.SECONDARY}
|
|
392
|
+
size={EComponentSize.MD}
|
|
393
|
+
onClick={handleCancel}
|
|
394
|
+
>
|
|
395
|
+
Отмена
|
|
396
|
+
</Button>
|
|
397
|
+
<Button
|
|
398
|
+
theme={EButtonTheme.DANGER}
|
|
399
|
+
size={EComponentSize.MD}
|
|
400
|
+
onClick={handleConfirm}
|
|
401
|
+
>
|
|
402
|
+
Покинуть форму
|
|
403
|
+
</Button>
|
|
404
|
+
</Confirm.Controls>
|
|
405
|
+
<Confirm.Close
|
|
406
|
+
title="Закрыть"
|
|
407
|
+
// Закрыть по Esc, только если верхняя панель открыта.
|
|
408
|
+
clickByEsc={topOverlayOpened}
|
|
409
|
+
onClick={handleCancel}
|
|
410
|
+
/>
|
|
411
|
+
</Confirm>
|
|
412
|
+
</TopOverlay>
|
|
413
|
+
</LightBox.Content>
|
|
414
|
+
|
|
415
|
+
<LightBox.Controls>
|
|
416
|
+
<LightBox.Controls.Close title="Закрыть" onClick={handleCloseLightBox} />
|
|
417
|
+
</LightBox.Controls>
|
|
418
|
+
</LightBox>
|
|
419
|
+
) : null}
|
|
420
|
+
</div>
|
|
421
|
+
);
|
|
422
|
+
};
|
|
423
|
+
```
|
|
424
|
+
|