@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.
Files changed (46) hide show
  1. package/bundle/components/AlertContext.md +268 -0
  2. package/bundle/components/Button.md +5 -2
  3. package/bundle/components/ButtonDropdown.md +9 -1
  4. package/bundle/components/ButtonDropdownExtended.md +422 -0
  5. package/bundle/components/ButtonIcon.md +7 -1
  6. package/bundle/components/Calendar.md +183 -368
  7. package/bundle/components/Caption.md +291 -0
  8. package/bundle/components/Chip.md +310 -314
  9. package/bundle/components/CodeText.md +197 -0
  10. package/bundle/components/Col.md +311 -162
  11. package/bundle/components/CollapsibleTree.md +6 -3
  12. package/bundle/components/CollapsibleTreeExtended.md +6 -2
  13. package/bundle/components/Dropdown.md +748 -346
  14. package/bundle/components/FormField.md +468 -232
  15. package/bundle/components/IconWrapper.md +142 -89
  16. package/bundle/components/Island.md +221 -129
  17. package/bundle/components/KeyDownListener.md +215 -102
  18. package/bundle/components/LightBox.md +2252 -233
  19. package/bundle/components/List.md +26 -1
  20. package/bundle/components/ListActionItem.md +157 -0
  21. package/bundle/components/ListItem.md +34 -9
  22. package/bundle/components/ListItemSelectable.md +2 -2
  23. package/bundle/components/ListMaster.md +4 -5
  24. package/bundle/components/ListTableItem.md +391 -0
  25. package/bundle/components/LoaderMiddle.md +265 -0
  26. package/bundle/components/LoaderScreen.md +178 -81
  27. package/bundle/components/Notification.md +21 -21
  28. package/bundle/components/Page.md +1 -0
  29. package/bundle/components/Portal.md +131 -67
  30. package/bundle/components/Row.md +171 -69
  31. package/bundle/components/Text.md +320 -0
  32. package/bundle/components/TextField.md +364 -97
  33. package/bundle/components/ThemeProvider.md +305 -117
  34. package/bundle/components/Title.md +296 -0
  35. package/bundle/components/Tooltip.md +421 -181
  36. package/bundle/guides/commits.md +126 -24
  37. package/bundle/guides/context.md +1 -1
  38. package/bundle/guides/tests.md +42 -0
  39. package/bundle/manifest.json +109 -23
  40. package/bundle/release-notes/1.37.0.mdx +42 -1
  41. package/bundle/release-notes/1.38.0.mdx +36 -0
  42. package/bundle/release-notes/1.39.0.mdx +66 -0
  43. package/bundle/release-notes/1.40.0.mdx +27 -0
  44. package/bundle/release-notes/1.41.0.mdx +83 -0
  45. package/bundle/release-notes/1.42.0.mdx +7 -0
  46. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: FormField
3
- category: Forms
4
- figma-node: ""
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
- Низкоуровневый **compound-примитив** для построения полей ввода: контейнер,
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
- Используй `FormField` когда: строишь новый тип поля ввода, которого нет в
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
- ## Compound-структура
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
- Корневой `FormField` — provider `FormFieldContext`. Контекст хранит `size`,
106
- `status`, `targetId`, `labelId`, `prefixWidth`, `postfixWidth`, `filled`,
107
- `focused`, `active` и сеттеры. Под-компоненты читают/пишут это состояние.
44
+ `FormField` — контейнер и провайдер контекста. Собственного «значения» у него нет: состояния `filled` и `focused` вычисляются вложенными элементами ввода и поднимаются в контекст через сеттеры.
108
45
 
109
- ---
46
+ ### Обязательные props
110
47
 
111
- ## Варианты и props
48
+ Обязательных custom-props нет. Практически всегда передаётся `children` — субкомпоненты семейства.
112
49
 
113
- ### FormField — `IFormFieldProps extends React.HTMLAttributes<HTMLDivElement>`
50
+ ### Опциональные props
114
51
 
