@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.
- package/README.md +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- 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 | Создан документ |
|