@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.
Files changed (85) hide show
  1. package/README.md +7 -6
  2. package/bundle/components/AlertContext.md +11 -8
  3. package/bundle/components/AlertProcess.md +16 -12
  4. package/bundle/components/Badge.md +6 -3
  5. package/bundle/components/BodyPage.md +72 -4
  6. package/bundle/components/Button.md +46 -43
  7. package/bundle/components/ButtonDropdownExtended.md +3 -2
  8. package/bundle/components/ButtonIcon.md +7 -4
  9. package/bundle/components/Calendar.md +62 -59
  10. package/bundle/components/Caption.md +26 -23
  11. package/bundle/components/Checkbox.md +32 -29
  12. package/bundle/components/CheckboxTreeExtended.md +9 -5
  13. package/bundle/components/Chip.md +44 -41
  14. package/bundle/components/CodeText.md +26 -23
  15. package/bundle/components/Col.md +1 -2
  16. package/bundle/components/CollapsibleTree.md +8 -5
  17. package/bundle/components/CollapsibleTreeExtended.md +6 -1
  18. package/bundle/components/DatePickerExtended.md +409 -262
  19. package/bundle/components/Dropdown.md +33 -28
  20. package/bundle/components/EmptyView.md +12 -5
  21. package/bundle/components/ExpandAnimation.md +263 -218
  22. package/bundle/components/Footer.md +235 -278
  23. package/bundle/components/FooterPage.md +6 -2
  24. package/bundle/components/FormField.md +44 -41
  25. package/bundle/components/FormGroup.md +192 -52
  26. package/bundle/components/Header.md +491 -211
  27. package/bundle/components/HeaderPage.md +6 -2
  28. package/bundle/components/ImageGallery.md +33 -34
  29. package/bundle/components/ImageGalleryExtended.md +33 -34
  30. package/bundle/components/Island.md +52 -14
  31. package/bundle/components/LightBox.md +25 -18
  32. package/bundle/components/Link.md +211 -96
  33. package/bundle/components/ListActionItem.md +7 -4
  34. package/bundle/components/ListItem.md +11 -8
  35. package/bundle/components/ListItemControlsButton.md +16 -13
  36. package/bundle/components/ListItemControlsButtonDropdown.md +9 -6
  37. package/bundle/components/ListItemSelectable.md +7 -4
  38. package/bundle/components/ListMasterFooter.md +8 -5
  39. package/bundle/components/ListMasterHeader.md +8 -5
  40. package/bundle/components/ListSortableItem.md +11 -8
  41. package/bundle/components/ListTableItem.md +7 -4
  42. package/bundle/components/LoaderMiddle.md +7 -4
  43. package/bundle/components/LoaderScreen.md +11 -8
  44. package/bundle/components/LoaderSmall.md +7 -4
  45. package/bundle/components/Marker.md +184 -69
  46. package/bundle/components/MarkerStatus.md +206 -88
  47. package/bundle/components/MaskedField.md +2 -4
  48. package/bundle/components/MasterTable.md +602 -0
  49. package/bundle/components/ModalWindow.md +7 -4
  50. package/bundle/components/MultiselectField.md +2183 -132
  51. package/bundle/components/Notification.md +12 -9
  52. package/bundle/components/Overlay.md +33 -5
  53. package/bundle/components/Page.md +2 -2
  54. package/bundle/components/Pagination.md +27 -21
  55. package/bundle/components/Row.md +1 -1
  56. package/bundle/components/SelectExtendedField.md +3 -3
  57. package/bundle/components/SelectField.md +466 -148
  58. package/bundle/components/SliderExtended.md +39 -36
  59. package/bundle/components/SmallInput.md +194 -51
  60. package/bundle/components/Suggest.md +535 -99
  61. package/bundle/components/SwipeableArea.md +428 -79
  62. package/bundle/components/TableBasic.md +2302 -0
  63. package/bundle/components/TabsExtended.md +24 -21
  64. package/bundle/components/Tag.md +263 -78
  65. package/bundle/components/Text.md +28 -25
  66. package/bundle/components/TextField.md +3 -4
  67. package/bundle/components/ThemeProvider.md +50 -1
  68. package/bundle/components/Title.md +26 -23
  69. package/bundle/components/Tooltip.md +24 -21
  70. package/bundle/components/TopOverlay.md +368 -89
  71. package/bundle/components/TreeView.md +343 -166
  72. package/bundle/components/TreeViewNode.md +436 -0
  73. package/bundle/guides/codestyle.md +3 -0
  74. package/bundle/guides/commits.md +106 -26
  75. package/bundle/guides/context.md +84 -12
  76. package/bundle/guides/guidelines.md +321 -0
  77. package/bundle/guides/template.md +22 -12
  78. package/bundle/manifest.json +54 -20
  79. package/bundle/release-notes/1.44.0.mdx +113 -0
  80. package/bundle/release-notes/1.45.0.mdx +124 -0
  81. package/bundle/release-notes/1.46.0.mdx +7 -0
  82. package/bundle/tokens.json +6362 -0
  83. package/dist/index.js +177 -28
  84. package/dist/index.js.map +1 -1
  85. package/package.json +1 -1
@@ -1,10 +1,9 @@
1
1
  ---
2
2
  component: TopOverlay