115
- | Prop | Тип | Default | Описание |
52
+ | Prop | Тип | По умолчанию | Описание |
116
53
  |---|---|---|---|
117
- | `size` | `EComponentSize` | `LG` | Размер (`SM`/`MD`/`LG`); управляет высотой, радиусом, шрифтом. |
118
- | `status` | `EFormFieldStatus` | `DEFAULT` | `DEFAULT` / `DISABLED` / `ERROR` / `WARNING`. |
119
- | `active` | `boolean` | `false` | Принудительно активное состояние (например, при открытом dropdown). Фактическое `active` в контексте = `active || focused`. |
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
- ### Под-компоненты — ключевые props
60
+ ### Особенности поведения
132
61
 
133
- | Компонент | Важные props | Заметки |
134
- |---|---|---|
135
- | `FormFieldInput` | `render?: (props, ref) => ReactNode`, все `InputHTMLAttributes` | `render` позволяет подключить сторонний `<input>` (валидация, маска), получая наши стили/id/onFocus/onBlur. |
136
- | `FormFieldTextarea` | все `TextareaHTMLAttributes` | Симметричен Input, без `render`. |
137
- | `FormFieldMaskedInput` | `value: string`, `mask: TFormFieldMaskedInputMask`, `placeholderMask?`, `placeholderChar = "0"` | Использует `react-text-mask`. `presets` — статичное поле с готовыми масками (`phone`, `inn`, `cardNumber`, `snils`, `date`, `swiftCode` и др.) и их placeholder'ами. |
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
- `FormFieldPrefix`/`FormFieldPostfix` после рендера делают `getBoundingClientRect().width` → пишут в контекст → корень перерендеривается с новым padding. Если prefix/postfix меняют ширину динамически (например, текстом) — padding обновится на следующем рендер-цикле.
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
- `FormFieldLabel` использует те же `prefixWidth`/`postfixWidth` для вычисления `left`/`right`, чтобы плавающий label был ровно над инпутом, не под иконками.
95
+ ---
163
96
 
164
- Дефолт `TARGET_PADDING_X_DEFAULT = 12` — стартовое значение до первого измерения префикса/постфикса (и когда их нет).
97
+ ## Инварианты
165
98
 
166
- ### Floating label — `filled || active`
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
- Label плавает наверх, когда:
169
- - поле заполнено (`filled`), либо
170
- - поле активно (`active === active-prop || focused`).
109
+ ---
171
110
 
172
- `FormFieldInput`/`FormFieldTextarea` вычисляют `filled` через `isFilled(value)`:
173
- - `undefined` → `false`
174
- - `number` → `true`
175
- - строка → `value.length !== 0`
111
+ ## Accessibility
176
112
 
177
- Для select-подобных обёрток `FormFieldTarget` пишет `filled = children.length !== 0`.
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
- Принудительно задать `floating` можно prop'ом (для кейсов, где лейбл всегда сверху — например, поле с картинками).
120
+ ---
180
121
 
181
- ### Autofill detection через CSS-анимации
122
+ ## Связанные компоненты
182
123
 
183
- `FormFieldInput`/`FormFieldTextarea` слушают `onAnimationStart` и проверяют `animationName`:
184
- - `autofill-applied-hook*` → `setFilled(true)`
185
- - `autofill-cancelled-hook*` → `syncFilled(value)`
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
- Это единственный способ отловить браузерное автозаполнение, которое не триггерит `onChange`. Стороны анимации объявлены в CSS. Не ломать имена — они парсятся в JS.
136
+ ---
188
137
 
189
- ### `FormFieldInput.render` — точка расширения
138
+ ## Stories
190
139
 
191
- Через `render` пробрасываются все общие props (стили, id, `onFocus/onBlur`, `onAnimationStart`, `onChange`, `disabled`, `size`) и ref. Используется, например, в `FormFieldMaskedInput` — чтобы обернуть `react-text-mask`.
140
+ Основные истории: `stories/FormField/FormField.stories.tsx`
141
+ Файлы примеров: `stories/FormField/examples/`
192
142
 
