@sberbusiness/triplex-next-mcp-server 0.36.0-beta0 → 1.41.0-beta
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/bundle/components/AlertContext.md +268 -0
- package/bundle/components/Button.md +5 -2
- package/bundle/components/ButtonDropdown.md +9 -1
- package/bundle/components/ButtonDropdownExtended.md +422 -0
- package/bundle/components/ButtonIcon.md +7 -1
- package/bundle/components/Calendar.md +183 -368
- package/bundle/components/Caption.md +291 -0
- package/bundle/components/Chip.md +310 -314
- package/bundle/components/CodeText.md +197 -0
- package/bundle/components/Col.md +311 -162
- package/bundle/components/CollapsibleTree.md +6 -3
- package/bundle/components/CollapsibleTreeExtended.md +6 -2
- package/bundle/components/Dropdown.md +748 -346
- package/bundle/components/FormField.md +468 -232
- package/bundle/components/IconWrapper.md +142 -89
- package/bundle/components/Island.md +221 -129
- package/bundle/components/KeyDownListener.md +215 -102
- package/bundle/components/LightBox.md +2252 -233
- package/bundle/components/List.md +26 -1
- package/bundle/components/ListActionItem.md +157 -0
- package/bundle/components/ListItem.md +34 -9
- package/bundle/components/ListItemSelectable.md +2 -2
- package/bundle/components/ListMaster.md +4 -5
- package/bundle/components/ListTableItem.md +391 -0
- package/bundle/components/LoaderMiddle.md +265 -0
- package/bundle/components/LoaderScreen.md +178 -81
- package/bundle/components/Notification.md +21 -21
- package/bundle/components/Page.md +1 -0
- package/bundle/components/Portal.md +131 -67
- package/bundle/components/Row.md +171 -69
- package/bundle/components/Text.md +320 -0
- package/bundle/components/TextField.md +364 -97
- package/bundle/components/ThemeProvider.md +305 -117
- package/bundle/components/Title.md +296 -0
- package/bundle/components/Tooltip.md +421 -181
- package/bundle/guides/commits.md +126 -24
- package/bundle/guides/context.md +1 -1
- package/bundle/guides/tests.md +42 -0
- package/bundle/manifest.json +109 -23
- package/bundle/release-notes/1.37.0.mdx +42 -1
- package/bundle/release-notes/1.38.0.mdx +36 -0
- package/bundle/release-notes/1.39.0.mdx +66 -0
- package/bundle/release-notes/1.40.0.mdx +27 -0
- package/bundle/release-notes/1.41.0.mdx +83 -0
- package/bundle/release-notes/1.42.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: FormField
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [TextField, TextareaField, NumberField, AmountField, DateField, MonthYearField, SMSField, SelectField, SuggestField, MultiselectField, FormGroup, Button, ButtonIcon]
|
|
3
|
+
category: FormField
|
|
4
|
+
related: [FormFieldLabel, FormFieldInput, FormFieldTextarea, FormFieldTarget, FormFieldMaskedInput, FormFieldPrefix, FormFieldPostfix, FormFieldClear, FormFieldDescription, FormFieldCounter, FormGroup, TextField, TextareaField, MaskedField, SelectField, SuggestField, DateField]
|
|
7
5
|
tokens:
|
|
8
6
|
- --triplex-next-FormField-Background_Default
|
|
9
7
|
- --triplex-next-FormField-Background_Hover
|
|
@@ -33,294 +31,532 @@ version: "1.0"
|
|
|
33
31
|
|
|
34
32
|
## Назначение
|
|
35
33
|
|
|
36
|
-
Низкоуровневый
|
|
37
|
-
который управляет визуальным состоянием (hover/active/focus/filled/error/warning/disabled),
|
|
38
|
-
плавающим лейблом и отступами под prefix/postfix. Сам по себе не рендерит
|
|
39
|
-
`<input>` — принимает свои под-компоненты (`FormFieldInput`, `FormFieldTextarea`,
|
|
40
|
-
`FormFieldMaskedInput`, `FormFieldTarget`, `FormFieldLabel`, `FormFieldPrefix`,
|
|
41
|
-
`FormFieldPostfix`, `FormFieldClear`, `FormFieldDescription`, `FormFieldCounter`).
|
|
34
|
+
Низкоуровневый контейнер поля ввода: рисует фон, скругление, обводку и состояния (default / error / warning / disabled, активное и заполненное), а также раздаёт вложенным субкомпонентам общий контекст (`FormFieldContext`) — размер, статус, фокус, заполненность, идентификаторы элемента ввода и лейбла, ширины префикса и постфикса.
|
|
42
35
|
|
|
43
|
-
Используй `
|
|
44
|
-
готовых обёртках (`TextField`, `NumberField`, `DateField` и т.п.).
|
|
45
|
-
Не используй напрямую когда: для типового текстового поля подходит
|
|
46
|
-
`TextField` — он уже собирает `FormField` + `FormFieldInput` + `FormFieldLabel`.
|
|
36
|
+
Используй когда: собираешь поле из частей вручную — `FormFieldLabel` + один из элементов ввода (`FormFieldInput` / `FormFieldTextarea` / `FormFieldMaskedInput` / `FormFieldTarget`) + опционально `FormFieldPrefix` / `FormFieldPostfix` / `FormFieldDescription` / `FormFieldCounter`, либо строишь на его основе собственный field-компонент.
|
|
47
37
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
## Файловая структура
|
|
51
|
-
|
|
52
|
-
```text
|
|
53
|
-
src/components/FormField/
|
|
54
|
-
├── FormField.tsx # Корневой контейнер, Provider контекста
|
|
55
|
-
├── FormFieldContext.ts # IFormFieldContext (size/status/ids/widths/filled/focused)
|
|
56
|
-
├── FormFieldDescriptionContext.ts # Контекст для Description/Counter (withCounter)
|
|
57
|
-
├── enums.ts # EFormFieldStatus
|
|
58
|
-
├── consts.ts # TARGET_PADDING_X_DEFAULT = 12
|
|
59
|
-
├── types.ts # TFormFieldMaskedInputMask
|
|
60
|
-
├── index.ts # Barrel: все компоненты, enums, types
|
|
61
|
-
├── components/
|
|
62
|
-
│ ├── FormFieldInput.tsx # <input>, поддерживает render-функцию
|
|
63
|
-
│ ├── FormFieldTextarea.tsx # <textarea>
|
|
64
|
-
│ ├── FormFieldMaskedInput.tsx # Маскированный инпут (react-text-mask)
|
|
65
|
-
│ ├── FormFieldMaskedInputPresets.ts # Готовые маски: phone, inn, snils, date и т.д.
|
|
66
|
-
│ ├── FormFieldTarget.tsx # Нередактируемое значение (для select-подобных)
|
|
67
|
-
│ ├── FormFieldLabel.tsx # Плавающий label
|
|
68
|
-
│ ├── FormFieldPrefix.tsx # Левый слот, измеряет ширину → context
|
|
69
|
-
│ ├── FormFieldPostfix.tsx # Правый слот, измеряет ширину → context
|
|
70
|
-
│ ├── FormFieldClear.tsx # Кнопка очистки (ButtonIcon с иконкой X)
|
|
71
|
-
│ ├── FormFieldDescription.tsx # Описание под полем
|
|
72
|
-
│ ├── FormFieldCounter.tsx # Счётчик символов (внутри Description)
|
|
73
|
-
│ └── utils.ts # isFilled()
|
|
74
|
-
├── styles/
|
|
75
|
-
│ ├── FormField.module.less # Размеры/статусы/hover/active
|
|
76
|
-
│ └── FormField*.module.less # По одному на под-компонент
|
|
77
|
-
└── __tests__/
|
|
78
|
-
├── FormField.test.tsx # error/disabled/focus/props-passthrough
|
|
79
|
-
└── FormFieldInput.test.tsx
|
|
80
|
-
```
|
|
38
|
+
Не используй когда: достаточно готовой обёртки — `TextField` (однострочный ввод), `TextareaField` (многострочный), `MaskedField` (маска), `SelectField` / `SuggestField` / `DateField` (выбор значения). `FormGroup` альтернативой не является: это контейнер, внутрь которого кладут само поле вместе с сообщением об ошибке.
|
|
81
39
|
|
|
82
40
|
---
|
|
83
41
|
|
|
84
|
-
##
|
|
85
|
-
|
|
86
|
-
Составляется вручную (не через `Object.assign`), потомки — отдельные
|
|
87
|
-
экспорты, которые читают `FormFieldContext`:
|
|
88
|
-
|
|
89
|
-
```tsx
|
|
90
|
-
<FormField size={EComponentSize.LG} status={EFormFieldStatus.DEFAULT}>
|
|
91
|
-
<FormFieldPrefix>{/* иконка слева */}</FormFieldPrefix>
|
|
92
|
-
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
93
|
-
<FormFieldInput value={value} onChange={onChange} />
|
|
94
|
-
<FormFieldPostfix>
|
|
95
|
-
<FormFieldClear onClick={handleClear} />
|
|
96
|
-
{/* или счётчик, иконка, тд */}
|
|
97
|
-
</FormFieldPostfix>
|
|
98
|
-
<FormFieldDescription>
|
|
99
|
-
Текст описания
|
|
100
|
-
<FormFieldCounter>{value.length} / 100</FormFieldCounter>
|
|
101
|
-
</FormFieldDescription>
|
|
102
|
-
</FormField>
|
|
103
|
-
```
|
|
42
|
+
## Варианты и props
|
|
104
43
|
|
|
105
|
-
|
|
106
|
-
`status`, `targetId`, `labelId`, `prefixWidth`, `postfixWidth`, `filled`,
|
|
107
|
-
`focused`, `active` и сеттеры. Под-компоненты читают/пишут это состояние.
|
|
44
|
+
`FormField` — контейнер и провайдер контекста. Собственного «значения» у него нет: состояния `filled` и `focused` вычисляются вложенными элементами ввода и поднимаются в контекст через сеттеры.
|
|
108
45
|
|
|
109
|
-
|
|
46
|
+
### Обязательные props
|
|
110
47
|
|
|
111
|
-
|
|
48
|
+
Обязательных custom-props нет. Практически всегда передаётся `children` — субкомпоненты семейства.
|
|
112
49
|
|
|
113
|
-
###
|
|
50
|
+
### Опциональные props
|
|
114
51
|
|
|
115
|
-
| Prop | Тип |
|
|
52
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
116
53
|
|---|---|---|---|
|
|
117
|
-
| `size` | `EComponentSize` | `LG` | Размер
|
|
118
|
-
| `status` | `EFormFieldStatus` | `DEFAULT` | `DEFAULT` / `
|
|
119
|
-
| `active` | `boolean` | `false` | Принудительно активное состояние (например,
|
|
120
|
-
| `className`, `style`, `...HTMLDivAttributes`, `data-*` | — | — | Стандартные. Padding-Left/Right перезаписывается на ширины prefix/postfix. |
|
|
121
|
-
|
|
122
|
-
### EFormFieldStatus
|
|
54
|
+
| `size` | `EComponentSize` | `EComponentSize.LG` | Размер поля: `SM` / `MD` / `LG`. Влияет на скругление корневого элемента и через контекст — на размеры лейбла, элемента ввода и плейсхолдера маски. |
|
|
55
|
+
| `status` | `EFormFieldStatus` | `EFormFieldStatus.DEFAULT` | Визуальное состояние: `DEFAULT` / `ERROR` / `WARNING` / `DISABLED`. `DISABLED` дополнительно проставляет `disabled` вложенным `input`/`textarea` и `aria-disabled` + `tabIndex={-1}` у `FormFieldTarget`. |
|
|
56
|
+
| `active` | `boolean` | `false` | Принудительно активное состояние (например, когда фокус визуально принадлежит связанному dropdown). Итоговая активность = `active \|\| focused`. |
|
|
123
57
|
|
|
124
|
-
|
|
125
|
-
|---|---|
|
|
126
|
-
| `DEFAULT` | Обычное состояние. |
|
|
127
|
-
| `DISABLED` | Визуально disabled; `FormFieldInput`/`FormFieldTextarea` проставляют `disabled`, `FormFieldTarget` получает `tabIndex=-1` и `aria-disabled`. |
|
|
128
|
-
| `ERROR` | Красный фон/шейдоу. |
|
|
129
|
-
| `WARNING` | Жёлтый фон/шейдоу. |
|
|
58
|
+
Остальные props — стандартные атрибуты `div` и `data-*`, они пробрасываются на корневой элемент.
|
|
130
59
|
|
|
131
|
-
###
|
|
60
|
+
### Особенности поведения
|
|
132
61
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
| `FormFieldTarget` | `placeholder?: ReactNode` | Нередактируемый `<div>` с `tabIndex=0`, `aria-labelledby`, для select-подобных обёрток. Placeholder — только при отсутствии children. |
|
|
139
|
-
| `FormFieldLabel` | `floating?: boolean` | По умолчанию `floating = filled || active`. Принудительный `floating` нужен редко (например, когда поле всегда с плавающим лейблом). `htmlFor` ставится из контекста — не передавать вручную (тип `Omit<..., "htmlFor">`). |
|
|
140
|
-
| `FormFieldPrefix` / `FormFieldPostfix` | все `HTMLAttributes<HTMLSpanElement>` | Меряют `getBoundingClientRect().width` и пишут в `FormFieldContext`, чтобы контейнер получил корректные `padding-left/right`, а label не залез под иконки. |
|
|
141
|
-
| `FormFieldClear` | все `ButtonHTMLAttributes<HTMLButtonElement>` кроме `children` | Рендерит `ButtonIcon` с `CrossStrokeSrvIcon16`. `onMouseDown` вызывает `preventDefault` — кнопка не забирает фокус у поля при клике. |
|
|
142
|
-
| `FormFieldDescription` / `FormFieldCounter` | `HTMLAttributes<HTMLDivElement>` | `Counter` монтирует флаг `withCounter` в `FormFieldDescriptionContext`, который меняет layout Description (для корректного расположения текста и счётчика). |
|
|
62
|
+
- Горизонтальные внутренние отступы корневого `div` задаются инлайн-стилем из ширин префикса и постфикса (`paddingLeft: prefixWidth`, `paddingRight: postfixWidth`). Значение по умолчанию — `TARGET_PADDING_X_DEFAULT` (12px); `FormFieldPrefix` / `FormFieldPostfix` измеряют себя через `ResizeObserver` и обновляют его. Пользовательский `style` мерджится последним и может переопределить отступы.
|
|
63
|
+
- Класс `filled` выставляется, когда вложенный элемент ввода сообщил о наличии значения. Для `FormFieldInput` / `FormFieldTextarea` это включает браузерное автозаполнение (ловится через CSS-анимационные хуки `autofill-applied-hook` / `autofill-cancelled-hook`), для `FormFieldTarget` — наличие `children`.
|
|
64
|
+
- Кнопка `FormFieldClear` показывается стилями только при `filled` и hover/активном состоянии непустого и не заблокированного поля, а также при собственном фокусе (`:focus`) — чтобы кнопка оставалась видимой при клавиатурной навигации.
|
|
65
|
+
- Идентификаторы генерируются субкомпонентами (`uniqueId` из `lodash-es`, не `React.useId` — код должен оставаться совместимым с React 17) и попадают в контекст как `targetId` / `labelId`; на них опирается связка `label[htmlFor]` ↔ элемент ввода.
|
|
66
|
+
- Вне `FormField` субкомпоненты используют `initialFormFieldContext`, где `filled: true` — это осознанный временный фикс для `DropdownMobileHeader`, а сеттеры являются no-op.
|
|
143
67
|
|
|
144
68
|
---
|
|
145
69
|
|
|
146
|
-
##
|
|
147
|
-
|
|
148
|
-
### Контекст — источник истины
|
|
149
|
-
|
|
150
|
-
- `FormField` хранит всё состояние (`useState` внутри корня), собирает его в `IFormFieldContext` через `useMemo` и отдаёт через Provider.
|
|
151
|
-
- Под-компоненты **только читают** контекст (`size`, `status`, `filled`, `focused`, `active`, ширины) и **вызывают сеттеры** (`setFilled`, `setFocused`, `setTargetId`, `setLabelId`, `setPrefixWidth`, `setPostfixWidth`).
|
|
152
|
-
- Корневому `FormField` не нужно `value` — `filled` управляется через `FormFieldInput`/`FormFieldTextarea`/`FormFieldTarget` на основе их значения (см. `isFilled`).
|
|
153
|
-
|
|
154
|
-
### Автоматическое `padding-left/right` от prefix/postfix
|
|
70
|
+
## Дизайн-токены
|
|
155
71
|
|
|
156
|
-
`FormField` пишет в inline style:
|
|
157
|
-
```ts
|
|
158
|
-
style={{ paddingLeft: prefixWidth, paddingRight: postfixWidth, ...style }}
|
|
159
72
|
```
|
|
160
|
-
|
|
73
|
+
--triplex-next-FormField-Background_Default
|
|
74
|
+
--triplex-next-FormField-Background_Hover
|
|
75
|
+
--triplex-next-FormField-Background_Active
|
|
76
|
+
--triplex-next-FormField-Background_Disabled
|
|
77
|
+
--triplex-next-FormField-Background_Error
|
|
78
|
+
--triplex-next-FormField-Background_Error_Hover
|
|
79
|
+
--triplex-next-FormField-Background_Warning
|
|
80
|
+
--triplex-next-FormField-Background_Warning_Hover
|
|
81
|
+
--triplex-next-FormField-Shadow_Default
|
|
82
|
+
--triplex-next-FormField-Shadow_Active
|
|
83
|
+
--triplex-next-FormField-Shadow_Error_Active
|
|
84
|
+
--triplex-next-FormField-Shadow_Warning_Active
|
|
85
|
+
--triplex-next-FormField-Input_Color_Default
|
|
86
|
+
--triplex-next-FormField-Input_Color_Disabled
|
|
87
|
+
--triplex-next-FormField-Label_Color_Default
|
|
88
|
+
--triplex-next-FormField-Label_Color_Disabled
|
|
89
|
+
--triplex-next-FormField-Placeholder_Color
|
|
90
|
+
--triplex-next-FormField-Target_Color_Default
|
|
91
|
+
--triplex-next-FormField-Target_Color_Disabled
|
|
92
|
+
--triplex-next-FormField-Target_PlaceholderColor_Default
|
|
93
|
+
```
|
|
161
94
|
|
|
162
|
-
|
|
95
|
+
---
|
|
163
96
|
|
|
164
|
-
|
|
97
|
+
## Инварианты
|
|
165
98
|
|
|
166
|
-
|
|
99
|
+
- `forwardRef` обязателен, ref ведёт на корневой `div`; корневой элемент менять нельзя.
|
|
100
|
+
- `EFormFieldStatus` и его строковые значения (`default` / `disabled` / `error` / `warning`) — публичный API, как и props `size` / `status` / `active`.
|
|
101
|
+
- Barrel `src/components/FormField/index.ts` реэкспортирует `enums`, `types`, `components` и сам `FormField` — состав экспортов не сокращать. Маппинг статуса на класс (`STATUS_TO_CLASS_NAME_MAP`) — внутренняя константа модуля и в barrel не входит.
|
|
102
|
+
- `FormFieldContext` и `FormFieldDescriptionContext` в barrel не входят: это внутренний механизм семейства, но на нём завязаны все субкомпоненты — менять форму значения контекста без правки субкомпонентов нельзя.
|
|
103
|
+
- Классы `formField`, `filled`, `active`, `error`, `warning`, `disabled`, а также `sm` / `md` / `lg` проверяются unit-тестами и используются в вложенных селекторах стилей семейства.
|
|
104
|
+
- Генерация id — через `uniqueId` (`lodash-es`), без `React.useId`: ветка `release-0` собирается на React 17.
|
|
105
|
+
- `FormFieldDescription` и `FormFieldCounter` объявлены как `React.FC` без `forwardRef` — осознанное историческое отличие от остальных субкомпонентов. Добавление ref расширяет публичный API; решение владельца — оставить как есть.
|
|
106
|
+
- Внутренние утилиты семейства (`components/utils.ts` — `isFilled`, `setForwardedRef`; `components/useFormFieldAffixWidth.ts`) намеренно не попадают в barrel `components/index.ts`.
|
|
107
|
+
- Компонент не рендерит собственных подписей и текстов — библиотека мультиязычная.
|
|
167
108
|
|
|
168
|
-
|
|
169
|
-
- поле заполнено (`filled`), либо
|
|
170
|
-
- поле активно (`active === active-prop || focused`).
|
|
109
|
+
---
|
|
171
110
|
|
|
172
|
-
|
|
173
|
-
- `undefined` → `false`
|
|
174
|
-
- `number` → `true`
|
|
175
|
-
- строка → `value.length !== 0`
|
|
111
|
+
## Accessibility
|
|
176
112
|
|
|
177
|
-
|
|
113
|
+
- Корневой элемент — обычный `div` без роли: семантику даёт вложенный нативный `input` / `textarea`.
|
|
114
|
+
- Связка лейбла и поля: `FormFieldLabel` получает `htmlFor={targetId}` из контекста, элемент ввода — сгенерированный `id`. Поэтому `screen.getByLabelText(...)` находит поле, а клик по лейблу фокусирует ввод.
|
|
115
|
+
- `FormFieldTarget` (нередактируемое значение, например для select-подобных полей) получает `tabIndex={0}`, `aria-labelledby={labelId}` и `aria-disabled`; при `status = DISABLED` — `tabIndex={-1}`.
|
|
116
|
+
- При `status = DISABLED` вложенные `input` / `textarea` получают нативный `disabled`, то есть выпадают из таб-порядка.
|
|
117
|
+
- Плейсхолдер маски в `FormFieldMaskedInput` рендерится с `aria-hidden="true"` — скринридер читает только реальное значение input.
|
|
118
|
+
- Текст описания (`FormFieldDescription`) визуально связан с полем, но не связывается автоматически через `aria-describedby` — при необходимости потребитель передаёт `aria-describedby` элементу ввода сам.
|
|
178
119
|
|
|
179
|
-
|
|
120
|
+
---
|
|
180
121
|
|
|
181
|
-
|
|
122
|
+
## Связанные компоненты
|
|
182
123
|
|
|
183
|
-
`
|
|
184
|
-
- `
|
|
185
|
-
- `
|
|
124
|
+
- `FormFieldLabel` — плавающий лейбл; поднимается над полем, когда поле заполнено или активно (можно форсировать props `floating`).
|
|
125
|
+
- `FormFieldInput` — однострочный ввод; поддерживает render-prop `render` для подмены инпута с сохранением стилизации.
|
|
126
|
+
- `FormFieldTextarea` — многострочный ввод.
|
|
127
|
+
- `FormFieldMaskedInput` — маскированный ввод на базе `react-text-mask`; содержит пресеты масок (`FormFieldMaskedInput.presets`).
|
|
128
|
+
- `FormFieldTarget` — нередактируемое значение с `placeholder`, используется select-подобными полями.
|
|
129
|
+
- `FormFieldPrefix` / `FormFieldPostfix` — контейнеры слева/справа; их измеренная ширина становится внутренним отступом поля. Общая логика измерения и проброса ref вынесена во внутренний хук `components/useFormFieldAffixWidth.ts` (в barrel не экспортируется).
|
|
130
|
+
- `FormFieldClear` — кнопка очистки (`ButtonIcon` с иконкой креста), гасит фокус на `mousedown`.
|
|
131
|
+
- `FormFieldDescription` — описание под полем; провайдер `FormFieldDescriptionContext`.
|
|
132
|
+
- `FormFieldCounter` — счётчик символов внутри описания; сообщает `FormFieldDescription` о своём наличии.
|
|
133
|
+
- `TextField`, `TextareaField`, `MaskedField`, `SelectField`, `SuggestField`, `DateField` — компоненты уровнем выше, построенные на этом семействе.
|
|
134
|
+
- `FormGroup` — контейнер формы (`div` без собственной логики), объединяющий поле с `HelpBox` / `Alert` / описанием. О `FormField` ничего не знает и на семействе не построен: поле кладут внутрь него.
|
|
186
135
|
|
|
187
|
-
|
|
136
|
+
---
|
|
188
137
|
|
|
189
|
-
|
|
138
|
+
## Stories
|
|
190
139
|
|
|
191
|
-
|
|
140
|
+
Основные истории: `stories/FormField/FormField.stories.tsx`
|
|
141
|
+
Файлы примеров: `stories/FormField/examples/`
|
|
192
142
|
|
|
193
|
-
|
|
143
|
+
| Story | Example file | Что демонстрирует |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `status`, `size`, лейбла, плейсхолдера и описания |
|
|
146
|
+
| `Default` | `DefaultExample.tsx` | Минимальная композиция: лейбл + input |
|
|
147
|
+
| `WithPrefixAndPostfix` | `WithPrefixAndPostfixExample.tsx` | Контент слева и справа от поля и влияние на отступы |
|
|
148
|
+
| `WithClearButton` | `WithClearButtonExample.tsx` | Кнопка очистки значения в постфиксе |
|
|
149
|
+
| `WithCounter` | `WithCounterExample.tsx` | Описание со счётчиком символов |
|
|
150
|
+
| `States` | `StatesExample.tsx` | Статусы default / error / warning / disabled |
|
|
151
|
+
| `Textarea` | `TextareaExample.tsx` | Многострочный ввод |
|
|
152
|
+
| `Sizes` | `SizesExample.tsx` | Размеры `SM` / `MD` / `LG` |
|
|
153
|
+
| `MaskedInput` | `MaskedInputExample.tsx` | Маскированный ввод и пресеты масок |
|
|
154
|
+
| `VisualTests` | `VisualTestsExample.tsx` | Скриншот-регрессия: состояния и размеры на одной странице |
|
|
194
155
|
|
|
195
|
-
|
|
196
|
-
- Второй (`react-text-mask`) — принимает ввод, без guide'а, `value` берёт через `conformToMask`.
|
|
197
|
-
- Это интегрированное legacy-решение — не заменять на один инпут без согласования, placeholder-маска с подсветкой уже введённых символов требует двухслойной разметки.
|
|
198
|
-
- Специальная обработка для масок `phone` и `swiftCode` через `pipe` (нормализация `7`/`8` → `+7 (`, uppercase для SWIFT).
|
|
156
|
+
---
|
|
199
157
|
|
|
200
|
-
|
|
158
|
+
## История изменений
|
|
201
159
|
|
|
202
|
-
|
|
160
|
+
| Дата | Изменение |
|
|
161
|
+
|---|---|
|
|
162
|
+
| 2026-07-31 | Создан документ. AI-рефакторинг FormField: JSDoc на props, enum, контекстах и константах, активное состояние вычисляется одним выражением, контекст типизирован `IFormFieldContext`, unit-тесты расширены с 4 до 23 кейсов. Публичный API не изменён. |
|
|
163
|
+
| 2026-07-31 | AI-рефакторинг распространён на субкомпоненты `FormFieldInput`, `FormFieldLabel`, `FormFieldPrefix`, `FormFieldPostfix`, `FormFieldClear`, `FormFieldDescription`, `FormFieldCounter`: JSDoc с указанием значений по умолчанию, дедупликация `FormFieldPrefix` / `FormFieldPostfix` через внутренний хук `useFormFieldAffixWidth`, общий `setForwardedRef` в `components/utils.ts`, unit-тесты. Публичный API не изменён. `FormFieldTextarea`, `FormFieldTarget` и `FormFieldMaskedInput` намеренно не затронуты — их покрывают задачи `TextareaField`, `SelectField` и `MaskedField`. |
|
|
164
|
+
| 2026-07-31 | Исправлено: `className` у `FormFieldDescription` и `FormFieldCounter` затирал базовый класс — теперь объединяется через `clsx`. Изменение наблюдаемого поведения, зафиксировано в release notes 1.41.0. |
|
|
165
|
+
| 2026-08-03 | Ломающее изменение: `statusToClassNameMap` убран из публичного barrel и переименован во внутреннюю константу `STATUS_TO_CLASS_NAME_MAP`. Зафиксировано в release notes 1.41.0. |
|
|
203
166
|
|
|
204
|
-
|
|
205
|
-
```less
|
|
206
|
-
&.filled:not(.disabled) {
|
|
207
|
-
&:hover, &.active {
|
|
208
|
-
.formFieldClear { visibility: visible; opacity: 1; pointer-events: auto; }
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
Кнопка появляется только когда поле заполнено и в hover/active. Если компонент Clear используется вне FormField (редкий кейс) — видимость нужно задавать вручную.
|
|
167
|
+
---
|
|
213
168
|
|
|
214
|
-
|
|
169
|
+
## Примеры использования
|
|
215
170
|
|
|
216
|
-
|
|
217
|
-
- `FormFieldLabel` берёт `targetId` из контекста и ставит в `htmlFor`.
|
|
218
|
-
- `FormFieldLabel` сам генерирует `id` и пишет в `labelId` → `FormFieldTarget` использует его как `aria-labelledby`.
|
|
171
|
+
### Default
|
|
219
172
|
|
|
220
|
-
|
|
173
|
+
`stories/FormField/examples/DefaultExample.tsx`
|
|
221
174
|
|
|
222
|
-
|
|
175
|
+
```tsx
|
|
176
|
+
import React, { useState } from "react";
|
|
177
|
+
import { FormField, FormFieldInput, FormFieldLabel } from "@sberbusiness/triplex-next";
|
|
178
|
+
|
|
179
|
+
export const DefaultExample = () => {
|
|
180
|
+
const [value, setValue] = useState("");
|
|
181
|
+
return (
|
|
182
|
+
<div style={{ maxWidth: "300px" }}>
|
|
183
|
+
<FormField>
|
|
184
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
185
|
+
<FormFieldInput
|
|
186
|
+
value={value}
|
|
187
|
+
onChange={(e) => setValue(e.target.value)}
|
|
188
|
+
placeholder="Введите текст..."
|
|
189
|
+
/>
|
|
190
|
+
</FormField>
|
|
191
|
+
</div>
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
```
|
|
223
195
|
|
|
224
|
-
|
|
196
|
+
### WithPrefixAndPostfix
|
|
225
197
|
|
|
226
|
-
|
|
227
|
-
- **`IFormFieldContext` shape** — изменение полей/сеттеров ломает все под-компоненты и внешние обёртки (`TextField` и т.д.). Добавлять аккуратно.
|
|
228
|
-
- **`FormField` корень** переопределяет `paddingLeft`/`paddingRight` из `style` значениями из контекста — передавать свои padding'и через `style` бесполезно, их затрёт. Документировать для потребителей.
|
|
229
|
-
- **Имена animation `autofill-applied-hook*` / `autofill-cancelled-hook*`** — завязаны на CSS; при переименовании обновить оба места.
|
|
230
|
-
- **`EFormFieldStatus` значения** — публичный API, используются в `TextField`, `NumberField` и т.д.
|
|
231
|
-
- **`FormFieldMaskedInput.presets`** — публичный API (импортируется пользователями для переиспользования масок). Новые маски можно добавлять, существующие — не переименовывать и не менять форму.
|
|
232
|
-
- **`FormFieldClear.onMouseDown → preventDefault`** — не удалять (иначе фокус уходит при клике на очистку).
|
|
233
|
-
- **Ширина prefix/postfix пишется на каждом рендере** (`useEffect` без deps в `FormFieldPrefix`/`FormFieldPostfix`). Это намеренно — измеряем после каждого рендера, защита от зависимостей-гонок. При изменении помни про бесконечный ре-рендер: записываем в context, только если `width !== prefixWidth`.
|
|
234
|
-
- **`FormFieldInput` в `useLayoutEffect` с `[]` deps** делает первичную синхронизацию `filled` без warning'а — eslint отключён специально. Не включать зависимости.
|
|
198
|
+
`stories/FormField/examples/WithPrefixAndPostfixExample.tsx`
|
|
235
199
|
|
|
236
|
-
|
|
200
|
+
```tsx
|
|
201
|
+
import React, { useState } from "react";
|
|
202
|
+
import { DefaulticonStrokePrdIcon20 } from "@sberbusiness/icons-next";
|
|
203
|
+
import {
|
|
204
|
+
FormField,
|
|
205
|
+
FormFieldInput,
|
|
206
|
+
FormFieldLabel,
|
|
207
|
+
FormFieldPostfix,
|
|
208
|
+
FormFieldPrefix,
|
|
209
|
+
HelpBox,
|
|
210
|
+
ETooltipPreferPlace,
|
|
211
|
+
ETooltipSize,
|
|
212
|
+
} from "@sberbusiness/triplex-next";
|
|
213
|
+
|
|
214
|
+
export const WithPrefixAndPostfixExample = () => {
|
|
215
|
+
const [value, setValue] = useState("");
|
|
216
|
+
return (
|
|
217
|
+
<div style={{ maxWidth: "300px" }}>
|
|
218
|
+
<FormField>
|
|
219
|
+
<FormFieldPrefix>
|
|
220
|
+
<DefaulticonStrokePrdIcon20 paletteIndex={5} />
|
|
221
|
+
</FormFieldPrefix>
|
|
222
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
223
|
+
<FormFieldInput value={value} onChange={(e) => setValue(e.target.value)} />
|
|
224
|
+
<FormFieldPostfix>
|
|
225
|
+
<HelpBox tooltipSize={ETooltipSize.SM} preferPlace={ETooltipPreferPlace.ABOVE}>
|
|
226
|
+
Text
|
|
227
|
+
</HelpBox>
|
|
228
|
+
</FormFieldPostfix>
|
|
229
|
+
</FormField>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
```
|
|
237
234
|
|
|
238
|
-
|
|
235
|
+
### WithClearButton
|
|
239
236
|
|
|
240
|
-
|
|
241
|
-
- **`FormFieldTarget`** — не нативный input, поэтому:
|
|
242
|
-
- `tabIndex=0` в обычном состоянии, `-1` при `DISABLED`.
|
|
243
|
-
- `aria-labelledby={labelId}` — связь с `FormFieldLabel`.
|
|
244
|
-
- `aria-disabled` при `DISABLED`.
|
|
245
|
-
- **`FormFieldLabel`** — нативный `<label htmlFor={targetId}>`, связь через контекст.
|
|
246
|
-
- **Без хардкода текста** — подписи, placeholder'ы, `aria-label` пишет потребитель (мультиязычность).
|
|
247
|
-
- **`FormFieldClear`** — это `ButtonIcon` (без видимого текста). Потребитель **обязан** передать `aria-label` через `...rest`.
|
|
248
|
-
- **`FormFieldMaskedInput`** — первый (placeholder-)input помечен `aria-hidden="true"` и `tabIndex={-1}`, скринридер читает только второй (реальный) инпут.
|
|
249
|
-
- **Статусы `ERROR`/`WARNING`** не выставляют `aria-invalid` автоматически. Если нужно — потребитель передаёт `aria-invalid` в props инпута.
|
|
237
|
+
`stories/FormField/examples/WithClearButtonExample.tsx`
|
|
250
238
|
|
|
251
|
-
|
|
239
|
+
```tsx
|
|
240
|
+
import React, { useState } from "react";
|
|
241
|
+
import {
|
|
242
|
+
FormField,
|
|
243
|
+
FormFieldClear,
|
|
244
|
+
FormFieldInput,
|
|
245
|
+
FormFieldLabel,
|
|
246
|
+
FormFieldPostfix,
|
|
247
|
+
HelpBox,
|
|
248
|
+
ETooltipPreferPlace,
|
|
249
|
+
ETooltipSize,
|
|
250
|
+
} from "@sberbusiness/triplex-next";
|
|
251
|
+
|
|
252
|
+
export const WithClearButtonExample = () => {
|
|
253
|
+
const [value, setValue] = useState("");
|
|
254
|
+
return (
|
|
255
|
+
<div style={{ maxWidth: "300px" }}>
|
|
256
|
+
<FormField>
|
|
257
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
258
|
+
<FormFieldInput value={value} onChange={(e) => setValue(e.target.value)} />
|
|
259
|
+
<FormFieldPostfix>
|
|
260
|
+
<FormFieldClear onClick={() => setValue("")} />
|
|
261
|
+
<HelpBox tooltipSize={ETooltipSize.SM} preferPlace={ETooltipPreferPlace.ABOVE}>
|
|
262
|
+
Text
|
|
263
|
+
</HelpBox>
|
|
264
|
+
</FormFieldPostfix>
|
|
265
|
+
</FormField>
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
```
|
|
252
270
|
|
|
253
|
-
|
|
271
|
+
### WithCounter
|
|
254
272
|
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
--triplex-next-FormField-Background_Default
|
|
258
|
-
--triplex-next-FormField-Background_Hover
|
|
259
|
-
--triplex-next-FormField-Background_Active
|
|
260
|
-
--triplex-next-FormField-Background_Disabled
|
|
261
|
-
--triplex-next-FormField-Background_Error[_Hover]
|
|
262
|
-
--triplex-next-FormField-Background_Warning[_Hover]
|
|
263
|
-
--triplex-next-FormField-Shadow_Default
|
|
264
|
-
--triplex-next-FormField-Shadow_Active
|
|
265
|
-
--triplex-next-FormField-Shadow_Error_Active
|
|
266
|
-
--triplex-next-FormField-Shadow_Warning_Active
|
|
267
|
-
```
|
|
273
|
+
`stories/FormField/examples/WithCounterExample.tsx`
|
|
268
274
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
275
|
+
```tsx
|
|
276
|
+
import React, { useState } from "react";
|
|
277
|
+
import {
|
|
278
|
+
EFontType,
|
|
279
|
+
ETextSize,
|
|
280
|
+
FormField,
|
|
281
|
+
FormFieldCounter,
|
|
282
|
+
FormFieldDescription,
|
|
283
|
+
FormFieldInput,
|
|
284
|
+
FormFieldLabel,
|
|
285
|
+
Text,
|
|
286
|
+
} from "@sberbusiness/triplex-next";
|
|
287
|
+
|
|
288
|
+
export const WithCounterExample = () => {
|
|
289
|
+
const [value, setValue] = useState("");
|
|
290
|
+
const maxLength = 201;
|
|
291
|
+
return (
|
|
292
|
+
<div style={{ maxWidth: "300px" }}>
|
|
293
|
+
<FormField>
|
|
294
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
295
|
+
<FormFieldInput
|
|
296
|
+
value={value}
|
|
297
|
+
onChange={(e) => e.target.value.length <= maxLength && setValue(e.target.value)}
|
|
298
|
+
/>
|
|
299
|
+
</FormField>
|
|
300
|
+
<FormFieldDescription>
|
|
301
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
302
|
+
Описание поля
|
|
303
|
+
</Text>
|
|
304
|
+
<FormFieldCounter>
|
|
305
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
306
|
+
{value.length}/{maxLength}
|
|
307
|
+
</Text>
|
|
308
|
+
</FormFieldCounter>
|
|
309
|
+
</FormFieldDescription>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
276
313
|
```
|
|
277
314
|
|
|
278
|
-
|
|
279
|
-
- `SM` → 6px
|
|
280
|
-
- `MD` → 8px
|
|
281
|
-
- `LG` → 10px
|
|
315
|
+
### States
|
|
282
316
|
|
|
283
|
-
|
|
317
|
+
`stories/FormField/examples/StatesExample.tsx`
|
|
284
318
|
|
|
285
|
-
|
|
319
|
+
```tsx
|
|
320
|
+
import React, { useState } from "react";
|
|
321
|
+
import {
|
|
322
|
+
EFontType,
|
|
323
|
+
EFormFieldStatus,
|
|
324
|
+
ETextSize,
|
|
325
|
+
FormField,
|
|
326
|
+
FormFieldDescription,
|
|
327
|
+
FormFieldInput,
|
|
328
|
+
FormFieldLabel,
|
|
329
|
+
FormGroup,
|
|
330
|
+
Gap,
|
|
331
|
+
Text,
|
|
332
|
+
} from "@sberbusiness/triplex-next";
|
|
333
|
+
|
|
334
|
+
export const StatesExample = () => {
|
|
335
|
+
const [v, setV] = useState("");
|
|
336
|
+
const [e, setE] = useState("");
|
|
337
|
+
const [w, setW] = useState("");
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div style={{ maxWidth: "300px" }}>
|
|
341
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Default</div>
|
|
342
|
+
<FormGroup>
|
|
343
|
+
<FormField>
|
|
344
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
345
|
+
<FormFieldInput value={v} onChange={(x) => setV(x.target.value)} />
|
|
346
|
+
</FormField>
|
|
347
|
+
<FormFieldDescription>
|
|
348
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
349
|
+
Описание поля
|
|
350
|
+
</Text>
|
|
351
|
+
</FormFieldDescription>
|
|
352
|
+
</FormGroup>
|
|
353
|
+
<Gap size={24} />
|
|
354
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Error</div>
|
|
355
|
+
<FormGroup>
|
|
356
|
+
<FormField status={EFormFieldStatus.ERROR}>
|
|
357
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
358
|
+
<FormFieldInput value={e} onChange={(x) => setE(x.target.value)} />
|
|
359
|
+
</FormField>
|
|
360
|
+
<FormFieldDescription>
|
|
361
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.ERROR}>
|
|
362
|
+
Текст ошибки
|
|
363
|
+
</Text>
|
|
364
|
+
</FormFieldDescription>
|
|
365
|
+
</FormGroup>
|
|
366
|
+
<Gap size={24} />
|
|
367
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Warning</div>
|
|
368
|
+
<FormGroup>
|
|
369
|
+
<FormField status={EFormFieldStatus.WARNING}>
|
|
370
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
371
|
+
<FormFieldInput value={w} onChange={(x) => setW(x.target.value)} />
|
|
372
|
+
</FormField>
|
|
373
|
+
<FormFieldDescription>
|
|
374
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.WARNING}>
|
|
375
|
+
Текст предупреждения
|
|
376
|
+
</Text>
|
|
377
|
+
</FormFieldDescription>
|
|
378
|
+
</FormGroup>
|
|
379
|
+
<Gap size={24} />
|
|
380
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Disabled</div>
|
|
381
|
+
<FormGroup>
|
|
382
|
+
<FormField status={EFormFieldStatus.DISABLED}>
|
|
383
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
384
|
+
<FormFieldInput value="Value disabled" />
|
|
385
|
+
</FormField>
|
|
386
|
+
<FormFieldDescription>
|
|
387
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
388
|
+
Описание поля
|
|
389
|
+
</Text>
|
|
390
|
+
</FormFieldDescription>
|
|
391
|
+
</FormGroup>
|
|
392
|
+
</div>
|
|
393
|
+
);
|
|
394
|
+
};
|
|
395
|
+
```
|
|
286
396
|
|
|
287
|
-
###
|
|
288
|
-
- `TextField`, `TextareaField`, `SMSField`, `NumberField`, `AmountField`, `DateField`, `MonthYearField`, `DocumentNumberEdit` — типовые input-поля.
|
|
289
|
-
- `SelectField`, `SelectExtendedField`, `SuggestField`, `MultiselectField` — select-подобные (внутри обычно `FormFieldTarget`).
|
|
397
|
+
### Textarea
|
|
290
398
|
|
|
291
|
-
|
|
292
|
-
- `FormGroup` — группа `FormField`'ов в форме.
|
|
293
|
-
- `Button` / `ButtonIcon` — `FormFieldClear` наследует `ButtonIcon`.
|
|
294
|
-
- `Loader` — может рендериться в `FormFieldPostfix` для состояния загрузки.
|
|
399
|
+
`stories/FormField/examples/TextareaExample.tsx`
|
|
295
400
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
401
|
+
```tsx
|
|
402
|
+
import React, { useState } from "react";
|
|
403
|
+
import {
|
|
404
|
+
EFontType,
|
|
405
|
+
ETextSize,
|
|
406
|
+
FormField,
|
|
407
|
+
FormFieldCounter,
|
|
408
|
+
FormFieldDescription,
|
|
409
|
+
FormFieldLabel,
|
|
410
|
+
FormFieldTextarea,
|
|
411
|
+
FormGroup,
|
|
412
|
+
Text,
|
|
413
|
+
} from "@sberbusiness/triplex-next";
|
|
414
|
+
|
|
415
|
+
export const TextareaExample = () => {
|
|
416
|
+
const [value, setValue] = useState("");
|
|
417
|
+
const maxLength = 201;
|
|
418
|
+
return (
|
|
419
|
+
<div style={{ maxWidth: "300px" }}>
|
|
420
|
+
<FormGroup>
|
|
421
|
+
<FormField>
|
|
422
|
+
<FormFieldLabel>Название поля</FormFieldLabel>
|
|
423
|
+
<FormFieldTextarea
|
|
424
|
+
value={value}
|
|
425
|
+
onChange={(e) => e.target.value.length <= maxLength && setValue(e.target.value)}
|
|
426
|
+
/>
|
|
427
|
+
</FormField>
|
|
428
|
+
<FormFieldDescription>
|
|
429
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
430
|
+
Описание поля
|
|
431
|
+
</Text>
|
|
432
|
+
<FormFieldCounter>
|
|
433
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
434
|
+
{value.length}/{maxLength}
|
|
435
|
+
</Text>
|
|
436
|
+
</FormFieldCounter>
|
|
437
|
+
</FormFieldDescription>
|
|
438
|
+
</FormGroup>
|
|
439
|
+
</div>
|
|
440
|
+
);
|
|
441
|
+
};
|
|
442
|
+
```
|
|
299
443
|
|
|
300
|
-
|
|
444
|
+
### Sizes
|
|
301
445
|
|
|
302
|
-
|
|
446
|
+
`stories/FormField/examples/SizesExample.tsx`
|
|
303
447
|
|
|
304
|
-
|
|
305
|
-
|
|
448
|
+
```tsx
|
|
449
|
+
import React, { useState } from "react";
|
|
450
|
+
import { EComponentSize, FormField, FormFieldInput, FormFieldLabel, FormGroup } from "@sberbusiness/triplex-next";
|
|
451
|
+
|
|
452
|
+
export const SizesExample = () => {
|
|
453
|
+
const [sm, setSm] = useState("");
|
|
454
|
+
const [md, setMd] = useState("");
|
|
455
|
+
const [lg, setLg] = useState("");
|
|
456
|
+
return (
|
|
457
|
+
<div style={{ maxWidth: "400px" }}>
|
|
458
|
+
<div style={{ marginBottom: "32px" }}>
|
|
459
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>SM</div>
|
|
460
|
+
<FormGroup>
|
|
461
|
+
<FormField size={EComponentSize.SM}>
|
|
462
|
+
<FormFieldLabel>Маленькое поле</FormFieldLabel>
|
|
463
|
+
<FormFieldInput
|
|
464
|
+
value={sm}
|
|
465
|
+
onChange={(e) => setSm(e.target.value)}
|
|
466
|
+
placeholder="Введите текст..."
|
|
467
|
+
/>
|
|
468
|
+
</FormField>
|
|
469
|
+
</FormGroup>
|
|
470
|
+
</div>
|
|
471
|
+
<div style={{ marginBottom: "32px" }}>
|
|
472
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>MD</div>
|
|
473
|
+
<FormGroup>
|
|
474
|
+
<FormField size={EComponentSize.MD}>
|
|
475
|
+
<FormFieldLabel>Среднее поле</FormFieldLabel>
|
|
476
|
+
<FormFieldInput
|
|
477
|
+
value={md}
|
|
478
|
+
onChange={(e) => setMd(e.target.value)}
|
|
479
|
+
placeholder="Введите текст..."
|
|
480
|
+
/>
|
|
481
|
+
</FormField>
|
|
482
|
+
</FormGroup>
|
|
483
|
+
</div>
|
|
484
|
+
<div style={{ marginBottom: "32px" }}>
|
|
485
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>LG</div>
|
|
486
|
+
<FormGroup>
|
|
487
|
+
<FormField size={EComponentSize.LG}>
|
|
488
|
+
<FormFieldLabel>Большое поле</FormFieldLabel>
|
|
489
|
+
<FormFieldInput
|
|
490
|
+
value={lg}
|
|
491
|
+
onChange={(e) => setLg(e.target.value)}
|
|
492
|
+
placeholder="Введите текст..."
|
|
493
|
+
/>
|
|
494
|
+
</FormField>
|
|
495
|
+
</FormGroup>
|
|
496
|
+
</div>
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
};
|
|
500
|
+
```
|
|
306
501
|
|
|
307
|
-
|
|
308
|
-
|---|---|---|
|
|
309
|
-
| `Playground` | `PlaygroundExample.tsx` | Интерактив: status, size, label, placeholder, description. |
|
|
310
|
-
| `Default` | `DefaultExample.tsx` | Минимальная сборка FormField + Label + Input. |
|
|
311
|
-
| `WithPrefixAndPostfix` | `WithPrefixAndPostfixExample.tsx` | Иконки/контент в Prefix/Postfix + автовыравнивание label. |
|
|
312
|
-
| `WithClearButton` | `WithClearButtonExample.tsx` | `FormFieldClear` в Postfix, видимость при filled+hover. |
|
|
313
|
-
| `WithCounter` | `WithCounterExample.tsx` | `FormFieldDescription` + `FormFieldCounter`. |
|
|
314
|
-
| `States` | `StatesExample.tsx` | `DEFAULT` / `ERROR` / `WARNING` / `DISABLED`. |
|
|
315
|
-
| `Textarea` | `TextareaExample.tsx` | `FormFieldTextarea` вместо Input. |
|
|
316
|
-
| `Sizes` | `SizesExample.tsx` | SM / MD / LG. |
|
|
317
|
-
| `MaskedInput` | `MaskedInputExample.tsx` | `FormFieldMaskedInput` c готовыми presets. |
|
|
318
|
-
| `VisualTests` | `VisualTestsExample.tsx` | Screenshot-регрессия. |
|
|
502
|
+
### MaskedInput
|
|
319
503
|
|
|
320
|
-
|
|
504
|
+
`stories/FormField/examples/MaskedInputExample.tsx`
|
|
321
505
|
|
|
322
|
-
|
|
506
|
+
```tsx
|
|
507
|
+
import React, { useState } from "react";
|
|
508
|
+
import {
|
|
509
|
+
EFontType,
|
|
510
|
+
ETextSize,
|
|
511
|
+
FormField,
|
|
512
|
+
FormFieldDescription,
|
|
513
|
+
FormFieldLabel,
|
|
514
|
+
FormFieldMaskedInput,
|
|
515
|
+
FormGroup,
|
|
516
|
+
Gap,
|
|
517
|
+
Text,
|
|
518
|
+
} from "@sberbusiness/triplex-next";
|
|
519
|
+
|
|
520
|
+
export const MaskedInputExample = () => {
|
|
521
|
+
const [phoneValue, setPhoneValue] = useState("");
|
|
522
|
+
const [cardValue, setCardValue] = useState("");
|
|
523
|
+
return (
|
|
524
|
+
<div style={{ maxWidth: "300px" }}>
|
|
525
|
+
<FormGroup>
|
|
526
|
+
<FormField>
|
|
527
|
+
<FormFieldLabel>Номер телефона</FormFieldLabel>
|
|
528
|
+
<FormFieldMaskedInput
|
|
529
|
+
value={phoneValue}
|
|
530
|
+
onChange={(e) => setPhoneValue(e.target.value)}
|
|
531
|
+
mask={FormFieldMaskedInput.presets.masks.phone}
|
|
532
|
+
/>
|
|
533
|
+
</FormField>
|
|
534
|
+
<FormFieldDescription>
|
|
535
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
536
|
+
Описание поля
|
|
537
|
+
</Text>
|
|
538
|
+
</FormFieldDescription>
|
|
539
|
+
</FormGroup>
|
|
540
|
+
<Gap size={24} />
|
|
541
|
+
<FormGroup>
|
|
542
|
+
<FormField>
|
|
543
|
+
<FormFieldLabel>Номер карты</FormFieldLabel>
|
|
544
|
+
<FormFieldMaskedInput
|
|
545
|
+
value={cardValue}
|
|
546
|
+
onChange={(e) => setCardValue(e.target.value)}
|
|
547
|
+
mask={FormFieldMaskedInput.presets.masks.cardNumber}
|
|
548
|
+
placeholderMask={FormFieldMaskedInput.presets.placeholderMasks.cardNumber}
|
|
549
|
+
placeholder="Введите номер карты"
|
|
550
|
+
/>
|
|
551
|
+
</FormField>
|
|
552
|
+
<FormFieldDescription>
|
|
553
|
+
<Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
554
|
+
Описание поля
|
|
555
|
+
</Text>
|
|
556
|
+
</FormFieldDescription>
|
|
557
|
+
</FormGroup>
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
};
|
|
561
|
+
```
|
|
323
562
|
|
|
324
|
-
| Дата | Изменение |
|
|
325
|
-
|---|---|
|
|
326
|
-
| 2026-04-18 | Создан документ |
|