@sberbusiness/triplex-next-mcp-server 0.24.0-beta1

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 (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +51 -0
@@ -0,0 +1,322 @@
1
+ ---
2
+ component: LightBox
3
+ category: Overlays
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [ModalWindow, Overlay, TopOverlay, Portal, MobileView, FocusTrap]
7
+ tokens:
8
+ - --lightBox-screen-left
9
+ - --lightBox-screen-width
10
+ - --lightBox-screen-top
11
+ stories: stories/LightBox/LightBox.stories.tsx
12
+ version: "1.0"
13
+ ---
14
+
15
+ # LightBox
16
+
17
+ ## Назначение
18
+
19
+ Полноэкранный модальный контейнер (лайтбокс) — занимает всю область окна
20
+ с backdrop'ом и focus-trap'ом. Composite: Header-controls + Content +
21
+ левая/правая sidebar-панели + выезжающий справа SideOverlay + TopOverlay.
22
+ Рассчитан на "иммерсивные" сценарии (просмотр документа, мастер настройки,
23
+ workflow-редактор).
24
+
25
+ Используй когда: нужен полноэкранный режим с собственным scroll-container'ом,
26
+ focus management и возможностью иметь вложенные overlay'и (sidebar,
27
+ top-bar, боковая панель). Для стандартного диалога — `ModalWindow`.
28
+
29
+ ---
30
+
31
+ ## Файловая структура
32
+
33
+ ```text
34
+ src/components/LightBox/
35
+ ├── LightBox.tsx # Основной компонент + Object.assign
36
+ ├── LightBoxContent.tsx # Скроллируемый контент, paddingTop = высота controls
37
+ ├── enums.ts # ELightBoxSize (SM/MD/LG/XL)
38
+ ├── index.ts # Barrel (+ re-export SideOverlay, enums)
39
+ ├── LightBoxControls/
40
+ │ ├── LightBoxControls.tsx # Container с data-lightbox-component="controls"
41
+ │ ├── LightBoxClose.tsx
42
+ │ ├── LightBoxNext.tsx
43
+ │ └── LightBoxPrev.tsx
44
+ ├── LightBoxSidebars/
45
+ │ ├── LightBoxLeftSidebar.tsx # forwardRef, ResizeObserver, show/hide по minVisibleWidth
46
+ │ └── LightBoxRightSidebar.tsx
47
+ ├── LightBoxSideOverlay/
48
+ │ ├── LightBoxSideOverlay.tsx # Выезжающая справа панель (Overlay direction=RIGHT)
49
+ │ ├── LightBoxSideOverlayCloseDesktop.tsx
50
+ │ ├── LightBoxSideOverlayCloseMobile.tsx
51
+ │ ├── LightBoxSideOverlayLoader.tsx
52
+ │ └── index.ts # Re-export SideOverlay
53
+ ├── LightBoxViewManager/
54
+ │ ├── LightBoxViewManager.tsx # Вычисляет CSS-переменные --lightBox-screen-*
55
+ │ └── LightBoxViewManagerConsts.ts
56
+ └── styles/
57
+ ├── LightBox.module.less
58
+ ├── LightBoxControls.module.less
59
+ └── LightBoxScroll.module.less
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Compound-структура
65
+
66
+ ```tsx
67
+ <LightBox size={ELightBoxSize.LG} isLoading={loading}>
68
+ <LightBox.Controls>
69
+ <LightBox.Controls.Prev onClick={...} />
70
+ <LightBox.Controls.Next onClick={...} />
71
+ <LightBox.Controls.Close onClick={onClose} />
72
+ </LightBox.Controls>
73
+
74
+ <LightBox.LeftSidebar fixed onShow={...}>
75
+ {/* Левая панель — список разделов */}
76
+ </LightBox.LeftSidebar>
77
+
78
+ <LightBox.Content isLoading={contentLoading} loadingTitle="Загрузка…">
79
+ {/* Основной контент */}
80
+ </LightBox.Content>
81
+
82
+ <LightBox.RightSidebar>{/* Правая панель */}</LightBox.RightSidebar>
83
+
84
+ <LightBox.SideOverlay opened={sideOpen} onClose={...}>
85
+ {/* Выезжает справа */}
86
+ </LightBox.SideOverlay>
87
+
88
+ <LightBox.TopOverlay>{/* TopOverlay из components/TopOverlay */}</LightBox.TopOverlay>
89
+ </LightBox>
90
+ ```
91
+
92
+ `LightBox.TopOverlay` — **реэкспорт внешнего** компонента `TopOverlay`,
93
+ а не локальный. Это нужно учитывать при типизации.
94
+
95
+ ---
96
+
97
+ ## Варианты и props
98
+
99
+ ### LightBox — `ILightBoxProps extends HTMLAttributes<HTMLDivElement>`
100
+
101
+ | Prop | Тип | Default | Описание |
102
+ |---|---|---|---|
103
+ | `children` | `ReactElement[]` | — | Обязателен как массив (не `ReactNode`). Компонент рендерит всех без перебора — дети статичные слоты. |
104
+ | `size` | `ELightBoxSize` | `MD` | `SM`/`MD`/`LG`/`XL` — влияет на ширину контентной зоны. |
105
+ | `focusTrapProps` | `FocusTrapProps` | — | Переопределение параметров `focus-trap-react`. |
106
+ | `forwardRef` | `MutableRefObject<HTMLElement \| null>` | — | **Не `ref`**, а отдельный prop. Корневой компонент — не `forwardRef`. |
107
+ | `mountNode` | `HTMLDivElement` | auto | Куда рендерить через Portal. По умолчанию — DOM-элемент `#LightBox-next-mount-node` (создаётся при первом монтировании). |
108
+ | `lightBoxViewManagerNodeId` | `string` | `"LightBox-next-view-manager-node"` | DOM-элемент, по границам которого позиционируется LightBox (для кейса "LightBox не на весь экран"). |
109
+ | `isLoading` | `boolean` | — | Показывает loader, блокирует FocusTrap. |
110
+ | `isSideOverlayOpened` | `boolean` | — | Родитель контролирует — включает классы `.lightBoxSideOverlayActive` на корне. |
111
+ | `isTopOverlayOpened` | `boolean` | — | Аналогично для TopOverlay. |
112
+
113
+ ### ELightBoxSize
114
+
115
+ ```ts
116
+ enum ELightBoxSize { SM = "sm", MD = "md", LG = "lg", XL = "xl" }
117
+ ```
118
+
119
+ **Не `EComponentSize`** — собственный enum с дополнительным `XL`.
120
+
121
+ ### LightBox.Content — `ILightBoxContentProps`
122
+
123
+ | Prop | Тип | Описание |
124
+ |---|---|---|
125
+ | `isLoading` | `boolean` | Накладывает `LoaderScreen` поверх контента. |
126
+ | `loadingTitle` | `ReactNode` | Подпись под спиннером. |
127
+
128
+ - Внутри — свой scroll-контейнер. `paddingTop` **вычисляется рантайм** по высоте `data-lightbox-component="controls"` (querySelector — **не по ref**).
129
+ - Слушает resize через `react-resize-detector` (debounce 100ms) и `WindowResizeListener`.
130
+
131
+ ### LightBox.LeftSidebar / RightSidebar — `forwardRef<HTMLDivElement>`
132
+
133
+ | Prop | Тип | Default | Описание |
134
+ |---|---|---|---|
135
+ | `fixed` | `boolean` | — | Закрепляет панель (не скроллится вместе с контентом). |
136
+ | `minVisibleWidth` | `number` | `100` | Ширина (px), ниже которой контент панели скрывается классом `.hidden`. |
137
+ | `onShow` / `onHide` | `() => void` | — | Callback'и при пересечении порога ширины. Хранятся в ref — замыкание на актуальное значение. |
138
+
139
+ Использует `ResizeObserver` на outer-элементе.
140
+
141
+ ### LightBox.SideOverlay — `ILightBoxSideOverlayProps`
142
+
143
+ | Prop | Тип | Default | Описание |
144
+ |---|---|---|---|
145
+ | `opened` | `boolean` | — | Управляемое состояние. |
146
+ | `onClose` / `onOpen` | `() => void` | — | Срабатывают после завершения `transitionend` (не сразу при смене `opened`). |
147
+ | `focusTrapProps` | `FocusTrapProps` | — | Собственный FocusTrap внутри LightBox'овского (вложенные trap'ы). |
148
+ | `isLoading` | `boolean` | — | Показывает внутренний loader. |
149
+ | `isTopLevelSideOverlayOpened` | `boolean` | — | Если сверху открыт ещё один overlay — отключить scroll в текущем. |
150
+ | `isTopOverlayOpened` | `boolean` | — | Аналогично. |
151
+ | `size` | `EComponentSize` | `MD` | Ширина панели. |
152
+
153
+ - Имеет подкомпоненты `.CloseDesktop` и `.CloseMobile` (две разных кнопки закрытия по вьюпорту).
154
+ - Внутри — `OverlayBase direction={RIGHT}` + `OverlayMask` + `FocusTrap`.
155
+ - Отслеживает `opening`/`closing` state'ы по `prevOpened` (useRef) — для анимированных переходов.
156
+
157
+ ### LightBox.Controls — `ILightBoxControlsFC` (Object.assign)
158
+
159
+ Container с `data-lightbox-component="controls"` — **используется для вычисления paddingTop контента** (querySelector в LightBoxContent). Не менять атрибут.
160
+
161
+ Подкомпоненты: `.Close`, `.Next`, `.Prev`.
162
+
163
+ ### LightBox.TopOverlay
164
+
165
+ Реэкспорт `TopOverlay` из `../TopOverlay/TopOverlay`. Используй как `<LightBox.TopOverlay>` или `<TopOverlay>` — идентично.
166
+
167
+ ---
168
+
169
+ ## Ключевые особенности реализации
170
+
171
+ ### Два portal'а + ViewManager
172
+
173
+ ```tsx
174
+ <>
175
+ <Portal container={lightBoxMountNode.current}>{renderLightBox()}</Portal>
176
+ <LightBoxViewManager lightBoxViewManagerNode={...} lightBoxMountNode={...} />
177
+ </>
178
+ ```
179
+
180
+ - **LightBox** рендерится в `#LightBox-next-mount-node` (обычно в body).
181
+ - **ViewManager** рендерится в `#LightBox-next-view-manager-node` (по умолчанию в body, но потребитель может создать его в нужном контейнере) и **вычисляет CSS-переменные** `--lightBox-screen-left/width/top` через `<style>`-тег на mountNode.
182
+ - Это позволяет LightBox'у "уменьшаться" до размеров конкретной области (например, если есть глобальный sidebar — LightBox не должен его перекрывать).
183
+
184
+ ### `FocusTrap` + `MobileView` fallback
185
+
186
+ ```tsx
187
+ <MobileView fallback={<FocusTrap ...>{renderLightBox()}</FocusTrap>}>
188
+ {renderLightBox()}
189
+ </MobileView>
190
+ ```
191
+
192
+ - На mobile (определяется `MobileView`) — **без focus trap'а** (нативный scroll/focus лучше работает без trap'а на мобильных).
193
+ - На desktop — `FocusTrap` с `initialFocus = getFirstInteractionElementByDataAttr` из `FocusTrapUtils`.
194
+ - `active={!isLoading}` — trap неактивен во время загрузки.
195
+
196
+ ### Safari z-index hack
197
+
198
+ ```tsx
199
+ <span ref={tempButtonRef} className={styles.tempElSafariBug} />
200
+
201
+ useEffect(() => {
202
+ if (!isSideOverlayOpened && tempButtonRef.current) {
203
+ const nextZIndex = Math.round(Math.random() * 100);
204
+ tempButtonRef.current.style.zIndex = nextZIndex.toString();
205
+ }
206
+ }, [isSideOverlayOpened]);
207
+ ```
208
+
209
+ - Невидимый `<span>`, у которого меняется `z-index` при закрытии SideOverlay — **принудительно вызывает repaint** в Safari. Баг-тикет `DCBSWT-2866`. Не трогать.
210
+
211
+ ### Scrollbar-width и body lock
212
+
213
+ ```ts
214
+ useEffect(() => {
215
+ addClassNameWithScrollbarWidth(scrollStyles); // вычисляет ширину скроллбара, прописывает в CSS
216
+ addClassNamesToDocumentElement(); // .bodyOverflowHidden → documentElement
217
+ setTimeout(addClassNamesToDocumentElement, 100); // fix: при переключении двух LightBox
218
+ return () => bodyClassNamesIsLightBoxOpen.forEach(c => document.documentElement.classList.remove(c));
219
+ }, []);
220
+ ```
221
+
222
+ - Блокировка скролла — на `documentElement`, не `body`.
223
+ - `setTimeout(100)` — фикс роутерного бага: новый LightBox монтируется **до** размонтирования старого, классы удаляются старым — повторно добавляем через 100ms.
224
+
225
+ ### `LightBoxContent.paddingTop` — вычисление по querySelector
226
+
227
+ ```ts
228
+ const controlsNode = document.querySelector(`[data-lightbox-component="controls"]`);
229
+ if (controlsNode) nextPaddingTop += controlsNode.offsetHeight;
230
+ ```
231
+
232
+ - Контент добавляет сверху padding равный высоте Controls-бара, чтобы они не перекрывали начало контента.
233
+ - Debounce 100ms, изменение применяется при `deltaTop > 1` — антидрожание.
234
+
235
+ ### `LightBoxSideOverlay` — разделение `opening`/`closing`/`opened`
236
+
237
+ ```ts
238
+ useEffect(() => {
239
+ if (prevOpened.current && !opened) { setOpening(false); setClosing(true); }
240
+ else if (!prevOpened.current && opened) { setClosing(false); setOpening(true); }
241
+ prevOpened.current = opened;
242
+ }, [opened]);
243
+ ```
244
+
245
+ - Tri-state: `opened` + `opening` (анимация раскрытия) + `closing` (анимация закрытия). `onOpen`/`onClose` срабатывают **после `transitionEnd`**, не сразу.
246
+ - FocusTrap активен только когда `opened && !opening && !closing`.
247
+
248
+ ### `LeftSidebar.onShow/onHide` через ref
249
+
250
+ Чтобы `ResizeObserver`-коллбэк не пересоздавался при смене обработчиков, они пишутся в `onShowRef`/`onHideRef` через `useEffect`. Паттерн "stable callback".
251
+
252
+ ---
253
+
254
+ ## Инварианты
255
+
256
+ - **`children: ReactElement[]`** — типом заявлен массив. Потребители фактически пишут слоты подряд — TypeScript нестрого проверяет. Не менять без переосмысления.
257
+ - **`data-lightbox-component="controls"`** атрибут на Controls-контейнере — **необходим** для работы `LightBoxContent` (querySelector). При замене на ref нужно править `LightBoxContent.tsx`.
258
+ - **IDs `LightBox-next-mount-node` и `LightBox-next-view-manager-node`** — константы в модуле, переименование — breaking.
259
+ - **`forwardRef` prop (а не `ref`)** — публичный API. LightBoxBase — не `forwardRef`; `Object.assign` поверх. Не переименовывать в `ref`.
260
+ - **`ELightBoxSize`** — собственный enum, не `EComponentSize`; есть значение `XL`.
261
+ - **Safari span `tempElSafariBug`** — не удалять без регрессии (см. DCBSWT-2866).
262
+ - **`setTimeout(100)` на body-lock** — костыль против роутерных гонок, не убирать.
263
+ - **`LightBox.SideOverlay.CloseDesktop`/`.CloseMobile`** — две отдельные кнопки, не одна. Mobile/desktop визуально разные.
264
+ - **`Portal` + `lightBoxMountNode`** — LightBox никогда не рендерится inline. При SSR потребителю нужен ClientOnly-wrapper.
265
+ - **`MobileView` fallback wraps FocusTrap, children — нет**: на mobile focus trap не активен.
266
+ - **`onOpen`/`onClose` SideOverlay** вызываются по `transitionend`, не по смене `opened`. Важно для консьюмеров, ждущих завершения анимации.
267
+
268
+ ---
269
+
270
+ ## Accessibility
271
+
272
+ - `role="dialog"` + `aria-modal="true"` — на корне LightBox и на SideOverlay.
273
+ - FocusTrap удерживает фокус внутри (desktop). Потребитель должен проставить tabbable-элементы и осмысленные labels.
274
+ - Initial focus — первый элемент с `data-focus-initial` (см. `FocusTrapUtils.getFirstInteractionElementByDataAttr`) или первый tabbable.
275
+ - `allowOutsideClick: true` — клик вне модалки не ломает trap (но и не закрывает — консьюмер сам вешает onClose).
276
+ - **Mobile — без focus trap'а**. Семантика диалога остаётся через `role="dialog"`, но скринридеры могут не удерживать фокус — закладывайте это для мобильных UX-тестов.
277
+ - Контент не анонсируется при открытии (нет `aria-labelledby`/`aria-describedby` по умолчанию) — потребитель должен добавить через `...rest`.
278
+
279
+ ---
280
+
281
+ ## Дизайн-токены
282
+
283
+ LightBoxViewManager прописывает **runtime CSS-переменные**:
284
+
285
+ ```
286
+ --lightBox-screen-left
287
+ --lightBox-screen-width
288
+ --lightBox-screen-top
289
+ ```
290
+
291
+ Их значения вычисляются по `getBoundingClientRect` у `lightBoxViewManagerNode`. Эти переменные используются LESS-стилями LightBox для позиционирования.
292
+
293
+ Прочие визуальные токены — от ThemeProvider (`scopeClassName` подставляется на корень).
294
+
295
+ ---
296
+
297
+ ## Связанные компоненты
298
+
299
+ - `ModalWindow` — простой диалог, не полноэкранный.
300
+ - `Overlay` / `OverlayBase` / `OverlayMask` — основа для SideOverlay.
301
+ - `TopOverlay` — верхняя выезжающая панель (реэкспортируется как `LightBox.TopOverlay`).
302
+ - `Portal` — используется для render'а.
303
+ - `MobileView` — разграничение desktop/mobile.
304
+ - `LoaderScreen` — loader внутри Content.
305
+ - `focus-trap-react` — FocusTrap.
306
+ - `react-resize-detector` — resize-listener в Content и ViewManager.
307
+
308
+ ---
309
+
310
+ ## Stories
311
+
312
+ `stories/LightBox/LightBox.stories.tsx`
313
+ `stories/LightBox/LightBoxInitialOpened.stories.tsx`
314
+ Примеры: `stories/LightBox/examples/`
315
+
316
+ ---
317
+
318
+ ## История изменений
319
+
320
+ | Дата | Изменение |
321
+ |---|---|
322
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,161 @@
1
+ ---
2
+ component: Link
3
+ category: Navigation
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [Button, IconWrapper, Typography]
7
+ tokens:
8
+ - --triplex-next-Link-Color
9
+ - --triplex-next-Link-Color-Hover
10
+ - --triplex-next-Link-Color-Active
11
+ stories: stories/Link/Link.stories.tsx
12
+ version: "1.0"
13
+ ---
14
+
15
+ # Link
16
+
17
+ ## Назначение
18
+
19
+ Инлайновая ссылка — `<a>` с дизайн-цветами бренда и поддержкой
20
+ **leading/trailing-контента** (иконки). Вставляет zero-width space
21
+ для корректного выравнивания иконки с текстом и умеет **привязывать
22
+ иконку к первому/последнему слову** многословной ссылки, чтобы иконка
23
+ не "отлетала" на новую строку при переносе.
24
+
25
+ Используй когда: нужна стандартная текстовая ссылка (в тексте, в списке).
26
+ Для стилизованной кнопки-CTA — `Button` с `type="link"`.
27
+
28
+ ---
29
+
30
+ ## Файловая структура
31
+
32
+ ```text
33
+ src/components/Link/
34
+ ├── Link.tsx # forwardRef<HTMLAnchorElement>
35
+ ├── index.ts # Barrel
36
+ ├── styles/Link.module.less
37
+ └── __tests__/Link.test.tsx
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Варианты и props
43
+
44
+ ### `ILinkCommonProps extends AnchorHTMLAttributes<HTMLAnchorElement>`
45
+
46
+ | Prop | Тип | Обязателен | Описание |
47
+ |---|---|---|---|
48
+ | `children` | `ReactNode` | Да | Текст или node. Для строк включается логика "иконка + первое/последнее слово". |
49
+ | `contentBefore` | `() => ReactElement` | Нет | Рендер-функция leading-контента (обычно иконка). |
50
+ | `contentAfter` | `() => ReactElement` | Нет | Рендер-функция trailing-контента. |
51
+ | `href`, `target`, `rel`, `onClick`, … | стандартные `<a>`-атрибуты | — | Передаются на `<a>`. |
52
+
53
+ **`contentBefore` / `contentAfter` — render-props**, не `ReactNode`. Функции вызываются каждый раз при рендере.
54
+
55
+ ---
56
+
57
+ ## Ключевые особенности реализации
58
+
59
+ ### Zero-width space перед leading-контентом
60
+
61
+ ```tsx
62
+ const renderContentBefore = () =>
63
+ contentBefore ? <>{"\u200B"}{contentBefore()}</> : null;
64
+ ```
65
+
66
+ - `"\u200B"` (ZWSP) — невидимый символ, используется для корректного **выравнивания по baseline**. Без него иконка "прыгает" относительно текста.
67
+ - Аналогичного для `contentAfter` нет — баланс достигается CSS-стилями.
68
+
69
+ ### Привязка иконки к первому/последнему слову
70
+
71
+ ```tsx
72
+ if (words.length < 2 || (words.length < 3 && contentBefore && contentAfter)) {
73
+ return <span>{before}{text}{after}</span>; // всё в одном span
74
+ }
75
+ // Иначе: первый + оставшиеся + последний слово как отдельные узлы
76
+ return <>{firstNode} {restWords} {lastNode}</>;
77
+ ```
78
+
79
+ - Для строковых `children` Link парсит текст по пробелам и **оборачивает первое/последнее слово** в `<span>` вместе с `contentBefore`/`contentAfter`.
80
+ - Это гарантирует, что иконка не разорвётся с соседним словом при word-wrap.
81
+ - Для нестроковых `children` — простая конкатенация: `{before}{node}{after}`.
82
+ - Edge case: `words.length < 2` (одно слово) или `< 3` при обоих before+after → всё в одном `<span>` (иначе нечего "разделять").
83
+
84
+ ### `IconWrapper displayContents`
85
+
86
+ ```tsx
87
+ <a>
88
+ <IconWrapper displayContents>{content}</IconWrapper>
89
+ </a>
90
+ ```
91
+
92
+ - Оборачивает **весь контент** в `IconWrapper` с `display: contents`, чтобы включить `hoverable`-класс для иконок в `contentBefore`/`contentAfter` (меняет `fill` при hover).
93
+ - `displayContents` нужен, чтобы обёртка не создавала свой box и не ломала inline-поток.
94
+
95
+ ### `data-tx={process.env.npm_package_version}` atribute
96
+
97
+ Версия библиотеки пишется в DOM — для дебага на production (можно узнать версию, просмотрев разметку). Этот атрибут стоит сохранить при изменениях.
98
+
99
+ ### `role="link"` на `<a>`
100
+
101
+ **Избыточно** — `<a href>` по умолчанию имеет role="link". Но без `href` роль теряется → `role="link"` делает компонент семантически корректным и для "кнопкоподобных" ссылок без href'а. Не удалять без рефакторинга.
102
+
103
+ ### `onBlur` / `onMouseDown` прокидываются отдельно
104
+
105
+ Остальные handler'ы идут через `...rest`, но эти два — отдельно. **Легаси** — можно убрать, упростив до `...rest`. При рефакторинге проверять storybook-примеры.
106
+
107
+ ---
108
+
109
+ ## Инварианты
110
+
111
+ - **`<a>` с `role="link"`** — не менять тег на `<span>`/`<button>` (нарушит screen reader'ов).
112
+ - **`contentBefore` / `contentAfter` — функции**, не nodes. Публичный API.
113
+ - **ZWSP перед leading-контентом** — не убирать без визуальной регрессии.
114
+ - **Строковые children обрабатываются специально** (первое/последнее слово). Не все потребители это знают; при рефакторинге поддерживать логику разрыва слов.
115
+ - **`IconWrapper displayContents`** — обязателен, обеспечивает `hoverable` для иконок внутри.
116
+ - **`data-tx` атрибут** — продакшн-маркер версии, не трогать.
117
+ - **`forwardRef<HTMLAnchorElement>`** — не `HTMLElement`.
118
+
119
+ ---
120
+
121
+ ## Accessibility
122
+
123
+ - `<a>` с `href` — полная клавиатурная навигация (Tab, Enter).
124
+ - `role="link"` — дублирующая роль, но нужна для `<a>` без href (interactive span-like ссылки).
125
+ - Иконки в `contentBefore`/`contentAfter` не имеют `aria-label` — потребитель должен передать нужные атрибуты через `contentBefore={() => <Icon aria-hidden="true" />}` (если декоративная) или осмысленный `aria-label` у иконки (если несёт смысл).
126
+ - `disabled`-состояние у `<a>` невозможно; для блокировки — `tabIndex={-1}` + CSS (нет в компоненте).
127
+
128
+ ---
129
+
130
+ ## Дизайн-токены
131
+
132
+ ```
133
+ --triplex-next-Link-Color # основной цвет
134
+ --triplex-next-Link-Color-Hover # hover
135
+ --triplex-next-Link-Color-Active # active
136
+ ```
137
+
138
+ (Названия могут отличаться — проверь `styles/Link.module.less`.)
139
+
140
+ ---
141
+
142
+ ## Связанные компоненты
143
+
144
+ - `IconWrapper` — обёртка для `hoverable`-стилей иконок (используется внутри).
145
+ - `Button` с `type="link"` — кнопка, стилизованная как ссылка.
146
+ - `Typography` — для текста без ссылочной функции.
147
+
148
+ ---
149
+
150
+ ## Stories
151
+
152
+ `stories/Link/Link.stories.tsx`
153
+ Примеры: `stories/Link/examples/`
154
+
155
+ ---
156
+
157
+ ## История изменений
158
+
159
+ | Дата | Изменение |
160
+ |---|---|
161
+ | 2026-04-18 | Создан документ |