@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,120 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: MobileView
|
|
3
|
+
category: Utilities
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [MediaWidth]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# MobileView
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Удобная обёртка над `MediaWidth maxWidth={SM_MAX}` — рендерит `children`
|
|
17
|
+
на мобиле (< 768px), иначе `fallback`. Фактически **именованный пресет**
|
|
18
|
+
для частого use-case'а mobile-vs-desktop.
|
|
19
|
+
|
|
20
|
+
Используй когда: нужно рендерить разные React-деревья для mobile/desktop.
|
|
21
|
+
Для стилей — CSS `@media` напрямую. Для более гибких границ —
|
|
22
|
+
`MediaWidth`/`MediaMinWidth`/`MediaMaxWidth`.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Файловая структура
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
src/components/MobileView/
|
|
30
|
+
├── MobileView.tsx # FC, 3 строки — обёртка над MediaWidth
|
|
31
|
+
└── index.ts # Barrel
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Нет стилей, тестов, stories.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Варианты и props
|
|
39
|
+
|
|
40
|
+
### `IMobileViewProps`
|
|
41
|
+
|
|
42
|
+
| Prop | Тип | Обязателен | Описание |
|
|
43
|
+
|---|---|---|---|
|
|
44
|
+
| `children` | `ReactElement \| null` | Да | Рендерится при ширине ≤ SM_MAX (767px). |
|
|
45
|
+
| `fallback` | `ReactElement \| null` | Да | Рендерится в остальных случаях (desktop). |
|
|
46
|
+
|
|
47
|
+
Обе props — обязательные. Следует контракту `MediaWidth`.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Ключевые особенности реализации
|
|
52
|
+
|
|
53
|
+
### Брейкпоинт — `EScreenWidth.SM_MAX`
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<MediaWidth maxWidth={EScreenWidth.SM_MAX} fallback={fallback}>
|
|
57
|
+
{children}
|
|
58
|
+
</MediaWidth>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `SM_MAX` обычно равен `767px` (проверь в `helpers/breakpoints`).
|
|
62
|
+
- **Фиксированный брейкпоинт** — если нужен другой (например, планшеты), используй `MediaWidth` с явным `maxWidth`, а не оборачивай MobileView.
|
|
63
|
+
|
|
64
|
+
### `children` на mobile, `fallback` на desktop
|
|
65
|
+
|
|
66
|
+
Контракт отличается от "обычных" компонентов: **`children` — мобильное поведение**, а не базовое. При чтении кода это бывает контринтуитивно. Пример:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<MobileView
|
|
70
|
+
fallback={<DesktopLayout />}
|
|
71
|
+
>
|
|
72
|
+
<MobileLayout />
|
|
73
|
+
</MobileView>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Не SSR-safe через `useMatchMedia` (см. MediaWidth-AI)
|
|
77
|
+
|
|
78
|
+
Поведение наследуется от `MediaWidth`: `window.innerWidth` читается при рендере.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Инварианты
|
|
83
|
+
|
|
84
|
+
- **Брейкпоинт `SM_MAX`** фиксирован — публичное поведение.
|
|
85
|
+
- **`children` — mobile, `fallback` — desktop** — не менять семантику без миграции потребителей.
|
|
86
|
+
- **Обе prop'ы обязательны** (`ReactElement | null`, не `ReactNode`, не optional).
|
|
87
|
+
- **FC, не forwardRef** — ref не пробрасывается.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Accessibility
|
|
92
|
+
|
|
93
|
+
Тот же нюанс, что и `MediaWidth`: разные React-деревья → смена DOM на брейкпоинте. Для стилистических различий предпочитай CSS.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Дизайн-токены
|
|
98
|
+
|
|
99
|
+
Нет.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Связанные компоненты
|
|
104
|
+
|
|
105
|
+
- `MediaWidth` — общая абстракция (MobileView — её конкретизация).
|
|
106
|
+
- `EScreenWidth.SM_MAX` — из `@sberbusiness/triplex-next/helpers/breakpoints`.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Stories
|
|
111
|
+
|
|
112
|
+
Нет.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## История изменений
|
|
117
|
+
|
|
118
|
+
| Дата | Изменение |
|
|
119
|
+
|---|---|
|
|
120
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: ModalWindow
|
|
3
|
+
category: Overlays
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [LightBox, Portal, Island, Page, TriggerClickOnKeyDownEvent, Button]
|
|
7
|
+
tokens:
|
|
8
|
+
- --modalWindow-screen-left
|
|
9
|
+
- --modalWindow-screen-width
|
|
10
|
+
- --modalWindow-screen-top
|
|
11
|
+
- --modal-window-top
|
|
12
|
+
stories: stories/ModalWindow/ModalWindow.stories.tsx
|
|
13
|
+
version: "1.0"
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# ModalWindow
|
|
17
|
+
|
|
18
|
+
## Назначение
|
|
19
|
+
|
|
20
|
+
Модальный диалог средних размеров — контент по центру экрана, backdrop-маска,
|
|
21
|
+
focus-trap, Escape-close, анимированное появление/исчезновение (CSSTransition).
|
|
22
|
+
Подкомпоненты: `Header` (на базе `HeaderPage`), `Body` (на базе `Island`),
|
|
23
|
+
`Footer` (на базе `FooterPage`), `Content` (scroll-container с Page внутри),
|
|
24
|
+
`Close` (Button с Escape-handler), и `ViewManager` (вычисляет CSS-переменные
|
|
25
|
+
позиционирования).
|
|
26
|
+
|
|
27
|
+
Используй когда: нужен стандартный диалог (подтверждение, мастер, форма).
|
|
28
|
+
Для полноэкранного — `LightBox`. Для поповера — `Overlay`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Файловая структура
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
src/components/ModalWindow/
|
|
36
|
+
├── ModalWindow.tsx # forwardRef, Portal + CSSTransition + FocusTrap
|
|
37
|
+
├── index.ts # Barrel всех подкомпонентов
|
|
38
|
+
├── components/
|
|
39
|
+
│ ├── ModalWindowHeader.tsx # Обёртка над HeaderPage type=FIRST, + .Title
|
|
40
|
+
│ ├── ModalWindowBody.tsx # Обёртка над Island TYPE_1, MD
|
|
41
|
+
│ ├── ModalWindowContent.tsx # <div> + <Page> + optional LoaderScreen
|
|
42
|
+
│ ├── ModalWindowFooter.tsx # Обёртка над FooterPage type=FIRST, + .Description
|
|
43
|
+
│ ├── ModalWindowClose.tsx # Button + TriggerClickOnKeyDownEvent(ESCAPE)
|
|
44
|
+
│ └── ModalWindowViewManager.tsx # CSS-переменные --modalWindow-screen-*
|
|
45
|
+
└── styles/*.module.less
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Compound-структура
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<ModalWindow
|
|
54
|
+
isOpen={isOpen}
|
|
55
|
+
closeButton={<ModalWindow.Close onClick={onClose} />}
|
|
56
|
+
size={EComponentSize.MD}
|
|
57
|
+
onExited={onAnimationClose}
|
|
58
|
+
>
|
|
59
|
+
<ModalWindow.Content isLoading={loading}>
|
|
60
|
+
<ModalWindow.Header>
|
|
61
|
+
<ModalWindow.Header.Title>Заголовок</ModalWindow.Header.Title>
|
|
62
|
+
</ModalWindow.Header>
|
|
63
|
+
|
|
64
|
+
<ModalWindow.Body>
|
|
65
|
+
Основной контент...
|
|
66
|
+
</ModalWindow.Body>
|
|
67
|
+
|
|
68
|
+
<ModalWindow.Footer>
|
|
69
|
+
<ModalWindow.Footer.Description>Подпись</ModalWindow.Footer.Description>
|
|
70
|
+
<Button>ОК</Button>
|
|
71
|
+
</ModalWindow.Footer>
|
|
72
|
+
</ModalWindow.Content>
|
|
73
|
+
</ModalWindow>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Важно:** сами подкомпоненты (`ModalWindowHeader`, `ModalWindowBody`,
|
|
77
|
+
`ModalWindowContent`, `ModalWindowFooter`, `ModalWindowClose`) экспортируются
|
|
78
|
+
из barrel, но **не навешаны** на `ModalWindow` через `Object.assign`. Нужно
|
|
79
|
+
импортировать их **именованно**: `import { ModalWindow, ModalWindowHeader } from ...`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Варианты и props
|
|
84
|
+
|
|
85
|
+
### ModalWindow — `IModalWindowProps extends HTMLAttributes<HTMLDivElement>`
|
|
86
|
+
|
|
87
|
+
| Prop | Тип | Default | Описание |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| `isOpen` | `boolean` | — (обязателен) | Управляемое состояние. |
|
|
90
|
+
| `children` | `ReactElement` | — | Один корневой элемент (обычно `ModalWindowContent`). |
|
|
91
|
+
| `closeButton` | `ReactNode` | — (обязателен) | Кнопка закрыть, вставляется рядом с `children`. Обычно `<ModalWindowClose onClick={onClose} />`. |
|
|
92
|
+
| `focusTrapProps` | `FocusTrapProps` | — | Переопределение параметров `focus-trap-react`. |
|
|
93
|
+
| `onExited` | `() => void` | — | Callback после завершения анимации закрытия. |
|
|
94
|
+
| `size` | `EComponentSize` | `MD` | Размер модалки (ширина). |
|
|
95
|
+
| `topPosition` | `number` | `100` | Отступ сверху в px. Пишется в CSS-переменную `--modal-window-top`. |
|
|
96
|
+
| `containerClassName` | `string` | — | className внешнего контейнера (с backdrop'ом). |
|
|
97
|
+
| `className`, `...HTMLDivAttributes` | — | — | Стандартные, идут на `div[role="dialog"]`. |
|
|
98
|
+
|
|
99
|
+
### ModalWindowClose — `IModalWindowCloseProps extends Omit<IButtonSecondaryProps, "size" | "theme" | "icon">`
|
|
100
|
+
|
|
101
|
+
- Кнопка с иконкой `CrossStrokeSrvIcon20` в правом-верхнем углу модалки.
|
|
102
|
+
- **Автоматически триггерит клик по Escape** через `TriggerClickOnKeyDownEvent` (eventKeyCode=ESCAPE).
|
|
103
|
+
- `size=MD` и `theme=SECONDARY` захардкожены — не переопределить.
|
|
104
|
+
- **Title "Закрыть"** — хардкод русского, i18n-debt.
|
|
105
|
+
|
|
106
|
+
### ModalWindowHeader — `IModalWindowHeaderProps extends Omit<IHeaderPageTypeFirstProps, "children" | "type">`
|
|
107
|
+
|
|
108
|
+
- Обёртка над `<HeaderPage type={FIRST}>`.
|
|
109
|
+
- Статический `.Title = HeaderPage.Title`.
|
|
110
|
+
|
|
111
|
+
### ModalWindowBody — `IModalWindowBodyProps extends IIslandProps`
|
|
112
|
+
|
|
113
|
+
- Обёртка над `<Island type={TYPE_1} size={MD}>` с дополнительным классом `modalWindowBody`.
|
|
114
|
+
- **Хардкод TYPE_1/MD** — если нужен другой тип/размер Island, текущий API не позволяет.
|
|
115
|
+
|
|
116
|
+
### ModalWindowContent — `IModalWindowContentProps extends HTMLAttributes<HTMLDivElement>`
|
|
117
|
+
|
|
118
|
+
| Prop | Тип | Описание |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `isLoading` | `boolean` | Показывает `LoaderScreen type="middle"` над содержимым. |
|
|
121
|
+
| `loadingTitle` | `ReactNode` | Подпись под спиннером. |
|
|
122
|
+
|
|
123
|
+
Внутри — обёртывает `children` в `<Page>`. Scroll-контейнер.
|
|
124
|
+
|
|
125
|
+
### ModalWindowFooter — `IModalWindowFooterProps extends Omit<IFooterPageTypeFirstProps, "children" | "type">`
|
|
126
|
+
|
|
127
|
+
- Обёртка над `<FooterPage type={FIRST}>`.
|
|
128
|
+
- Статический `.Description = FooterPage.Description`.
|
|
129
|
+
|
|
130
|
+
### ModalWindowViewManager
|
|
131
|
+
|
|
132
|
+
Рендерится автоматически внутри `ModalWindow`. Отдельно использовать не нужно (но экспортирован для nuance-сценариев).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Ключевые особенности реализации
|
|
137
|
+
|
|
138
|
+
### Два DOM-контейнера: `#ufs-modal-window-wrapper` + свой `portal-node`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
let wrapperNode = document.querySelector<HTMLDivElement>(`#${modalNodeName}-wrapper`);
|
|
142
|
+
if (!wrapperNode) { /* create & append to body */ }
|
|
143
|
+
|
|
144
|
+
mountNode.current = document.createElement("div");
|
|
145
|
+
mountNode.current.className = `${modalNodeName}-portal-node`;
|
|
146
|
+
wrapperNode.appendChild(mountNode.current);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- **Каждая открытая ModalWindow создаёт свой `.portal-node`** внутри общего `#ufs-modal-window-wrapper`. Это позволяет держать несколько модалок параллельно (хотя обычно одна).
|
|
150
|
+
- При закрытии portal-node удаляется, wrapper — остаётся (для повторного использования).
|
|
151
|
+
- **ID wrapper'а `ufs-modal-window-wrapper`** — известная "точка привязки" для интеграции с Layout'ом (сббол).
|
|
152
|
+
|
|
153
|
+
### Body-классы `modal-open` + `no-hash-overflow-hidden`
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const bodyClassNameModalOpen = ["modal-open", "no-hash-overflow-hidden"];
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
- При открытии — **добавляются на `body`** (не на `documentElement`, в отличие от LightBox).
|
|
160
|
+
- Класс `modal-open` — от внешнего Layout (сббол), блюрит фоновый контент.
|
|
161
|
+
- Снимаются при закрытии/unmount.
|
|
162
|
+
|
|
163
|
+
### Animation — `CSSTransition` + `appear` + `unmountOnExit`
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<CSSTransition
|
|
167
|
+
in={isOpen}
|
|
168
|
+
timeout={animationExitTime} // 300
|
|
169
|
+
classNames="global-modalWindowTransition"
|
|
170
|
+
appear enter exit
|
|
171
|
+
onEnter={handleOpenModal} // добавить body-классы
|
|
172
|
+
onExited={handleCloseModal} // удалить body-классы + unmount portal + onExited()
|
|
173
|
+
mountOnEnter
|
|
174
|
+
unmountOnExit
|
|
175
|
+
>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **classNames `global-modalWindowTransition`** — строка, не CSS-Modules. Соответствующие CSS-правила живут в глобальных стилях/index.less.
|
|
179
|
+
- `appear` — чтобы `onEnter` сработал на самой первой анимации.
|
|
180
|
+
- `unmountOnExit` — DOM-элемент удаляется после анимации, `renderPortal` сбрасывается в false.
|
|
181
|
+
|
|
182
|
+
### `renderPortal` state и двойное монтирование
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
const [renderPortal, setRenderPortal] = useState(false);
|
|
186
|
+
useEffect(() => { if (isOpen) { mountPortalNode(); setRenderPortal(true); } }, [isOpen]);
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- До открытия `renderPortal=false` → ранний return `null` → Portal не рендерится.
|
|
190
|
+
- При `isOpen=true` — создаётся mountNode (imperatively), ставится `renderPortal=true` — затем react рендерит Portal.
|
|
191
|
+
- **На повторные открытия** — state остаётся true после закрытия (handleCloseModal сбрасывает в false), и при следующем `isOpen=true` — снова создаётся mountNode.
|
|
192
|
+
|
|
193
|
+
### `topPosition` через CSS-переменную
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
const topPositionStyle = { "--modal-window-top": `${topPosition}px` };
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- Default 100px. Команды могут переопределять, передавая `topPosition`.
|
|
200
|
+
- CSS-переменная именно `--modal-window-top` (kebab с дефисами), **не в naming-convention `--triplex-next-*`**. Это исторический паттерн, не переименовывать.
|
|
201
|
+
|
|
202
|
+
### ModalWindowClose с Escape-handler
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
<TriggerClickOnKeyDownEvent eventKeyCode={EVENT_KEY_CODES.ESCAPE} targetRef={ref}>
|
|
206
|
+
<Button ref={ref} icon={<CrossIcon />} ... />
|
|
207
|
+
</TriggerClickOnKeyDownEvent>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- Escape нажатие → имитирует click на кнопку → `onClick` → закрытие.
|
|
211
|
+
- Кнопка **обязательно должна быть в дереве** (не опциональна). Если `closeButton` не передан — TS ошибка (prop обязателен).
|
|
212
|
+
|
|
213
|
+
### `ModalWindowViewManager` — CSS-переменные позиционирования
|
|
214
|
+
|
|
215
|
+
Аналогично `LightBoxViewManager`: пишет в `:root` (именно в `:root`, а не на `lightBoxMountNode`):
|
|
216
|
+
|
|
217
|
+
```css
|
|
218
|
+
:root {
|
|
219
|
+
--modalWindow-screen-left: ...;
|
|
220
|
+
--modalWindow-screen-width: ...;
|
|
221
|
+
--modalWindow-screen-top: ...;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- Default id `modalWindowViewManagerNodeId` создаётся в body. Используется для позиционирования относительно окна (если есть глобальный sidebar, Layout может переопределить эту node).
|
|
226
|
+
- **Отличие от LightBox**: переменные в `:root` — глобальные; любая ModalWindow подстраивается.
|
|
227
|
+
|
|
228
|
+
### `focus-trap-react` v11 (используется `FocusTrap` как default, не `.FocusTrap`)
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { FocusTrap, FocusTrapProps } from "focus-trap-react";
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
**Внимание**: старые версии использовали default-import. Не ломать.
|
|
235
|
+
|
|
236
|
+
### ModalWindowBody хардкодит Island type=TYPE_1, size=MD
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
<Island type={EIslandType.TYPE_1} size={EComponentSize.MD} {...islandProps}>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`...islandProps` идут **после** `type`/`size` — потребитель теоретически может переопределить через props. Но это не публичная часть API (not документированная). Не полагайся.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Инварианты
|
|
247
|
+
|
|
248
|
+
- **`isOpen` управляемый, `children` — один `ReactElement`, `closeButton` — обязателен** — публичный контракт.
|
|
249
|
+
- **ID DOM-контейнеров: `ufs-modal-window-wrapper`, `modalWindowViewManagerNodeId`** — **интеграционные** точки с внешним Layout'ом. Не переименовывать.
|
|
250
|
+
- **Body-классы `modal-open`, `no-hash-overflow-hidden`** — контракт с внешним Layout; изменение — регрессия фонового блюра.
|
|
251
|
+
- **Escape → closeButton click** через `TriggerClickOnKeyDownEvent` — поведенческий контракт `ModalWindowClose`.
|
|
252
|
+
- **CSS-переменная `--modal-window-top`** (kebab-case, не `--triplex-next-ModalWindow-Top`) — публичная, исторический naming.
|
|
253
|
+
- **`CSSTransition classNames="global-modalWindowTransition"`** — строковый класс, нет в CSS-Modules. Стили в глобальном файле.
|
|
254
|
+
- **`ModalWindowHeader/Footer` — обёртки над HeaderPage/FooterPage type=FIRST** — не поддерживают TYPE_2/TYPE_3.
|
|
255
|
+
- **`ModalWindowBody` хардкодит Island TYPE_1/MD** — кастомизация через props не публична.
|
|
256
|
+
- **`ModalWindow.Close` title="Закрыть" + `TriggerClickOnKeyDownEvent` eventKeyCode=ESCAPE** — i18n-debt (rus-hardcode).
|
|
257
|
+
- **`forwardRef<HTMLDivElement>`** — корневая node (dialog), но **подкомпоненты не являются слотами через Object.assign**.
|
|
258
|
+
- **ViewManager пишет в `:root`**, не в scoped-элемент — глобально влияет на любой ModalWindow.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Accessibility
|
|
263
|
+
|
|
264
|
+
- `role="dialog"` + `aria-modal="true"` — есть на корне.
|
|
265
|
+
- `FocusTrap` активен пока `isOpen` — фокус удерживается внутри.
|
|
266
|
+
- `allowOutsideClick: true` — outside click не ломает trap; закрытие делается **только через `closeButton`** или Escape.
|
|
267
|
+
- **Нет `aria-labelledby`/`aria-describedby` по умолчанию** — потребитель должен добавить через `...rest` или передать `aria-label`.
|
|
268
|
+
- `ModalWindowClose` — Button с `title="Закрыть"` (рус), tabIndex нормальный.
|
|
269
|
+
- Escape обрабатывается через TriggerClickOnKeyDownEvent — глобально на window (см. `KeyDownListener-AI`).
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Дизайн-токены
|
|
274
|
+
|
|
275
|
+
Runtime CSS-переменные:
|
|
276
|
+
```
|
|
277
|
+
--modalWindow-screen-left, --modalWindow-screen-width, --modalWindow-screen-top — от ViewManager
|
|
278
|
+
--modal-window-top — от topPosition prop
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Визуал — от `Island` (внутри Body) и ThemeProvider scope (scopeClassName на контейнере).
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Связанные компоненты
|
|
286
|
+
|
|
287
|
+
- `LightBox` — полноэкранный аналог.
|
|
288
|
+
- `Portal` — используется для render'а.
|
|
289
|
+
- `Island` / `Page` / `HeaderPage` / `FooterPage` — базовые примитивы для Body/Content/Header/Footer.
|
|
290
|
+
- `Button` — в ModalWindowClose.
|
|
291
|
+
- `TriggerClickOnKeyDownEvent` — для Escape handling.
|
|
292
|
+
- `LoaderScreen` — loader в Content.
|
|
293
|
+
- `focus-trap-react`, `react-transition-group` — внешние зависимости.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Stories
|
|
298
|
+
|
|
299
|
+
`stories/ModalWindow/ModalWindow.stories.tsx`
|
|
300
|
+
Примеры: `stories/ModalWindow/examples/`
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## История изменений
|
|
305
|
+
|
|
306
|
+
| Дата | Изменение |
|
|
307
|
+
|---|---|
|
|
308
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: MonthYearField
|
|
3
|
+
category: Form Controls
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [DatePickerExtended, Calendar, TextField, DropdownMobileInput, FormField]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/MonthYearField/MonthYearField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# MonthYearField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Поле для выбора **месяца и года** (без числа дня). Built on top of
|
|
17
|
+
`DatePickerExtended` c `pickType=monthYearPick`. Превращает строковое
|
|
18
|
+
значение ISO-даты (YYYY-MM-DD) в отображение "Mar 2026" в `TextField`,
|
|
19
|
+
показывает календарь по клику / Enter / Space, реализует clear-кнопку.
|
|
20
|
+
|
|
21
|
+
Используй когда: нужен month-picker (отчётные периоды, выбор срока).
|
|
22
|
+
Для полноценной даты — `DateField`. Для диапазона — `DateRange`/аналог.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Файловая структура
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
src/components/MonthYearField/
|
|
30
|
+
├── MonthYearField.tsx # forwardRef, обёртка над DatePickerExtended
|
|
31
|
+
├── MonthYearFieldTarget.tsx # TextField с клик/keyboard → открытие dropdown
|
|
32
|
+
├── MonthYearFieldContext.tsx # { onChange } — для clear в Target
|
|
33
|
+
├── constants.ts # inputMonthYearFormat = "MMM YYYY"
|
|
34
|
+
├── types.ts # IMonthYearFieldProps
|
|
35
|
+
├── utils.ts # MonthYearPickerUtils: parse/validate даты
|
|
36
|
+
├── index.ts # Barrel
|
|
37
|
+
└── __tests__/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Варианты и props
|
|
43
|
+
|
|
44
|
+
### `IMonthYearFieldProps extends Omit<IDatePickerExtendedProps, "pickedDate" | "onDateChange" | "renderTarget" | "renderDropdownHeaderTarget">, Pick<ITextFieldProps, "size" | "status" | "label">`
|
|
45
|
+
|
|
46
|
+
| Prop | Тип | Default | Описание |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| `value` | `string` | — (обязателен) | ISO-дата (формата `format`), например `"2026-04-01"`. |
|
|
49
|
+
| `onChange` | `(value: string) => void` | — (обязателен) | Вызывается при выборе нового месяца. Передаётся дата в `format`. |
|
|
50
|
+
| `format` | `string` | `dateFormatYYYYMMDD` (`"YYYY-MM-DD"`) | moment-формат для `value`. |
|
|
51
|
+
| `limitRange` | `IDateLimitRange` | `globalLimitRange` | Границы доступных дат. |
|
|
52
|
+
| `placeholder` | `string` | — | Плейсхолдер input'а. |
|
|
53
|
+
| `size` | `EComponentSize` | — | Пробрасывается в TextField. |
|
|
54
|
+
| `status` | `EFormFieldStatus` | — | Пробрасывается в TextField (`DISABLED` → readOnly). |
|
|
55
|
+
| `label` | `ReactNode` | — | Label TextField'а. |
|
|
56
|
+
| `targetProps` | `ITextFieldProps` | — | Дополнительные props TextField. `inputProps` мержатся с внутренними. |
|
|
57
|
+
| `...DatePickerExtended-props` | — | — | Все остальные, кроме исключённых. |
|
|
58
|
+
|
|
59
|
+
**Исключены из `IDatePickerExtendedProps`**: `pickedDate`, `onDateChange`, `renderTarget`, `renderDropdownHeaderTarget` — они управляются внутренне.
|
|
60
|
+
|
|
61
|
+
### Внутренние формирующие константы
|
|
62
|
+
|
|
63
|
+
- `inputMonthYearFormat = "MMM YYYY"` — как показывается в TextField (moment-format).
|
|
64
|
+
- `ECalendarPickType.monthYearPick` — `pickType` DatePickerExtended.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Ключевые особенности реализации
|
|
69
|
+
|
|
70
|
+
### Внутренний state `pickerValues`
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
const [pickerValues, setPickerValues] = useState(
|
|
74
|
+
MonthYearPickerUtils.getPickerValues(value, format, limitRange)
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `pickerValues = { inputString: "Mar 2026", calendarDate: moment }` — два представления одной даты.
|
|
79
|
+
- Синхронизируется с `value` через `useEffect(() => { if (newInputString !== current) setPickerValues(...) })` — обновление только при реальном изменении строки.
|
|
80
|
+
|
|
81
|
+
### `MonthYearPickerUtils.getPickerValues`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
if (value.length === 0) return { calendarDate: null, inputString: "" };
|
|
85
|
+
const date = moment(value, format, true); // strict
|
|
86
|
+
if (!valid || outsideRange) return { calendarDate: null, inputString: "" };
|
|
87
|
+
return { calendarDate: date, inputString: date.format(inputMonthYearFormat) };
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- **Strict-парсинг** (третий аргумент `true`) — формат должен совпадать один-в-один.
|
|
91
|
+
- Выход за `limitRange` → `null` calendar date + пустая строка в input'е. **Невалидное значение воспринимается как отсутствующее** — потребитель не получит обратный callback.
|
|
92
|
+
|
|
93
|
+
### `MonthYearFieldContext` — только для `onChange`
|
|
94
|
+
|
|
95
|
+
Нужен, чтобы `FormFieldClear` (внутри Target'а) мог вызвать `onChange("")` без prop-drilling через `renderTarget`.
|
|
96
|
+
|
|
97
|
+
### `MonthYearFieldTarget` — Enter/Space → открыть dropdown
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
handleInputKeyDown: isKey(event.code, "ENTER") → toggleDropdown;
|
|
101
|
+
isKey(event.code, "SPACE") → preventDefault + toggleDropdown;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- Клик по input'у — тоже toggleDropdown.
|
|
105
|
+
- Input всегда `readOnly` (выбор только через календарь).
|
|
106
|
+
- В postfix'е — `FormFieldClear` + `ButtonIcon` с иконкой календаря. Иконка меняется по `size`: `CalendarStrokeSrvIcon16` (SM) / `CalendarStrokeSrvIcon20` (MD/LG).
|
|
107
|
+
|
|
108
|
+
### Fallback на `dateFormatYYYYMMDD` и `globalLimitRange`
|
|
109
|
+
|
|
110
|
+
Если потребитель не передал `format`/`limitRange`, используются глобальные из `consts/DateConst`. Ожидается, что сам проект задаёт эти константы.
|
|
111
|
+
|
|
112
|
+
### `DropdownMobileInput` как header-target для мобильного dropdown'а
|
|
113
|
+
|
|
114
|
+
На mobile (в `DatePickerExtended`) вместо TextField'а рендерится упрощённый input.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Инварианты
|
|
119
|
+
|
|
120
|
+
- **`value: string` в `format`** — контракт. Не переключать на `Date`/`moment.Moment` без миграции.
|
|
121
|
+
- **Strict moment-parsing** — пустая/невалидная строка → `""` в UI. Потребитель должен синхронизировать невалидные значения сам.
|
|
122
|
+
- **`limitRange` — hard-clamp**: дата вне диапазона → пустое значение.
|
|
123
|
+
- **`MonthYearFieldContext` shape** (`{ onChange }`) — публичная точка расширения, не менять.
|
|
124
|
+
- **`inputMonthYearFormat = "MMM YYYY"`** — display-формат, не i18n-аизрован (moment уже локализован через global moment.locale, но формат-строка фиксирована).
|
|
125
|
+
- **`pickType=monthYearPick`** — контракт с DatePickerExtended. Изменение — breaking.
|
|
126
|
+
- **`targetProps.inputProps` мержатся** с внутренним `{ value, placeholder, readOnly }` — потребитель может переопределить.
|
|
127
|
+
- **ForwardRef<HTMLDivElement>** на корневую node DatePickerExtended.
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Accessibility
|
|
132
|
+
|
|
133
|
+
- Input `readOnly` — фокусируется, но не редактируется. Открытие: Enter / Space / click.
|
|
134
|
+
- **`<button>` с иконкой** (ButtonIcon) — `role="presentation"` и `tabIndex={-1}` — **не в tab-последовательности** (основной target — input). Дизайн-решение, чтобы не дублировать Tab-остановки.
|
|
135
|
+
- `FormFieldClear` — стандартная кнопка очистки (см. FormField-AI).
|
|
136
|
+
- Нет `aria-label` на иконке календаря — потребитель должен добавить через `targetProps` при необходимости.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Дизайн-токены
|
|
141
|
+
|
|
142
|
+
Нет собственных — наследуются от `TextField` и `Calendar`.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Связанные компоненты
|
|
147
|
+
|
|
148
|
+
- `DatePickerExtended` — основа логики (dropdown, popup, mobile-variant).
|
|
149
|
+
- `Calendar` — сам календарь (`pickType=monthYearPick` — режим месяца).
|
|
150
|
+
- `TextField` — визуальный target.
|
|
151
|
+
- `FormField.Clear` — кнопка очистки.
|
|
152
|
+
- `ButtonIcon` — кнопка календаря в postfix.
|
|
153
|
+
- `moment.js` — парсинг/форматирование (peerDependency в библиотеке).
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Stories
|
|
158
|
+
|
|
159
|
+
`stories/MonthYearField/MonthYearField.stories.tsx`
|
|
160
|
+
Примеры: `stories/MonthYearField/examples/`
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## История изменений
|
|
165
|
+
|
|
166
|
+
| Дата | Изменение |
|
|
167
|
+
|---|---|
|
|
168
|
+
| 2026-04-18 | Создан документ |
|