@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,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: MultiselectField
|
|
3
|
+
category: Form Controls
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [SelectExtendedField, SelectField, Dropdown, Checkbox, FormField]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/MultiselectField/MultiselectField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# MultiselectField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Контейнер для **мульти-выбора** из списка: `<SelectExtendedField>` с
|
|
17
|
+
`<MultiselectField.Dropdown>` внутри. Сам компонент — **тонкая обёртка**,
|
|
18
|
+
прокидывающая `size` через `MultiselectFieldContext` к дочерним слотам
|
|
19
|
+
(Header/Content/Footer). Список опций (чекбоксы), кнопки применения,
|
|
20
|
+
заголовок с поиском — реализуются потребителем в Header/Content/Footer.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужен выбор нескольких элементов с собственным dropdown'ом
|
|
23
|
+
и UI-контролами (поиск, "выбрать все", apply). Для одиночного выбора —
|
|
24
|
+
`SelectField`. Для более гибкого API — `SelectExtendedField` напрямую.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/MultiselectField/
|
|
32
|
+
├── MultiselectField.tsx # forwardRef, обёртка + Object.assign
|
|
33
|
+
├── MultiselectFieldContext.ts # { size }
|
|
34
|
+
├── index.ts # Barrel (только MultiselectField)
|
|
35
|
+
├── components/
|
|
36
|
+
│ ├── MultiselectFieldDropdown.tsx # Dropdown + FocusTrap + Object.assign
|
|
37
|
+
│ ├── MultiselectFieldDropdownHeader.tsx # <div>
|
|
38
|
+
│ ├── MultiselectFieldDropdownContent.tsx # <div> + опц. LoaderScreen
|
|
39
|
+
│ ├── MultiselectFieldDropdownFooter.tsx # <div>
|
|
40
|
+
│ └── index.ts
|
|
41
|
+
└── styles/*.module.less
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Compound-структура
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<MultiselectField
|
|
50
|
+
size={EComponentSize.MD}
|
|
51
|
+
opened={opened}
|
|
52
|
+
onOpenedChange={setOpened}
|
|
53
|
+
>
|
|
54
|
+
<MultiselectField.Target>
|
|
55
|
+
{/* Target — реэкспорт SelectExtendedFieldTarget */}
|
|
56
|
+
</MultiselectField.Target>
|
|
57
|
+
|
|
58
|
+
<MultiselectField.Dropdown>
|
|
59
|
+
<MultiselectField.Dropdown.Header>
|
|
60
|
+
{/* Поиск, "Выбрать все" */}
|
|
61
|
+
</MultiselectField.Dropdown.Header>
|
|
62
|
+
|
|
63
|
+
<MultiselectField.Dropdown.Content loading={loading}>
|
|
64
|
+
{items.map((it) => (
|
|
65
|
+
<Checkbox key={it.id} checked={...} onChange={...}>
|
|
66
|
+
{it.label}
|
|
67
|
+
</Checkbox>
|
|
68
|
+
))}
|
|
69
|
+
</MultiselectField.Dropdown.Content>
|
|
70
|
+
|
|
71
|
+
<MultiselectField.Dropdown.Footer>
|
|
72
|
+
<Button>Применить</Button>
|
|
73
|
+
</MultiselectField.Dropdown.Footer>
|
|
74
|
+
</MultiselectField.Dropdown>
|
|
75
|
+
</MultiselectField>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`MultiselectField.Target` **= реэкспорт** `SelectExtendedFieldTarget` (не собственный компонент). Используй как псевдоним.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Варианты и props
|
|
83
|
+
|
|
84
|
+
### MultiselectField — `IMultiselectFieldProps extends ISelectExtendedFieldProps`
|
|
85
|
+
|
|
86
|
+
| Prop | Тип | Default | Описание |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `size` | `EComponentSize` | `MD` | Пишется в `MultiselectFieldContext` и пробрасывается в Dropdown. |
|
|
89
|
+
| `...ISelectExtendedFieldProps` | — | — | Всё поведение outer-контейнера (opened, onOpenedChange, targetRef, …) от SelectExtendedField. |
|
|
90
|
+
|
|
91
|
+
### MultiselectField.Dropdown — `IMultiselectFieldDropdownProps extends IDropdownProps`
|
|
92
|
+
|
|
93
|
+
| Prop | Тип | Default | Описание |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `focusTrapProps` | `FocusTrapProps` | — | Переопределение `focus-trap-react`. |
|
|
96
|
+
| `opened`, `targetRef`, `mobileViewProps`, `...IDropdownProps` | — | — | От `Dropdown`. |
|
|
97
|
+
|
|
98
|
+
- **`width` захардкожен `EDropdownWidth.MIN_TARGET`** — dropdown всегда минимум как target. Не переопределяется.
|
|
99
|
+
- **`size` берётся из `MultiselectFieldContext`**, не от prop'а.
|
|
100
|
+
|
|
101
|
+
### MultiselectField.Dropdown.Header / Footer / Content — `HTMLAttributes<HTMLDivElement>`
|
|
102
|
+
|
|
103
|
+
- Header / Footer — простые `<div>`-слоты с собственной стилизацией (padding, border).
|
|
104
|
+
- Content — `<div tabIndex={-1}>` с `sizeToClassNameMap[size]` для подстройки (scroll height, padding). Имеет prop **`loading: boolean`** — при true показывает `LoaderScreen type="small"` поверх контента.
|
|
105
|
+
|
|
106
|
+
### MultiselectFieldContext
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
interface IMultiselectFieldContext { size: EComponentSize; }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Публичная точка расширения: при создании кастомных элементов в Content можно читать `size` и адаптировать под него.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Ключевые особенности реализации
|
|
117
|
+
|
|
118
|
+
### `size` — через Context, не через prop на Dropdown
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<MultiselectFieldContext.Provider value={{ size }}>
|
|
122
|
+
<SelectExtendedField {...props}>{children}</SelectExtendedField>
|
|
123
|
+
</MultiselectFieldContext.Provider>
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- Dropdown и Content читают `size` из контекста.
|
|
127
|
+
- **Dropdown переопределяет** `size` prop на Dropdown: `<Dropdown ... size={size} />`. То есть потребительский `size` на Dropdown **игнорируется** — всегда выигрывает context.
|
|
128
|
+
|
|
129
|
+
### `FocusTrap` внутри Dropdown
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<FocusTrap focusTrapOptions={{ clickOutsideDeactivates: true, preventScroll: true, ... }}>
|
|
133
|
+
<div role="presentation">{children}</div>
|
|
134
|
+
</FocusTrap>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `clickOutsideDeactivates: true` — клик вне dropdown'а деактивирует trap и закрывает (стандарт для popover/selects).
|
|
138
|
+
- Все потомки оборачиваются в `<div role="presentation">` — требование FocusTrap (нужен single wrapper-element).
|
|
139
|
+
|
|
140
|
+
### `MultiselectField.Target = SelectExtendedFieldTarget`
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
Object.assign(..., { Dropdown, Target: SelectExtendedFieldTarget })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **Это не собственная реализация**, а пересвет. Потребитель видит `.Target` в типах, но фактически это SelectExtendedField's target.
|
|
147
|
+
|
|
148
|
+
### Content `tabIndex={-1}`
|
|
149
|
+
|
|
150
|
+
Чтобы скролл внутри Content был фокусируемым мышью/колесиком (и при этом не участвовал в Tab-последовательности). Для переключения между чекбоксами — TAB идёт по интерактивным детям.
|
|
151
|
+
|
|
152
|
+
### Loader — `LoaderScreen type="small"`
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
{loading && <LoaderScreen type="small" className={styles.loaderScreen} />}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- Маленький оверлей (см. LoaderScreen-AI).
|
|
159
|
+
- Visibility — потребитель контролирует `loading` prop.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Инварианты
|
|
164
|
+
|
|
165
|
+
- **`size` через `MultiselectFieldContext`** — публичная точка расширения; не заменять на prop-drilling без решения по обратной совместимости.
|
|
166
|
+
- **`MultiselectField.Target = SelectExtendedFieldTarget`** — псевдоним, не своя реализация. При рефакторинге SelectExtendedField — проверять, что Target совместим.
|
|
167
|
+
- **`EDropdownWidth.MIN_TARGET`** захардкожен — ширина dropdown'а всегда ≥ ширины target'а.
|
|
168
|
+
- **`FocusTrap clickOutsideDeactivates: true`** — контракт: клик вне → закрытие. При смене — потребуется явное `onOpenedChange`.
|
|
169
|
+
- **`MultiselectFieldContext.size` default `MD`** — не менять без breaking-проверки.
|
|
170
|
+
- **`Content.loading`** — контроль loader'а, типичное API.
|
|
171
|
+
- **ForwardRef** на корне `MultiselectField` и на `MultiselectFieldDropdown`.
|
|
172
|
+
- **Header/Footer/Content — `React.FC`** (без forwardRef).
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Accessibility
|
|
177
|
+
|
|
178
|
+
- FocusTrap удерживает фокус внутри dropdown'а → клавиатурная навигация между чекбоксами/кнопками корректна.
|
|
179
|
+
- `role="presentation"` на wrapper'е FocusTrap — правильно: обёртка не несёт семантики.
|
|
180
|
+
- **Нет `aria-multiselectable="true"` на dropdown'е** — потребителям для скринридеров имеет смысл добавить через `...rest` на Content / Dropdown.
|
|
181
|
+
- Чекбоксы внутри Content — стандартный `<Checkbox>` с нативной keyboard-семантикой.
|
|
182
|
+
- Target — `SelectExtendedFieldTarget` обеспечивает `aria-expanded` / `aria-haspopup="listbox"` (проверь в SelectExtendedField, если не — гэп).
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Дизайн-токены
|
|
187
|
+
|
|
188
|
+
Нет собственных. Размеры/padding/цвет от `Dropdown`, `SelectExtendedField`, `Checkbox`.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Связанные компоненты
|
|
193
|
+
|
|
194
|
+
- `SelectExtendedField` / `SelectExtendedFieldTarget` — основа.
|
|
195
|
+
- `Dropdown` — popover-container.
|
|
196
|
+
- `Checkbox` — типичный элемент в Content.
|
|
197
|
+
- `LoaderScreen` — loader в Content.
|
|
198
|
+
- `focus-trap-react` — FocusTrap.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Stories
|
|
203
|
+
|
|
204
|
+
`stories/MultiselectField/MultiselectField.stories.tsx`
|
|
205
|
+
Примеры: `stories/MultiselectField/examples/`
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## История изменений
|
|
210
|
+
|
|
211
|
+
| Дата | Изменение |
|
|
212
|
+
|---|---|
|
|
213
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Notification
|
|
3
|
+
category: Feedback
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [SideOverlay, Alert, UnorderedList, ButtonIcon, Typography]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/Notification/Notification.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Notification
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Карточка уведомления — `<div role="alertdialog">` с фиксированной
|
|
17
|
+
compound-структурой: `Icon` (опц.), `Body` (Header / Content / List /
|
|
18
|
+
Footer), `Close` (опц.), `Time` (опц.). Содержательная часть собирается
|
|
19
|
+
потребителем; компонент задаёт лэйаут, типографику и поведение close-by-
|
|
20
|
+
hover для side-overlay.
|
|
21
|
+
|
|
22
|
+
`NotificationGrouped` — визуальная группировка (стопка) — оборачивает
|
|
23
|
+
одну Notification и подкладывает два декоративных `<div>` снизу как
|
|
24
|
+
эффект «стопки карточек» (без логики управления списком).
|
|
25
|
+
|
|
26
|
+
Используй когда: показываешь информационное / системное сообщение
|
|
27
|
+
(в SideOverlay, Alert, отдельной странице, history-list).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Файловая структура
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
src/components/Notification/
|
|
35
|
+
├── Notification.tsx # FC (как INotificationSFC) + slots
|
|
36
|
+
├── NotificationGrouped.tsx # FC, обёртка + декоративный footer
|
|
37
|
+
├── components/
|
|
38
|
+
│ ├── NotificationIcon.tsx # <div> wrapper для иконки
|
|
39
|
+
│ ├── NotificationBody.tsx # FC (как INotificationBodySFC) + slots
|
|
40
|
+
│ ├── NotificationHeader.tsx # <h3>
|
|
41
|
+
│ ├── NotificationBodyContent.tsx # <Text size=B3 type=PRIMARY tag=div>
|
|
42
|
+
│ ├── NotificationBodyList.tsx # <UnorderedList> + map по values[]
|
|
43
|
+
│ ├── NotificationFooter.tsx # <div>
|
|
44
|
+
│ ├── NotificationClose.tsx # <ButtonIcon> с CrossStrokeSrvIcon16
|
|
45
|
+
│ ├── NotificationTime.tsx # <span> с временем
|
|
46
|
+
│ └── NotificationGroupedFooter.tsx # 2 декоративных <div>
|
|
47
|
+
├── index.ts # Barrel: Notification + NotificationGrouped
|
|
48
|
+
├── styles/Notification.module.less
|
|
49
|
+
└── __tests__/Notification.test.tsx
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Compound-структура
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Notification onClick={...} isShowCloseOnHover>
|
|
58
|
+
<Notification.Icon><MailIcon /></Notification.Icon>
|
|
59
|
+
<Notification.Body>
|
|
60
|
+
<Notification.Body.Header>Заголовок</Notification.Body.Header>
|
|
61
|
+
<Notification.Body.Content>Текст сообщения</Notification.Body.Content>
|
|
62
|
+
<Notification.Body.List values={["Один", "Два"]} />
|
|
63
|
+
<Notification.Body.Footer>
|
|
64
|
+
<Button>Действие</Button>
|
|
65
|
+
</Notification.Body.Footer>
|
|
66
|
+
</Notification.Body>
|
|
67
|
+
<Notification.Close onClick={handleClose} />
|
|
68
|
+
<Notification.Time time="12:34" />
|
|
69
|
+
</Notification>
|
|
70
|
+
|
|
71
|
+
<NotificationGrouped>
|
|
72
|
+
<Notification>...</Notification>
|
|
73
|
+
</NotificationGrouped>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Контракт компонента: «Может содержать только Icon, Body, Close, Time» — на
|
|
77
|
+
уровне типов не ограничено (`children: ReactElement | ReactElement[]`),
|
|
78
|
+
но стилизация рассчитана только на эти 4 слота.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Варианты и props
|
|
83
|
+
|
|
84
|
+
### Notification — `INotificationProps extends React.ButtonHTMLAttributes<HTMLElement>`
|
|
85
|
+
|
|
86
|
+
| Prop | Тип | Default | Описание |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| `children` | `ReactElement \| ReactElement[]` | — | Только Icon/Body/Close/Time. |
|
|
89
|
+
| `withExtraBottomPadding` | `boolean` | `false` | Дополнительный нижний padding (mail-варианты). |
|
|
90
|
+
| `isShowCloseOnHover` | `boolean` | `false` | Close видим только при hover'е (для SideOverlay/Alert). |
|
|
91
|
+
| `onClick` | `() => void` | — | Клик по карточке. |
|
|
92
|
+
| `...HTMLAttributes` | `ButtonHTMLAttributes<HTMLElement>` | — | Прокидываются на корневой `<div>` (имя `Button*` — историческое). |
|
|
93
|
+
|
|
94
|
+
- Корневой тег — **`<div role="alertdialog">`**, не `<button>` (несмотря на `ButtonHTMLAttributes`).
|
|
95
|
+
- `data-tx={process.env.npm_package_version}` — версия пакета.
|
|
96
|
+
|
|
97
|
+
### Notification.Body — `INotificationBodySFC` (slots: Header, Content, List, Footer)
|
|
98
|
+
|
|
99
|
+
- `children: ReactNode` — последовательность заголовка / контента / списка / футера.
|
|
100
|
+
- Каждый слот — `React.FC` (без forwardRef).
|
|
101
|
+
|
|
102
|
+
### Notification.Body.Header
|
|
103
|
+
|
|
104
|
+
- `<h3>` с фиксированными стилями. `children: ReactNode`.
|
|
105
|
+
|
|
106
|
+
### Notification.Body.Content
|
|
107
|
+
|
|
108
|
+
- `<Text tag="div" size=B3 type=PRIMARY>{children}</Text>` — типографика жёстко зашита.
|
|
109
|
+
|
|
110
|
+
### Notification.Body.List — `INotificationBodyListProps extends IUnorderedListProps`
|
|
111
|
+
|
|
112
|
+
- **`values: string[]`** — массив строк, рендерятся как `UnorderedList.Item`.
|
|
113
|
+
- `...IUnorderedListProps` — пробрасывается. `className` мержится с локальным.
|
|
114
|
+
|
|
115
|
+
### Notification.Body.Footer
|
|
116
|
+
|
|
117
|
+
- `<div>` с локальной стилизацией. `children: ReactNode`.
|
|
118
|
+
|
|
119
|
+
### Notification.Close — `INotificationCloseProps extends Omit<IButtonIconProps, "children">`
|
|
120
|
+
|
|
121
|
+
- **Иконка фиксирована**: `CrossStrokeSrvIcon16 paletteIndex={5}`.
|
|
122
|
+
- `onClick: () => void` (обязателен).
|
|
123
|
+
- `children?: never` — нельзя кастомизировать иконку.
|
|
124
|
+
- Обёрнут в `<span>` для позиционирования.
|
|
125
|
+
|
|
126
|
+
### Notification.Icon — `INotificationIconProps`
|
|
127
|
+
|
|
128
|
+
- `children: ReactElement` — потребительская иконка. Обёрнута в `<div>` с фикс.размером (см. styles).
|
|
129
|
+
|
|
130
|
+
### Notification.Time — `INotificationTimeProps`
|
|
131
|
+
|
|
132
|
+
- `time: ReactText` (string|number) — рендерится в `<span>` без форматирования.
|
|
133
|
+
|
|
134
|
+
### NotificationGrouped — `INotificationGroupedProps`
|
|
135
|
+
|
|
136
|
+
- `children: ReactNode` — обычно одна `<Notification>`.
|
|
137
|
+
- Добавляет **2 декоративных `<div>`** снизу (`NotificationGroupedFooter`) — визуальный эффект «стопки».
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Ключевые особенности реализации
|
|
142
|
+
|
|
143
|
+
### `INotificationSFC` — slots через property assignment, не Object.assign
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
export const Notification: INotificationSFC = ((props) => {...}) as INotificationSFC;
|
|
147
|
+
Notification.displayName = "Notification";
|
|
148
|
+
Notification.Icon = NotificationIcon;
|
|
149
|
+
// ...
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
То же — `INotificationBodySFC`. **Нет `forwardRef`** на корне.
|
|
153
|
+
|
|
154
|
+
### `role="alertdialog"` на корневом `<div>`
|
|
155
|
+
|
|
156
|
+
- Семантика модального уведомления, требующего внимания. Несмотря на `ButtonHTMLAttributes` в типах — корень всегда `<div>`, поэтому атрибуты `<button>`-only (например `disabled`, `type="submit"`) не применятся.
|
|
157
|
+
|
|
158
|
+
### `isShowCloseOnHover` — для SideOverlay/Alert
|
|
159
|
+
|
|
160
|
+
- Класс `styles.showCloseOnHover` — включает CSS-правило, которое делает `Notification.Close` видимым только при hover'е на родителе. Используется в `Alert` и `SideOverlay` (по комментарию в коде).
|
|
161
|
+
|
|
162
|
+
### `withExtraBottomPadding` — mail-вариант
|
|
163
|
+
|
|
164
|
+
- Дополнительный нижний padding для случая mail-нотификации (где Footer часто содержит CTA-кнопки).
|
|
165
|
+
|
|
166
|
+
### `Body.List` — только строки
|
|
167
|
+
|
|
168
|
+
- `values: string[]` — невозможно использовать ReactNode-элементы (ссылки, форматирование) внутри списка без рефакторинга. Известное ограничение API.
|
|
169
|
+
|
|
170
|
+
### `Close` — фиксированная иконка
|
|
171
|
+
|
|
172
|
+
- `CrossStrokeSrvIcon16 paletteIndex={5}` — нельзя заменить (`children: never`). Если нужно другое — потребитель кладёт собственный `ButtonIcon` мимо слота.
|
|
173
|
+
|
|
174
|
+
### `Grouped` — только декорация
|
|
175
|
+
|
|
176
|
+
- `NotificationGrouped` не управляет stack'ом нескольких уведомлений; это чистый CSS-эффект «карточка из 3-х слоёв» вокруг одного `Notification`.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Инварианты
|
|
181
|
+
|
|
182
|
+
- **Корень — `<div role="alertdialog">`**, не интерактивный элемент. `ButtonHTMLAttributes` — историческое API (имя), не означает `<button>` рендера.
|
|
183
|
+
- **`children` — только Icon / Body / Close / Time** (контракт документации; типы шире, но стили рассчитаны только на эти 4 слота).
|
|
184
|
+
- **`Body` — Header / Content / List / Footer** в этом смысловом порядке (CSS закладывает spacing).
|
|
185
|
+
- **`Close` иконка фиксирована**; `children: never`.
|
|
186
|
+
- **`Body.List.values: string[]`** — не `ReactNode[]`. При расширении API — breaking.
|
|
187
|
+
- **`Body.Content` типография зашита** (`B3 / PRIMARY`).
|
|
188
|
+
- **`Body.Header` — `<h3>`**. Heading-уровень не настраивается — может конфликтовать с outline страницы (a11y).
|
|
189
|
+
- **`isShowCloseOnHover` ↔ родитель** — работает только когда `Notification` — внутри SideOverlay/Alert (CSS `:hover` на родителе/самом элементе).
|
|
190
|
+
- **FC, не forwardRef** на всех уровнях (legacy).
|
|
191
|
+
- **`NotificationGrouped` — декорация**, не контейнер для списка.
|
|
192
|
+
- **`data-tx`** — версия пакета на корне.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Accessibility
|
|
197
|
+
|
|
198
|
+
- `role="alertdialog"` — корректный для модального уведомления, но контейнер не сам управляет `aria-labelledby` / `aria-describedby` — потребитель должен передать их через `...HTMLAttributes`.
|
|
199
|
+
- `<h3>` в `Body.Header` — фиксированный heading-уровень. Для контекстов, где это нарушает outline (например, top-level Alert на странице без h1/h2), — возможен a11y-гэп.
|
|
200
|
+
- `Close` — `<ButtonIcon>` с иконкой crossbar; **нет `aria-label`** в коде (передаётся через `...buttonProps`). Потребитель должен установить `aria-label="Закрыть уведомление"` при создании `<Notification.Close>`.
|
|
201
|
+
- `isShowCloseOnHover` — close-кнопка не видна без hover'а ⇒ для keyboard-only пользователей может быть недоступна, если фокус-стили не показывают её. Проверить!
|
|
202
|
+
- Корень — `<div>`, но `onClick` есть — без `keydown` Enter/Space обработчика. Если `onClick` критичен — клавиатура не работает.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Дизайн-токены
|
|
207
|
+
|
|
208
|
+
Нет собственных. Цвета/spacing — из глобальных токенов (см. `styles/Notification.module.less`). Иконка `CrossStrokeSrvIcon16` использует `paletteIndex={5}`.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## Связанные компоненты
|
|
213
|
+
|
|
214
|
+
- `SideOverlay` / `Alert` — типичные контейнеры (закрытие по hover).
|
|
215
|
+
- `UnorderedList` — основа `Body.List`.
|
|
216
|
+
- `Text` (Typography) — `Body.Content`.
|
|
217
|
+
- `ButtonIcon` — `Close`.
|
|
218
|
+
- `CrossStrokeSrvIcon16` — иконка close.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Stories
|
|
223
|
+
|
|
224
|
+
`stories/Notification/Notification.stories.tsx`
|
|
225
|
+
Примеры: `stories/Notification/examples/`
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## История изменений
|
|
230
|
+
|
|
231
|
+
| Дата | Изменение |
|
|
232
|
+
|---|---|
|
|
233
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: NumberField
|
|
3
|
+
category: Form Controls
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [TextField, FormField, Amount]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/NumberField/NumberField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# NumberField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Текстовое поле для ввода **числовых значений**: фильтрует ввод на лету,
|
|
17
|
+
оставляя только цифры, один десятичный разделитель (`,`) и опциональный
|
|
18
|
+
unary minus в позиции 0. Сам visual layer — `TextFieldBase`; уникальное
|
|
19
|
+
поведение — кастомный `<input>` (`NumberFieldInput`), который чистит
|
|
20
|
+
строку и компенсирует позицию каретки.
|
|
21
|
+
|
|
22
|
+
Используй когда: поле для ввода числа без формата суммы (без
|
|
23
|
+
group-separator). Для сумм с разделителями разрядов — `Amount`. Для
|
|
24
|
+
строковых значений — `TextField`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/NumberField/
|
|
32
|
+
├── NumberField.tsx # FC, обёртка над TextFieldBase + NumberFieldInput
|
|
33
|
+
├── NumberFieldInput.tsx # forwardRef, кастомная фильтрация ввода
|
|
34
|
+
├── types.ts # INumberFieldProps = ITextFieldProps
|
|
35
|
+
├── index.ts # Barrel
|
|
36
|
+
└── __tests__/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Варианты и props
|
|
42
|
+
|
|
43
|
+
### `INumberFieldProps extends ITextFieldProps`
|
|
44
|
+
|
|
45
|
+
Полностью наследует API `TextField`. Уникального prop'а нет.
|
|
46
|
+
|
|
47
|
+
| Prop (важные из ITextFieldProps) | Тип | Описание |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `inputProps` | `IFormFieldInputProps` | Прокидывается в `<NumberFieldInput>` (включая `value`, `onChange`, `placeholder`, `ref`). |
|
|
50
|
+
| `size` / `status` / `label` / `prefix` / `postfix` / ... | — | От `TextFieldBase`. |
|
|
51
|
+
|
|
52
|
+
### Внутренние правила фильтрации (`NumberFieldInput`)
|
|
53
|
+
|
|
54
|
+
| Случай | Поведение |
|
|
55
|
+
|---|---|
|
|
56
|
+
| Цифра `0–9` | оставляется |
|
|
57
|
+
| `,` или `.` (decimal separator) | первый — нормализуется в `,`; второй (если **правее каретки**) — заменяет первый, первый стирается |
|
|
58
|
+
| `-` в позиции `0` | оставляется как unary minus |
|
|
59
|
+
| Любое другое | удаляется |
|
|
60
|
+
| `,` или `.` левее уже существующего разделителя | удаляется |
|
|
61
|
+
|
|
62
|
+
- Разделитель — `AmountConst.DecimalComma` (`,`).
|
|
63
|
+
- Каретка корректируется: `setCaretPosition(target, caret + (newLength - oldLength))`.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Ключевые особенности реализации
|
|
68
|
+
|
|
69
|
+
### Фильтр в `onChange`, **не** в `onBeforeInput`
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
const handleChange = (event) => {
|
|
73
|
+
const caret = getCaretPosition(event.target);
|
|
74
|
+
const value = getFilteredValue(event.target.value, caret);
|
|
75
|
+
const caretShift = value.length - event.target.value.length;
|
|
76
|
+
event.target.value = value;
|
|
77
|
+
setCaretPosition(event.target, caret + caretShift);
|
|
78
|
+
onChange?.(event);
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `event.target.value` мутируется **синхронно** до вызова потребительского `onChange` — потребитель получает уже отфильтрованное значение.
|
|
83
|
+
- React будет видеть это как controlled-input change (если потребитель контролирует значение).
|
|
84
|
+
|
|
85
|
+
### Поведение «второй разделитель правее каретки»
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// Если уже был ',' и пользователь вводит ещё один справа от каретки:
|
|
89
|
+
// сдвигаем разделитель вправо, старый затираем.
|
|
90
|
+
if (separatorIndex == -1) buffer.push(",")
|
|
91
|
+
else if (i > caret) { buffer.push(","); buffer[separatorIndex] = ""; ... }
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Полезно для UX: если пользователь печатает `123,4` и ставит курсор после `123`, нажатие `,` снова — переместит разделитель.
|
|
95
|
+
- Если новый разделитель **левее или на каретке** — он игнорируется (старый сохраняется).
|
|
96
|
+
|
|
97
|
+
### Unary minus только в позиции 0
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
else if (i === 0 && StringUtils.isUnaryMinus(value[i])) buffer.push("-");
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- `-` где-либо ещё — удаляется.
|
|
104
|
+
- Поддерживается **один** минус. Двойной минус — невозможен.
|
|
105
|
+
|
|
106
|
+
### `,` vs `.`
|
|
107
|
+
|
|
108
|
+
- `.` принимается на ввод (например, нажатие `.` на нумпаде), но конвертируется в `,` (`AmountConst.DecimalComma`).
|
|
109
|
+
- Контракт значения: всегда строка с `,` (если используется decimal). Для backend'а, если ожидается `.` — потребитель должен сам сконвертировать.
|
|
110
|
+
|
|
111
|
+
### `NumberField` — только `TextFieldBase`-обёртка
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
export const NumberField = ({ inputProps, ...restProps }) => (
|
|
115
|
+
<TextFieldBase {...restProps}>
|
|
116
|
+
<NumberFieldInput {...inputProps} />
|
|
117
|
+
</TextFieldBase>
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
- **Не `forwardRef`** на `NumberField` — но `NumberFieldInput` — `forwardRef<HTMLInputElement>`. Если нужен ref на input — потребитель должен передать его через `inputProps.ref`.
|
|
122
|
+
|
|
123
|
+
### `IFormFieldInputProps` для `inputProps`
|
|
124
|
+
|
|
125
|
+
- `inputProps` — стандартный для FormField API (`value`, `onChange`, `defaultValue`, `placeholder`, `disabled`, `ref`).
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Инварианты
|
|
130
|
+
|
|
131
|
+
- **Фильтрация — `digit | "," | начальный "-"`** — расширение списка допустимых символов = breaking.
|
|
132
|
+
- **Один разделитель**, конвертируется в `,`.
|
|
133
|
+
- **Unary minus только в позиции 0** — внутри числа `-` теряется.
|
|
134
|
+
- **`AmountConst.DecimalComma = ","`** — изменение глобальной константы повлияет на NumberField.
|
|
135
|
+
- **`event.target.value` мутируется** до вызова потребительского `onChange` — `onChange` получает уже отфильтрованную строку.
|
|
136
|
+
- **Каретка корректируется** через `setCaretPosition` — без потери позиции при удалении невалидных символов.
|
|
137
|
+
- **`NumberFieldInput` — `forwardRef`**, `NumberField` — простая FC.
|
|
138
|
+
- **API = `ITextFieldProps`** — нет собственных props. Расширения = breaking.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Accessibility
|
|
143
|
+
|
|
144
|
+
- Стандартный `<input>` (через `FormFieldInput`) — клавиатура работает как для TextField.
|
|
145
|
+
- `inputmode` / `type` **не выставляется** на `numeric` / `decimal` — на mobile откроется обычная клавиатура. Потребитель может передать через `inputProps.inputMode = "decimal"`.
|
|
146
|
+
- Скринридеры читают как обычное text input. Для семантики числа — потребитель добавляет `aria-*`.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Дизайн-токены
|
|
151
|
+
|
|
152
|
+
Нет собственных — наследуются от `TextField` / `FormField`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Связанные компоненты
|
|
157
|
+
|
|
158
|
+
- `TextField` / `TextFieldBase` — визуальная база.
|
|
159
|
+
- `FormFieldInput` — нижележащий `<input>`.
|
|
160
|
+
- `Amount` — для денежных значений (с group-separator и фиксированной точностью).
|
|
161
|
+
- `AmountConst` — `DecimalComma`, `DecimalPoint`.
|
|
162
|
+
- `StringUtils` — `isDigit`, `isDecimalSeparator`, `isUnaryMinus`.
|
|
163
|
+
- `inputUtils` — `getCaretPosition`, `setCaretPosition`.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Stories
|
|
168
|
+
|
|
169
|
+
`stories/NumberField/NumberField.stories.tsx`
|
|
170
|
+
Примеры: `stories/NumberField/examples/`
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## История изменений
|
|
175
|
+
|
|
176
|
+
| Дата | Изменение |
|
|
177
|
+
|---|---|
|
|
178
|
+
| 2026-04-18 | Создан документ |
|