193
- ### `FormFieldMaskedInput` — рендерит ДВА инпута
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
- - Первый (readOnly, `tabIndex=-1`, `aria-hidden`) — рисует placeholder-маску (например, `дд.мм.гггг`).
196
- - Второй (`react-text-mask`) — принимает ввод, без guide'а, `value` берёт через `conformToMask`.
197
- - Это интегрированное legacy-решение — не заменять на один инпут без согласования, placeholder-маска с подсветкой уже введённых символов требует двухслойной разметки.
198
- - Специальная обработка для масок `phone` и `swiftCode` через `pipe` (нормализация `7`/`8` → `+7 (`, uppercase для SWIFT).
156
+ ---
199
157
 
200
- ### `FormFieldClear` — `preventDefault` на mouseDown
158
+ ## История изменений
201
159
 
202
- Кнопка очистки лежит в `FormFieldPostfix`. При клике `onMouseDown` вызывает `event.preventDefault()` — иначе поле теряет фокус (= `focused = false`, = label падает, = filled пересчитывается по пустому значению). Не удалять этот обработчик.
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
- Видимость `FormFieldClear` управляется CSS в `FormField.module.less`:
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
- ### ID-связка label ↔ target
169
+ ## Примеры использования
215
170
 
216
- - Каждый target-компонент (`Input`/`Textarea`/`Target`) генерирует `id` через `uniqueId("input_"/"textarea_"/"target_")`, если не передан снаружи, и пишет его в `FormFieldContext.targetId` через `useLayoutEffect`.
217
- - `FormFieldLabel` берёт `targetId` из контекста и ставит в `htmlFor`.
218
- - `FormFieldLabel` сам генерирует `id` и пишет в `labelId` → `FormFieldTarget` использует его как `aria-labelledby`.
171
+ ### Default
219
172
 
220
- Из-за `uniqueId` (lodash-es, модульный счётчик) id'ы не SSR-safe. Это осознанный legacy-выбор; при SSR используй `id` prop с явным значением.
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
- - **`forwardRef`** — на всех under компонентах кроме `FormFieldDescription` и `FormFieldCounter` (они `React.FC`, т.к. не ожидается ref наружу). `FormField`, `FormFieldInput`, `FormFieldTextarea`, `FormFieldTarget`, `FormFieldLabel`, `FormFieldPrefix`, `FormFieldPostfix`, `FormFieldClear` — обязательно `forwardRef`.
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
- ## Accessibility
235
+ ### WithClearButton
239
236
 
240
- - **`<input>`/`<textarea>`** — нативные, полная поддержка клавиатуры. `disabled` ставится библиотекой, когда `status === DISABLED`.
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
- **Корневой контейнер (`FormField.module.less`):**
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
- --triplex-next-FormField-Input_Color_Default / _Disabled
272
- --triplex-next-FormField-Label_Color_Default / _Disabled
273
- --triplex-next-FormField-Placeholder_Color
274
- --triplex-next-FormField-Target_Color_Default / _Disabled
275
- --triplex-next-FormField-Target_PlaceholderColor_Default
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
- Border-radius задан литералами в `.sm` / `.md` / `.lg`:
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
- ### Высокоуровневые обёртки (используют FormField внутри)
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
- - `FormFieldClear` импортирует `ButtonIcon` из `../Button/ButtonIcon` — не дублировать.
298
- - `FormFieldMaskedInput` импортирует `FormFieldInput` по абсолютному пути `@sberbusiness/triplex-next/components/FormField/components/FormFieldInput` — исторически, работает.
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
- ## Stories
446
+ `stories/FormField/examples/SizesExample.tsx`
303
447
 
304
- Основные: `stories/FormField/FormField.stories.tsx`
305
- Примеры: `stories/FormField/examples/`
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
- | Story | Example file | Что демонстрирует |
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 | Создан документ |