@sberbusiness/triplex-next-mcp-server 1.50.0-beta → 1.53.1
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 +1 -0
- package/bundle/components/AlertProcess.md +6 -3
- package/bundle/components/AlertProcessSpoiler.md +216 -0
- package/bundle/components/Badge.md +4 -3
- package/bundle/components/ButtonDropdown.md +5 -3
- package/bundle/components/Carousel.md +671 -0
- package/bundle/components/CarouselExtended.md +7 -5
- package/bundle/components/CarouselIndicators.md +666 -0
- package/bundle/components/CarouselItem.md +579 -0
- package/bundle/components/CarouselNextButton.md +633 -0
- package/bundle/components/CarouselPrevButton.md +633 -0
- package/bundle/components/CarouselViewport.md +631 -0
- package/bundle/components/CheckboxTreeExtended.md +21 -7
- package/bundle/components/CheckboxTreeExtendedCheckbox.md +591 -0
- package/bundle/components/CheckboxTreeExtendedNode.md +577 -0
- package/bundle/components/Chip.md +1 -0
- package/bundle/components/ChipDatePicker.md +332 -0
- package/bundle/components/ChipDatePickerTarget.md +316 -0
- package/bundle/components/ChipMonthYearPicker.md +4 -3
- package/bundle/components/ChipMonthYearPickerTarget.md +318 -0
- package/bundle/components/ChipMultiselect.md +63 -73
- package/bundle/components/ChipSelect.md +385 -0
- package/bundle/components/ChipSelectTarget.md +388 -0
- package/bundle/components/ChipSuggest.md +10 -16
- package/bundle/components/ChipSuggestTarget.md +506 -0
- package/bundle/components/CollapsibleTree.md +2 -1
- package/bundle/components/CollapsibleTreeExtended.md +2 -1
- package/bundle/components/DateField.md +4 -2
- package/bundle/components/DatePickerExtended.md +16 -14
- package/bundle/components/DateRange.md +17 -10
- package/bundle/components/Dropdown.md +36 -63
- package/bundle/components/EmptyView.md +9 -4
- package/bundle/components/ExpandAnimation.md +9 -5
- package/bundle/components/FormField.md +1 -1
- package/bundle/components/ImageGallery.md +19 -52
- package/bundle/components/ImageGalleryExtended.md +123 -139
- package/bundle/components/ListItemControlsButtonDropdown.md +3 -2
- package/bundle/components/LoaderSmall.md +5 -4
- package/bundle/components/MonthYearRange.md +334 -0
- package/bundle/components/MultiselectField.md +81 -143
- package/bundle/components/Notification.md +2 -1
- package/bundle/components/NotificationGrouped.md +174 -0
- package/bundle/components/NumberField.md +259 -116
- package/bundle/components/OrderedList.md +149 -112
- package/bundle/components/Overlay.md +3 -2
- package/bundle/components/PageIndicators.md +306 -0
- package/bundle/components/Pagination.md +81 -3
- package/bundle/components/PaginationExtended.md +160 -0
- package/bundle/components/Portal.md +5 -2
- package/bundle/components/Puller.md +375 -0
- package/bundle/components/SMSField.md +277 -102
- package/bundle/components/SelectExtendedField.md +11 -7
- package/bundle/components/SelectField.md +7 -4
- package/bundle/components/SmartSpot.md +259 -0
- package/bundle/components/Step.md +2 -1
- package/bundle/components/Stepper.md +7 -7
- package/bundle/components/Suggest.md +7 -6
- package/bundle/components/SuggestField.md +8 -6
- package/bundle/components/TableBasic.md +12 -14
- package/bundle/components/Tooltip.md +9 -8
- package/bundle/guides/commits.md +4 -1
- package/bundle/guides/context.md +5 -5
- package/bundle/guides/stories.md +29 -21
- package/bundle/manifest.json +145 -13
- package/bundle/release-notes/1.50.0.mdx +4 -0
- package/bundle/release-notes/1.51.0.mdx +57 -0
- package/bundle/release-notes/1.52.0.mdx +55 -0
- package/bundle/release-notes/1.53.0.mdx +60 -0
- package/bundle/release-notes/1.54.0.mdx +45 -0
- package/bundle/release-notes/1.55.0.mdx +7 -0
- package/bundle/tokens.json +377 -222
- package/dist/index.js +39 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: NotificationGrouped
|
|
3
|
+
category: Feedback
|
|
4
|
+
related: [Notification]
|
|
5
|
+
tokens:
|
|
6
|
+
- Notification.Background
|
|
7
|
+
- Notification.Shadow
|
|
8
|
+
stories: stories/NotificationGrouped/NotificationGrouped.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# NotificationGrouped
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Визуально обозначает группу уведомлений: под содержимым отображаются два декоративных
|
|
17
|
+
слоя, создающие эффект стопки карточек. Обычно в группу вкладывают один `Notification`,
|
|
18
|
+
который представляет несколько сообщений.
|
|
19
|
+
|
|
20
|
+
Используй когда: нужно показать свёрнутую группу уведомлений одной карточкой.
|
|
21
|
+
Не используй когда: нужно отобразить каждое сообщение отдельно — отрендери список
|
|
22
|
+
`Notification`. Количество сообщений, раскрытие группы и закрытие управляются приложением.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Варианты и props
|
|
27
|
+
|
|
28
|
+
| Prop | Тип | Описание |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `children` | `React.ReactNode` | Содержимое группы. Обычно один `Notification`; тип не ограничивает состав дочерних узлов |
|
|
31
|
+
|
|
32
|
+
Других props у обёртки нет: HTML-атрибуты, `className` и обработчики задаются вложенному
|
|
33
|
+
`Notification` или внешнему контейнеру. `ref` передаётся на корневой `div` группы,
|
|
34
|
+
а не на вложенное уведомление.
|
|
35
|
+
|
|
36
|
+
Компонент выводит `children` без преобразования и всегда добавляет декоративный footer,
|
|
37
|
+
в том числе при `children={null}`. Два слоя обозначают стопку независимо от числа сообщений.
|
|
38
|
+
Для пользовательского интерфейса передавай содержательную карточку.
|
|
39
|
+
|
|
40
|
+
Ширина группы на десктопе — 376px, на ширине экрана до 767px — 100% родителя.
|
|
41
|
+
Нижний внешний отступ — 32px; декоративные слои выступают на 8px и 16px ниже содержимого.
|
|
42
|
+
Отступы, иконка, текст, время и действия самой карточки настраиваются через `Notification`.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Дизайн-токены
|
|
47
|
+
|
|
48
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см.
|
|
49
|
+
[`ThemeProvider-ai.md`](../ThemeProvider/ThemeProvider-ai.md) → «Как переопределять токены».
|
|
50
|
+
Значения по умолчанию — `src/components/DesignTokens/components/Notification.ts`.
|
|
51
|
+
|
|
52
|
+
| Токен | Назначение |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `Notification.Background` | Фон декоративных слоёв |
|
|
55
|
+
| `Notification.Shadow` | Тень декоративных слоёв |
|
|
56
|
+
|
|
57
|
+
Токены общие с `Notification`: их переопределение меняет карточку и слои стопки.
|
|
58
|
+
Цвет текста и времени относятся к вложенному уведомлению и описаны в
|
|
59
|
+
[`Notification-ai.md`](./Notification-ai.md).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Инварианты
|
|
64
|
+
|
|
65
|
+
- `INotificationGroupedProps.children` обязателен и сохраняет тип `React.ReactNode`.
|
|
66
|
+
- `forwardRef<HTMLDivElement, INotificationGroupedProps>` и `displayName="NotificationGrouped"`
|
|
67
|
+
сохраняются; корневой элемент и ref-target — `div` группы.
|
|
68
|
+
- Публичные экспорты `NotificationGrouped` и `INotificationGroupedProps` из
|
|
69
|
+
`src/components/Notification/index.ts` сохраняются.
|
|
70
|
+
- Декоративный `NotificationGroupedFooter` располагается после `children` и содержит
|
|
71
|
+
два слоя. Он не экспортируется через публичный barrel.
|
|
72
|
+
- Классы `notificationGroupedWrapper`, `notificationGroupedFooterItem`, `first`, `second`
|
|
73
|
+
и пути токенов сохраняются. Разметка и порядок слоёв влияют на visual baselines.
|
|
74
|
+
- Группа не фильтрует дочерние узлы и не управляет состоянием сообщений.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Accessibility
|
|
79
|
+
|
|
80
|
+
Обёртка — обычный `div` без роли, `tabIndex` и клавиатурных обработчиков. Курсор при
|
|
81
|
+
наведении имеет вид указателя, но компонент сам не добавляет интерактивность.
|
|
82
|
+
Декоративные слои не содержат текста или элементов управления.
|
|
83
|
+
|
|
84
|
+
Роль `alertdialog` задаёт вложенный `Notification`, а не группа. Подписи ARIA и действия
|
|
85
|
+
передавай самому уведомлению и его дочерним элементам; например, `aria-label` для
|
|
86
|
+
`Notification.Close` задаёт потребитель на языке приложения. Клавиатурный фокус
|
|
87
|
+
принадлежит вложенным кнопкам и ссылкам.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Связанные компоненты
|
|
92
|
+
|
|
93
|
+
- `Notification` — карточка внутри группы; определяет содержимое, действия и accessibility.
|
|
94
|
+
- `NotificationGroupedFooter` — приватный декоративный footer из двух пустых слоёв;
|
|
95
|
+
отдельного публичного API и AI-документа нет.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Stories
|
|
100
|
+
|
|
101
|
+
Основные истории: `stories/NotificationGrouped/NotificationGrouped.stories.tsx`
|
|
102
|
+
Файлы примеров: `stories/NotificationGrouped/examples/`
|
|
103
|
+
|
|
104
|
+
| Story | Example file | Что демонстрирует |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `Default` | `Default.tsx` | Минимальная группа с одним уведомлением |
|
|
107
|
+
| `WithFooter` | `WithFooter.tsx` | Карточка с действием, кнопкой закрытия и временем |
|
|
108
|
+
| `VisualTests` | `VisualTests.tsx` | Клавиатурный фокус на действии и закрытие при наведении на карточку |
|
|
109
|
+
|
|
110
|
+
Playground отсутствует: у обёртки только `children`, настраиваемых вариантов нет.
|
|
111
|
+
Stories снимаются на xs/xl; реальное наведение для `VisualTests` выполняется test-runner
|
|
112
|
+
через `hoverSelector`, а фокус устанавливается клавиатурой в `play`.
|
|
113
|
+
Несколько групп подряд — story `Business Stack` в `stories/Notification/`.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## История изменений
|
|
118
|
+
|
|
119
|
+
| Дата | Изменение |
|
|
120
|
+
|---|---|
|
|
121
|
+
| 2026-10-08 | Создан документ; уточнены JSDoc, выделены unit-тесты и добавлены отдельные modern stories NotificationGrouped |
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Примеры использования
|
|
126
|
+
|
|
127
|
+
### Default
|
|
128
|
+
|
|
129
|
+
`stories/NotificationGrouped/examples/Default.tsx`
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import React from "react";
|
|
133
|
+
import { Notification, NotificationGrouped } from "@sberbusiness/triplex-next";
|
|
134
|
+
|
|
135
|
+
export const Default = () => (
|
|
136
|
+
<NotificationGrouped>
|
|
137
|
+
<Notification>
|
|
138
|
+
<Notification.Body>
|
|
139
|
+
<Notification.Body.Content>Получены новые сообщения.</Notification.Body.Content>
|
|
140
|
+
</Notification.Body>
|
|
141
|
+
</Notification>
|
|
142
|
+
</NotificationGrouped>
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### WithFooter
|
|
147
|
+
|
|
148
|
+
`stories/NotificationGrouped/examples/WithFooter.tsx`
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import React from "react";
|
|
152
|
+
import { Button, EButtonTheme, EComponentSize, Notification, NotificationGrouped } from "@sberbusiness/triplex-next";
|
|
153
|
+
|
|
154
|
+
export const WithFooter = () => (
|
|
155
|
+
<NotificationGrouped>
|
|
156
|
+
<Notification withExtraBottomPadding>
|
|
157
|
+
<Notification.Body>
|
|
158
|
+
<Notification.Body.Header>Новые платежи</Notification.Body.Header>
|
|
159
|
+
<Notification.Body.Content>
|
|
160
|
+
Получены три платежа. Откройте список для проверки.
|
|
161
|
+
</Notification.Body.Content>
|
|
162
|
+
<Notification.Body.Footer>
|
|
163
|
+
<Button theme={EButtonTheme.SECONDARY} size={EComponentSize.SM} onClick={() => {}}>
|
|
164
|
+
Открыть платежи
|
|
165
|
+
</Button>
|
|
166
|
+
</Notification.Body.Footer>
|
|
167
|
+
</Notification.Body>
|
|
168
|
+
<Notification.Close aria-label="Закрыть уведомление" onClick={() => {}} />
|
|
169
|
+
<Notification.Time time="14:30" />
|
|
170
|
+
</Notification>
|
|
171
|
+
</NotificationGrouped>
|
|
172
|
+
);
|
|
173
|
+
```
|
|
174
|
+
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: NumberField
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [TextField, FormField, Amount]
|
|
3
|
+
category: TextFields
|
|
4
|
+
related: [AmountField, TextField, FormField]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/NumberField/NumberField.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
@@ -13,166 +11,311 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
оставляя только цифры, один десятичный разделитель (`,`) и опциональный
|
|
18
|
-
unary minus в позиции 0. Сам visual layer — `TextFieldBase`; уникальное
|
|
19
|
-
поведение — кастомный `<input>` (`NumberFieldInput`), который чистит
|
|
20
|
-
строку и компенсирует позицию каретки.
|
|
14
|
+
Однострочное поле для ввода числовой строки с плавающим лейблом, статусом, префиксом/постфиксом и описанием. При изменении ввода оставляет цифры, ведущий минус и один десятичный разделитель; точку заменяет запятой. Композиция: приватный `TextFieldBase` (семейство `FormField`) и приватный `NumberFieldInput` поверх `FormFieldInput`.
|
|
21
15
|
|
|
22
|
-
Используй когда:
|
|
23
|
-
group-separator). Для сумм с разделителями разрядов — `Amount`. Для
|
|
24
|
-
строковых значений — `TextField`.
|
|
16
|
+
Используй когда: пользователь вводит произвольное целое или дробное число без денежного форматирования, ограничения точности и разделителей разрядов.
|
|
25
17
|
|
|
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
|
-
```
|
|
18
|
+
Не используй когда: нужна денежная сумма с форматированием и нормализованным значением (`AmountField`), произвольный текст (`TextField`), фиксированная маска (`MaskedField`) или только отображение числа без ввода.
|
|
38
19
|
|
|
39
20
|
---
|
|
40
21
|
|
|
41
22
|
## Варианты и props
|
|
42
23
|
|
|
43
|
-
|
|
24
|
+
`INumberFieldProps` наследует `ITextFieldProps`; собственных дополнительных props нет. `inputProps` передаётся в `NumberFieldInput`, остальные props — в `TextFieldBase`. Полный контракт атрибутов и слотов описан в `TextField-ai.md` и исходных интерфейсах.
|
|
44
25
|
|
|
45
|
-
|
|
26
|
+
### Обязательные props
|
|
46
27
|
|
|
47
|
-
| Prop
|
|
28
|
+
| Prop | Назначение и ограничения |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `inputProps` | Атрибуты и обработчики элемента ввода: `value`/`defaultValue`, `onChange`, `placeholder`, `id`, ARIA-атрибуты, `ref` и `render`. Для контролируемого ввода храни строку из `event.target.value`; это значение с запятой, а не нормализованная числовая строка с точкой. `inputProps.ref` указывает на `HTMLInputElement`. |
|
|
31
|
+
|
|
32
|
+
### Опциональные props
|
|
33
|
+
|
|
34
|
+
| Prop | По умолчанию | Назначение |
|
|
48
35
|
|---|---|---|
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
36
|
+
| `size` | `EComponentSize.LG` | Размер SM / MD / LG; наследуется вложенным input через `FormField`. |
|
|
37
|
+
| `status` | `EFormFieldStatus.DEFAULT` | DEFAULT / ERROR / WARNING / DISABLED. Статус DISABLED устанавливает нативный `disabled` на input. |
|
|
38
|
+
| `active` | `false` | Принудительно активное визуальное состояние; фокус input также делает поле активным. |
|
|
39
|
+
| `label` | — | Плавающий лейбл, связанный с input через `htmlFor`. |
|
|
40
|
+
| `prefix`, `postfix` | — | Контент слева и справа от input; кнопку очистки потребитель добавляет в `postfix` и сам сбрасывает значение. |
|
|
41
|
+
| `description`, `counter` | — | Описание и счётчик под полем, отображаются в общем блоке. |
|
|
51
42
|
|
|
52
|
-
|
|
43
|
+
`className`, `style`, data-атрибуты и другие div-атрибуты применяются к контейнеру `FormField` внутри `FormGroup`. Внешний `ref` указывает на этот же `<div>`; ссылка на input передаётся отдельно через `inputProps.ref`.
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|---|---|
|
|
56
|
-
| Цифра `0–9` | оставляется |
|
|
57
|
-
| `,` или `.` (decimal separator) | первый — нормализуется в `,`; второй (если **правее каретки**) — заменяет первый, первый стирается |
|
|
58
|
-
| `-` в позиции `0` | оставляется как unary minus |
|
|
59
|
-
| Любое другое | удаляется |
|
|
60
|
-
| `,` или `.` левее уже существующего разделителя | удаляется |
|
|
45
|
+
### Особенности поведения
|
|
61
46
|
|
|
62
|
-
-
|
|
63
|
-
-
|
|
47
|
+
- Фильтрация выполняется только при событии изменения ввода. Значения `value` и `defaultValue`, переданные снаружи, не нормализуются автоматически.
|
|
48
|
+
- Разрешены цифры `0`–`9`. Минус сохраняется только в начале исходной строки; другие символы удаляются. Ведущие нули остаются.
|
|
49
|
+
- Точка и запятая принимаются как десятичные разделители; в результате остаётся не более одной запятой. При повторном разделителе выбор зависит от каретки: разделитель справа от неё заменяет ранее найденный, остальные повторения игнорируются.
|
|
50
|
+
- Пустая строка и промежуточные значения `"-"`, `","`, `"-,"` допустимы. Компонент не проверяет завершённость числа, диапазон или количество дробных знаков и не преобразует строку в `number`.
|
|
51
|
+
- Перед вызовом `inputProps.onChange` изменяется `event.target.value`; обработчик получает тот же event с отфильтрованным значением. Если input в фокусе, каретка сдвигается только на символы, удалённые левее неё: символы, удалённые правее (например, минус не в начале строки), позицию не меняют.
|
|
52
|
+
- По умолчанию рендерится текстовый `<input>`. Сохраняй текстовый тип для работы с запятой и кареткой; подходящий `inputMode` потребитель задаёт через `inputProps`.
|
|
53
|
+
- При использовании `inputProps.render` передавай предоставленные атрибуты, обработчики и ref своему input, чтобы сохранить фильтрацию и связь с `FormField`.
|
|
64
54
|
|
|
65
55
|
---
|
|
66
56
|
|
|
67
|
-
##
|
|
57
|
+
## Дизайн-токены
|
|
68
58
|
|
|
69
|
-
|
|
59
|
+
Собственных стилей и токенов нет, поэтому `tokens: []`. Внешний вид задаёт семейство `FormField`; его токены описаны в `FormField-ai.md` и находятся в `src/components/DesignTokens/components/FormField.ts`.
|
|
70
60
|
|
|
71
|
-
|
|
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 (если потребитель контролирует значение).
|
|
61
|
+
---
|
|
84
62
|
|
|
85
|
-
|
|
63
|
+
## Инварианты
|
|
86
64
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
65
|
+
- `forwardRef<HTMLDivElement, INumberFieldProps>` и `NumberField.displayName = "NumberField"` — не убирать. Внешний ref передаётся через `TextFieldBase` в `<div>` `FormField`; ref на input остаётся отдельным контрактом `inputProps.ref`. Оба поддерживают объектную и callback-форму.
|
|
66
|
+
- Имена `NumberField`, `INumberFieldProps`, унаследованные props, значения `EComponentSize`/`EFormFieldStatus` и barrel-экспорты — публичный API. Не менять без согласования.
|
|
67
|
+
- `NumberFieldInput` не экспортируется из barrel и не имеет отдельного AI.md: это приватная реализация фильтрации. Проверяй её поведение через публичный `NumberField`.
|
|
68
|
+
- Строковый контракт `inputProps.onChange` с запятой и промежуточными значениями сохраняется. Переход на числовое значение, точку или ограничения точности меняет поведение потребителя.
|
|
69
|
+
- Сохраняй композицию `TextFieldBase` / `FormFieldInput`, разделение контейнерных props и `inputProps`, а также общий блок `description`/`counter`.
|
|
70
|
+
- Story IDs и файлы baseline для `Sizes` и `Statuses` используются visual regression тестами; не переименовывать без синхронного обновления.
|
|
71
|
+
- Компонент и его зависимости должны оставаться совместимыми с React 17 для ветки `release-0`; не добавлять React 18-only API.
|
|
93
72
|
|
|
94
|
-
|
|
95
|
-
- Если новый разделитель **левее или на каретке** — он игнорируется (старый сохраняется).
|
|
73
|
+
---
|
|
96
74
|
|
|
97
|
-
|
|
75
|
+
## Accessibility
|
|
98
76
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
- Input имеет стандартную текстовую семантику. Лейбл связывается с ним через `htmlFor` и `id`, переданный в `inputProps.id` либо сгенерированный `FormFieldInput`.
|
|
78
|
+
- Когда видимого лейбла нет, доступное имя задаёт потребитель через `inputProps["aria-label"]` или `inputProps["aria-labelledby"]`.
|
|
79
|
+
- `status={EFormFieldStatus.DISABLED}` блокирует input и исключает его из таб-обхода. `active` задаёт визуальное состояние и сам по себе не переводит фокус.
|
|
80
|
+
- `aria-invalid` и связь с описанием через `aria-describedby` потребитель передаёт через `inputProps`; статус ERROR не устанавливает эти атрибуты автоматически.
|
|
81
|
+
- Фокус и blur синхронизируются с `FormField`; пользовательские обработчики `inputProps.onFocus`/`onBlur` сохраняются. Для программного фокуса используй `inputProps.ref`.
|
|
82
|
+
- Отдельных обработчиков клавиш и роли `spinbutton` нет; клавиатурная навигация стандартная для `<input>`.
|
|
102
83
|
|
|
103
|
-
|
|
104
|
-
- Поддерживается **один** минус. Двойной минус — невозможен.
|
|
84
|
+
---
|
|
105
85
|
|
|
106
|
-
|
|
86
|
+
## Связанные компоненты
|
|
107
87
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
88
|
+
- `AmountField` — альтернатива для денежного ввода с разделителями разрядов, заданной точностью и нормализованным значением с точкой.
|
|
89
|
+
- `TextField` — источник наследуемого контракта `ITextFieldProps`. Его приватный `TextFieldBase` задаёт раскладку поля и слотов; отдельного AI.md у базы нет.
|
|
90
|
+
- `FormField` — семейство, обеспечивающее размер, статус, фокус, заполненность и связь лейбла с input. `NumberFieldInput` использует его `FormFieldInput`.
|
|
91
|
+
- `NumberFieldInput` — приватный слой фильтрации цифр, знака и десятичного разделителя с коррекцией каретки.
|
|
92
|
+
- `FormFieldClear` — необязательная кнопка очистки в `postfix`; пример `Production` очищает контролируемое значение и возвращает фокус в input.
|
|
110
93
|
|
|
111
|
-
|
|
94
|
+
---
|
|
112
95
|
|
|
113
|
-
|
|
114
|
-
export const NumberField = ({ inputProps, ...restProps }) => (
|
|
115
|
-
<TextFieldBase {...restProps}>
|
|
116
|
-
<NumberFieldInput {...inputProps} />
|
|
117
|
-
</TextFieldBase>
|
|
118
|
-
);
|
|
119
|
-
```
|
|
96
|
+
## Stories
|
|
120
97
|
|
|
121
|
-
|
|
98
|
+
Основные истории: `stories/NumberField/NumberField.stories.tsx`
|
|
99
|
+
Файлы примеров: `stories/NumberField/examples/`
|
|
122
100
|
|
|
123
|
-
|
|
101
|
+
| Story | Example file | Что демонстрирует |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `Playground` | `Playground.tsx` | Управление размером, статусом, лейблом, активностью, postfix и description |
|
|
104
|
+
| `Default` | `Default.tsx` | Контролируемый ввод с лейблом и placeholder |
|
|
105
|
+
| `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG |
|
|
106
|
+
| `Statuses` | `Statuses.tsx` | Статусы DEFAULT / ERROR / WARNING / DISABLED |
|
|
107
|
+
| `Production` | `Production.tsx` | Кнопка очистки с возвратом фокуса через `inputProps.ref`, единица измерения и подсказка в postfix, описание со ссылкой |
|
|
124
108
|
|
|
125
|
-
|
|
109
|
+
`Sizes` и `Statuses` покрыты существующими baseline-скриншотами для viewport xs/xl. `Playground`, `Default` и `Production` исключены из screenshot-прогона через `testRunner.skip`.
|
|
126
110
|
|
|
127
111
|
---
|
|
128
112
|
|
|
129
|
-
##
|
|
113
|
+
## История изменений
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
- **`AmountConst.DecimalComma = ","`** — изменение глобальной константы повлияет на NumberField.
|
|
135
|
-
- **`event.target.value` мутируется** до вызова потребительского `onChange` — `onChange` получает уже отфильтрованную строку.
|
|
136
|
-
- **Каретка корректируется** через `setCaretPosition` — без потери позиции при удалении невалидных символов.
|
|
137
|
-
- **`NumberFieldInput` — `forwardRef`**, `NumberField` — простая FC.
|
|
138
|
-
- **API = `ITextFieldProps`** — нет собственных props. Расширения = breaking.
|
|
115
|
+
| Дата | Изменение |
|
|
116
|
+
|---|---|
|
|
117
|
+
| 2026-10-08 | Создан документ (TRI-63). Добавлены ref на контейнер FormField и displayName; описаны фильтрация строкового ввода, refs, accessibility и существующие stories. Исправлен сдвиг каретки, когда фильтр удаляет символы правее неё |
|
|
139
118
|
|
|
140
119
|
---
|
|
141
120
|
|
|
142
|
-
##
|
|
121
|
+
## Примеры использования
|
|
143
122
|
|
|
144
|
-
|
|
145
|
-
- `inputmode` / `type` **не выставляется** на `numeric` / `decimal` — на mobile откроется обычная клавиатура. Потребитель может передать через `inputProps.inputMode = "decimal"`.
|
|
146
|
-
- Скринридеры читают как обычное text input. Для семантики числа — потребитель добавляет `aria-*`.
|
|
123
|
+
### Default
|
|
147
124
|
|
|
148
|
-
|
|
125
|
+
`stories/NumberField/examples/Default.tsx`
|
|
149
126
|
|
|
150
|
-
|
|
127
|
+
```tsx
|
|
128
|
+
import React, { useState } from "react";
|
|
129
|
+
import { NumberField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
130
|
+
|
|
131
|
+
export const Default = () => {
|
|
132
|
+
const [value, setValue] = useState<string>("");
|
|
133
|
+
|
|
134
|
+
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
135
|
+
setValue(event.target.value);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div style={{ maxWidth: 300 }}>
|
|
140
|
+
<NumberField
|
|
141
|
+
size={EComponentSize.LG}
|
|
142
|
+
status={EFormFieldStatus.DEFAULT}
|
|
143
|
+
inputProps={{
|
|
144
|
+
value,
|
|
145
|
+
placeholder: "0",
|
|
146
|
+
onChange: handleInputChange,
|
|
147
|
+
}}
|
|
148
|
+
label="Label"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
```
|
|
151
154
|
|
|
152
|
-
|
|
155
|
+
### Sizes
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
`stories/NumberField/examples/Sizes.tsx`
|
|
155
158
|
|
|
156
|
-
|
|
159
|
+
```tsx
|
|
160
|
+
import React, { useState } from "react";
|
|
161
|
+
import { NumberField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
162
|
+
|
|
163
|
+
interface ISizeItemProps {
|
|
164
|
+
size: EComponentSize;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const SizeItem = ({ size }: ISizeItemProps) => {
|
|
168
|
+
const [value, setValue] = useState<string>("");
|
|
169
|
+
|
|
170
|
+
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
171
|
+
setValue(event.target.value);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div style={{ maxWidth: 300 }}>
|
|
176
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
|
|
177
|
+
<NumberField
|
|
178
|
+
size={size}
|
|
179
|
+
status={EFormFieldStatus.DEFAULT}
|
|
180
|
+
inputProps={{
|
|
181
|
+
value,
|
|
182
|
+
placeholder: "0",
|
|
183
|
+
onChange: handleInputChange,
|
|
184
|
+
}}
|
|
185
|
+
label="Label"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
157
190
|
|
|
158
|
-
|
|
159
|
-
- `FormFieldInput` — нижележащий `<input>`.
|
|
160
|
-
- `Amount` — для денежных значений (с group-separator и фиксированной точностью).
|
|
161
|
-
- `AmountConst` — `DecimalComma`, `DecimalPoint`.
|
|
162
|
-
- `StringUtils` — `isDigit`, `isDecimalSeparator`, `isUnaryMinus`.
|
|
163
|
-
- `inputUtils` — `getCaretPosition`, `setCaretPosition`.
|
|
191
|
+
const SIZES = Object.values(EComponentSize);
|
|
164
192
|
|
|
165
|
-
|
|
193
|
+
export const Sizes = () => (
|
|
194
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
195
|
+
{SIZES.map((size) => (
|
|
196
|
+
<SizeItem key={size} size={size} />
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
```
|
|
166
201
|
|
|
167
|
-
|
|
202
|
+
### Statuses
|
|
168
203
|
|
|
169
|
-
`stories/NumberField/
|
|
170
|
-
Примеры: `stories/NumberField/examples/`
|
|
204
|
+
`stories/NumberField/examples/Statuses.tsx`
|
|
171
205
|
|
|
172
|
-
|
|
206
|
+
```tsx
|
|
207
|
+
import React, { useState } from "react";
|
|
208
|
+
import { NumberField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
209
|
+
|
|
210
|
+
interface IStatusItemProps {
|
|
211
|
+
status: EFormFieldStatus;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const StatusItem = ({ status }: IStatusItemProps) => {
|
|
215
|
+
const [value, setValue] = useState<string>("");
|
|
216
|
+
|
|
217
|
+
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
218
|
+
setValue(event.target.value);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div style={{ maxWidth: 300 }}>
|
|
223
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
|
|
224
|
+
<NumberField
|
|
225
|
+
size={EComponentSize.LG}
|
|
226
|
+
status={status}
|
|
227
|
+
inputProps={{
|
|
228
|
+
value,
|
|
229
|
+
placeholder: "0",
|
|
230
|
+
onChange: handleInputChange,
|
|
231
|
+
}}
|
|
232
|
+
label="Label"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
173
237
|
|
|
174
|
-
|
|
238
|
+
const STATUSES = Object.values(EFormFieldStatus);
|
|
239
|
+
|
|
240
|
+
export const Statuses = () => (
|
|
241
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
242
|
+
{STATUSES.map((status) => (
|
|
243
|
+
<StatusItem key={status} status={status} />
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Production
|
|
250
|
+
|
|
251
|
+
`stories/NumberField/examples/Production.tsx`
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import React, { useState, useRef } from "react";
|
|
255
|
+
import {
|
|
256
|
+
NumberField,
|
|
257
|
+
FormFieldClear,
|
|
258
|
+
Text,
|
|
259
|
+
HelpBox,
|
|
260
|
+
Link,
|
|
261
|
+
EComponentSize,
|
|
262
|
+
EFormFieldStatus,
|
|
263
|
+
ETextSize,
|
|
264
|
+
EFontType,
|
|
265
|
+
ETooltipSize,
|
|
266
|
+
} from "@sberbusiness/triplex-next";
|
|
267
|
+
|
|
268
|
+
export const Production = () => {
|
|
269
|
+
const [value, setValue] = useState<string>("");
|
|
270
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
271
|
+
|
|
272
|
+
const handleInputChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
273
|
+
setValue(event.target.value);
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const handleClearButtonClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
|
277
|
+
setValue("");
|
|
278
|
+
inputRef.current?.focus();
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
|
|
282
|
+
event.preventDefault();
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<div style={{ maxWidth: 300 }}>
|
|
287
|
+
<NumberField
|
|
288
|
+
size={EComponentSize.LG}
|
|
289
|
+
status={EFormFieldStatus.DEFAULT}
|
|
290
|
+
inputProps={{
|
|
291
|
+
value,
|
|
292
|
+
placeholder: "0",
|
|
293
|
+
onChange: handleInputChange,
|
|
294
|
+
ref: inputRef,
|
|
295
|
+
}}
|
|
296
|
+
label="Label"
|
|
297
|
+
postfix={
|
|
298
|
+
<>
|
|
299
|
+
<FormFieldClear aria-label="clear value" onClick={handleClearButtonClick} />
|
|
300
|
+
<Text size={ETextSize.B2} type={EFontType.SECONDARY}>
|
|
301
|
+
мм
|
|
302
|
+
</Text>
|
|
303
|
+
<HelpBox tooltipSize={ETooltipSize.SM}>Helpful details appear here</HelpBox>
|
|
304
|
+
</>
|
|
305
|
+
}
|
|
306
|
+
description={
|
|
307
|
+
<>
|
|
308
|
+
<Text size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
309
|
+
(21) Description{" "}
|
|
310
|
+
<Link href="#" onClick={handleLinkClick}>
|
|
311
|
+
Link text
|
|
312
|
+
</Link>
|
|
313
|
+
</Text>
|
|
314
|
+
</>
|
|
315
|
+
}
|
|
316
|
+
/>
|
|
317
|
+
</div>
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
```
|
|
175
321
|
|
|
176
|
-
| Дата | Изменение |
|
|
177
|
-
|---|---|
|
|
178
|
-
| 2026-04-18 | Создан документ |
|