3
- category: Overlay
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Overlay, LightBox]
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
- Оверлей, выезжающий из верха экрана: обёртка над `Overlay` с
17
- `direction=TOP`, `Mask` + `Panel`, FocusTrap, и автокоррекцией top-позиции
18
- при скролле родителя/`LightBox`-контейнера. Используется для системных
19
- сообщений, временных панелей.
15
+ Верхняя панель поверх контента `LightBox`: затемняющая маска на всю ширину экрана
16
+ лайтбокса и выезжающая сверху панель с контентом. Компонент — тонкая надстройка над
17
+ `Overlay` с зафиксированным `direction = EOverlayDirection.TOP`, ловушкой фокуса
18
+ (`FocusTrapExtended`) и пересчётом вертикального позиционирования относительно
19
+ экрана лайтбокса.
20
20
 
21
- Используй когда: нужна верхняя shading-панель внутри `LightBox` или
22
- прокручиваемого родителя, со встроенной фиксацией фокуса. Для общего
23
- оверлея — `Overlay` напрямую.
21
+ Реэкспортируется как `LightBox.TopOverlay` — это тот же самый компонент, а не обёртка.
24
22
 
25
- ---
26
-
27
- ## Файловая структура
28
-
29
- ```text
30
- src/components/TopOverlay/
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
- ## Публичный API
32
+ ## Варианты и props
40
33
 
41
- ### `ITopOverlayProps`
34
+ ### Обязательные props
42
35
 
43
- ```ts
44
- extends Pick<IOverlayProps, "opened" | "onOpen" | "onClose">
45
- ```
36
+ | Prop | Тип | Описание |
37
+ |---|---|---|
38
+ | `opened` | `boolean` | Управляемое состояние панели. Компонент не хранит его сам и не поднимает наружу: `setOpened` в нижележащий `Overlay` передаётся no-op'ом, закрытие инициирует только потребитель |
46
39
 
47
- | Prop | Тип | Default | Описание |
40
+ ### Опциональные props
41
+
42
+ | Prop | Тип | По умолчанию | Описание |
48
43
  |---|---|---|---|
49
- | `opened` | `boolean` | — | **Обязателен**. Контролируемое открытие. |
50
- | `onOpen?` | `() => void` | — | Колбэк при открытии (вызывается из `handleOpen`, после `updateTopPosition`). |
51
- | `onClose?` | `() => void` | — | Колбэк при закрытии (после анимации). |
52
- | `focusTrapProps?` | `FocusTrapProps` | — | Пропсы для `focus-trap-react`. По умолчанию `allowOutsideClick: true, preventScroll: true`. |
53
- | `children?` | `ReactNode` | — | Содержимое панели. |
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
- | `closing` | `boolean` | Анимация закрытия в процессе (`opened` стал `false`, ещё не отработал). |
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
- - `opened: false → true`: `useEffect` зовёт `updateTopPosition`. `Overlay.onOpen` → `handleOpen` (внутри: `updateTopPosition` ещё раз + `setActiveFocusTrap(true)` + `onOpen?.()`).
82
- - `opened: true → false`: `setClosing(true)`. `Overlay.onClosing` (анимация началась) → `handleClosing` → `setActiveFocusTrap(false)`. `Overlay.onClose` (анимация завершилась) → `handleClose` → `setClosing(false)` + сброс `overlayWrapperTopPosition` + `onClose?.()`.
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
- ```tsx
87
- <FocusTrap active={activeFocusTrap} focusTrapOptions={{allowOutsideClick: true, preventScroll: true, ...}}>
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
- - **`FC, не forwardRef`** — ref не пробрасывается.
106
- - **`opened` контролируемый** — внутреннего state открытости нет. `setOpened` передаётся в `Overlay`, но это пустой no-op (`() => {}`); закрытие должно идти через внешний `opened={false}`.
107
- - **`direction = EOverlayDirection.TOP`** — зашито; для других направлений использовать `Overlay` напрямую.
108
- - **`FocusTrap` использует `focus-trap-react`** — `allowOutsideClick: true` и `preventScroll: true` зашиты как defaults; потребитель может переопределить через `focusTrapProps.focusTrapOptions`.
109
- - **`activeFocusTrap` отключается ДО `onClose`** (на `onClosing`) — чтобы анимация уехала с уже разблокированным фокусом.
110
- - **`updateTopPosition` читает `--lightBox-screen-top`** — связь с `LightBox`. Без `LightBox` переменная отсутствует → `0`.
111
- - **`Overlay.Mask` и `Overlay.Panel` рендерятся внутри одного render-prop'а** — порядок Mask → Panel зашит.
112
- - **Стили `.topOverlay`/`.topOverlayMask`/`.topOverlayPanel`/`.topOverlayWrapper` + `.opened`/`.closing`** — публичные CSS-классы (используются для управления visual transitions).
113
- - **Без явной валидации `children`** — потребитель отвечает за содержимое.
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
- - `FocusTrap` — фокус заперт внутри overlay при `activeFocusTrap = true`.
120
- - `allowOutsideClick: true` — клик вне overlay не блокируется (закрытие — на потребителе через external `opened`).
121
- - `preventScroll: true` — focus-trap не вызывает scroll при initial focus.
122
- - ARIA-роли (dialog/alertdialog) на overlay не выставляются — потребитель отвечает за разметку children (например, `<div role="dialog" aria-modal="true" aria-label>`).
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
- - `Overlay` (`Overlay.Mask`, `Overlay.Panel`) — основа.
129
- - `EOverlayDirection.TOP` — направление.
130
- - `LightBox` — типичный родитель (через CSS-переменную `--lightBox-screen-top`).
131
- - `focus-trap-react` (`FocusTrap`, `FocusTrapProps`) — npm-пакет для трапа фокуса.
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-04-18 | Создан документ |
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
+