@sberbusiness/triplex-next-mcp-server 1.41.0-beta → 1.43.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 (41) hide show
  1. package/bundle/components/AlertProcess.md +460 -0
  2. package/bundle/components/Amount.md +145 -169
  3. package/bundle/components/Badge.md +155 -219
  4. package/bundle/components/Body.md +108 -116
  5. package/bundle/components/Button.md +1 -0
  6. package/bundle/components/CarouselExtended.md +355 -194
  7. package/bundle/components/Checkbox.md +7 -1
  8. package/bundle/components/CheckboxTreeExtended.md +149 -248
  9. package/bundle/components/CheckboxXGroup.md +2 -0
  10. package/bundle/components/CheckboxYGroup.md +7 -2
  11. package/bundle/components/ChipGroup.md +195 -126
  12. package/bundle/components/ComposedKeyDownListener.md +323 -0
  13. package/bundle/components/FooterPage.md +2 -1
  14. package/bundle/components/HeaderPage.md +2 -1
  15. package/bundle/components/Island.md +100 -12
  16. package/bundle/components/KeyDownListener.md +11 -72
  17. package/bundle/components/LightBox.md +6 -0
  18. package/bundle/components/LoaderSmall.md +230 -0
  19. package/bundle/components/MaskedField.md +481 -0
  20. package/bundle/components/MediaWidth.md +252 -108
  21. package/bundle/components/MobileView.md +192 -68
  22. package/bundle/components/ModalWindow.md +3 -1
  23. package/bundle/components/Notification.md +21 -3
  24. package/bundle/components/Page.md +3 -1
  25. package/bundle/components/Pagination.md +20 -0
  26. package/bundle/components/SelectExtendedField.md +691 -236
  27. package/bundle/components/SliderExtended.md +358 -222
  28. package/bundle/components/TabsExtended.md +380 -194
  29. package/bundle/components/UnorderedList.md +254 -0
  30. package/bundle/components/UnorderedListExtended.md +255 -0
  31. package/bundle/components/WindowResizeListener.md +249 -0
  32. package/bundle/guides/commits.md +66 -3
  33. package/bundle/guides/context.md +26 -0
  34. package/bundle/guides/stories.md +3 -1
  35. package/bundle/guides/template.md +1 -1
  36. package/bundle/manifest.json +64 -21
  37. package/bundle/release-notes/1.41.1.mdx +18 -0
  38. package/bundle/release-notes/1.42.0.mdx +81 -0
  39. package/bundle/release-notes/1.43.0.mdx +94 -0
  40. package/bundle/release-notes/1.44.0.mdx +7 -0
  41. package/package.json +1 -1
@@ -0,0 +1,481 @@
1
+ ---
2
+ component: MaskedField
3
+ category: TextFields
4
+ related: [TextField, FormField, FormFieldMaskedInput, FormGroup, DateField, MonthYearField]
5
+ tokens: []
6
+ stories: stories/MaskedField/MaskedField.stories.tsx
7
+ version: "1.0"
8
+ ---
9
+
10
+ # MaskedField
11
+
12
+ ## Назначение
13
+
14
+ Компонент однострочного ввода по маске — то же поле, что `TextField`, но вместо обычного input внутри используется `FormFieldMaskedInput`: ввод форматируется на лету (телефон, дата, номер карты, ИНН, СНИЛС и другие пресеты), а незаполненная часть маски подсказывается пользователю. Композиция над семейством `FormField` (`FormGroup` → `FormField` → `FormFieldMaskedInput` + `FormFieldLabel`/`FormFieldPrefix`/`FormFieldPostfix`/`FormFieldDescription`/`FormFieldCounter`).
15
+
16
+ Используй когда: значение имеет фиксированный формат, и его нужно форматировать во время ввода — номер телефона, дата, номер счёта/карты, ИНН/КПП/БИК, СНИЛС, паспортные данные, идентификаторы ЖКУ. Готовые маски лежат в `FormFieldMaskedInput.presets.masks`, подсказки формата — в `FormFieldMaskedInput.presets.placeholderMasks`.
17
+
18
+ Не используй когда: формат произвольный (`TextField`), нужен календарный выбор даты (`DateField`) или месяца (`MonthYearField`), нужна денежная сумма с разрядами (`AmountField`), многострочный ввод (`TextareaField`) или выбор из подсказок (`SuggestField`).
19
+
20
+ ---
21
+
22
+ ## Варианты и props
23
+
24
+ `MaskedField` — тонкая обёртка без собственного state: `maskedInputProps` уходит в `FormFieldMaskedInput`, остальные props — в `TextFieldBase` (и далее в `FormField`). Вся логика маски живёт в `FormFieldMaskedInput` (на базе `react-text-mask`).
25
+
26
+ ### Обязательные props
27
+
28
+ | Prop | Тип | Описание |
29
+ |---|---|---|
30
+ | `maskedInputProps` | `IFormFieldMaskedInputProps & { ref? }` | Свойства поля с маской. Обязательные внутри: `mask` (массив строк и регулярных выражений) и `value` (строка, поле контролируемое). Опционально: `placeholderMask` (текстовая подсказка формата, например `дд.мм.гггг`), `placeholderChar` (символ-заполнитель, по умолчанию `"0"`), `forwardedRef` (ссылка на input), `ref` (ссылка на div-обёртку), `onChange` и остальные атрибуты `<input>`. |
31
+
32
+ ### Опциональные props
33
+
34
+ | Prop | Тип | По умолчанию | Описание |
35
+ |---|---|---|---|
36
+ | `size` | `EComponentSize` | `LG` | Размер поля: SM / MD / LG. |
37
+ | `status` | `EFormFieldStatus` | `DEFAULT` | Статус: default / error / warning / disabled. `DISABLED` автоматически ставит `disabled` на input. |
38
+ | `active` | `boolean` | `false` | Принудительно активное (focused) визуальное состояние рамки. |
39
+ | `label` | `ReactNode` | — | Плавающий лейбл; связывается с input через `htmlFor`. |
40
+ | `prefix` | `ReactNode` | — | Контент слева от input, рендерится в `FormFieldPrefix`. |
41
+ | `postfix` | `ReactNode` | — | Контент справа от input (`FormFieldClear`, `HelpBox`, иконка), рендерится в `FormFieldPostfix`. |
42
+ | `description` | `ReactNode` | — | Описание под полем (`FormFieldDescription`); цвет наследует статус. |
43
+ | `counter` | `ReactNode` | — | Счётчик символов справа под полем (`FormFieldCounter`). Блок описания рендерится, если задан `description` ИЛИ `counter`. |
44
+
45
+ Остальные неперечисленные props (`className`, data-атрибуты, div-атрибуты) пробрасываются на корневой `FormField` (div).
46
+
47
+ ### Особенности поведения
48
+
49
+ - **Значение всегда контролируемое.** `maskedInputProps.value` пропускается через `conformToMask`, поэтому в `onChange` приходит уже отформатированное значение (`"12.12."` после ввода четырёх цифр даты). Хранить в state можно как форматированное, так и «сырое» значение — маска применяется к обоим.
50
+ - **`onChange` вызывается только при реальном изменении** отформатированного значения: повторное событие с тем же значением игнорируется.
51
+ - **Подсказка маски** (`placeholderMask` или заполнение символом `placeholderChar`) рисуется отдельным `aria-hidden` слоем поверх поля и показывается, только когда поле в фокусе или заполнено. Если задан обычный `placeholder`, у пустого поля показывается он, а не маска.
52
+ - **Пресеты масок для телефона и SWIFT обрабатываются особо:** телефон нормализует вставку номера с `7`/`8`/`+7` в начале, SWIFT-код приводится к верхнему регистру.
53
+ - Ограничение длины задаёт сама маска — `maxLength` не нужен; `counter`, если он нужен, считает потребитель.
54
+ - Кнопка очистки не встроена — передаётся потребителем как `postfix={<FormFieldClear onClick={...} />}`.
55
+
56
+ ---
57
+
58
+ ## Дизайн-токены
59
+
60
+ Собственных CSS-токенов и стилей у `MaskedField` нет — поэтому `tokens` во frontmatter пустой. Внешний вид наследуется от семейства `FormField`; слой подсказки маски рисует `FormFieldMaskedInput` своим токеном `--triplex-next-FormField-Placeholder_Color`.
61
+
62
+ ---
63
+
64
+ ## Инварианты
65
+
66
+ - `forwardRef` на `MaskedField` — не убирать. Внешний `ref` указывает на корневой `<div>` `FormField`.
67
+ - Публичный API (`IMaskedFieldProps`: `maskedInputProps` + `label`/`prefix`/`postfix`/`description`/`counter` + унаследованные `size`/`status`/`active`) — изменение имён/типов/значений enum — breaking change.
68
+ - Barrel `index.ts` экспортирует `TextField` и `MaskedField` — состав экспортов не менять. `TextFieldBase` намеренно НЕ экспортируется — приватная база.
69
+ - Ссылка на элемент input передаётся только через `maskedInputProps.forwardedRef`. `maskedInputProps.ref` (`React.RefObject<HTMLDivElement>`) попадает на корневой `<div>` обёртки `FormFieldMaskedInput` — это ref самого `FormFieldMaskedInput`, а не поля ввода.
70
+ - `maskedInputProps.value` обязателен: `FormFieldMaskedInput` — контролируемый компонент. `defaultValue` исключён из props (`Omit<MaskedInputProps, ... | "defaultValue">`) и не передаётся внутрь `<input>`: `react-text-mask` подставляет его равным `value`, а на контролируемом поле это даёт предупреждение React о смешении controlled и uncontrolled.
71
+ - Маски и подсказки берутся из `FormFieldMaskedInput.presets` — состав пресетов и их значения часть публичного API.
72
+ - Уникальный `id` для связки label↔input генерируется через `lodash uniqueId` в `FormFieldInput` — не заменять на `useId` (React 17 совместимость через release-0).
73
+
74
+ ---
75
+
76
+ ## Accessibility
77
+
78
+ - Лейбл — настоящий `<label htmlFor>`, связанный с `id` инпута (id генерируется автоматически или берётся из `maskedInputProps.id`).
79
+ - Слой подсказки маски помечен `aria-hidden="true"` и не читается скринридером — формат ввода дополнительно стоит объяснить в `description` или `placeholder`.
80
+ - `status={EFormFieldStatus.DISABLED}` выставляет нативный атрибут `disabled` на input — поле штатно выпадает из таб-обхода.
81
+ - Семантика ошибки (`aria-invalid`, `aria-describedby`) не хардкодится — при необходимости потребитель передаёт атрибуты через `maskedInputProps`.
82
+ - Собственной клавиатурной логики нет; позицию каретки при вводе и вставке ведёт `react-text-mask`.
83
+
84
+ ---
85
+
86
+ ## Связанные компоненты
87
+
88
+ - `TextField` — сосед по папке: то же поле, но с обычным `FormFieldInput`. Документируется отдельно.
89
+ - `TextFieldBase` (внутренний, не в barrel) — базовая раскладка `FormGroup`/`FormField` + слоты `prefix`/`children`/`label`/`postfix` и блок `description`/`counter`. На нём построены `TextField` и `MaskedField`. Описан в `TextField-ai.md`.
90
+ - `FormFieldMaskedInput` — сам элемент ввода с маской и статическим свойством `presets` (`masks`, `placeholderMasks`). Используется напрямую, когда нужна нестандартная композиция поля.
91
+ - `FormFieldClear` — кнопка очистки, передаётся в `postfix`.
92
+ - `FormGroup` / `FormField` — контейнеры раскладки поля и описания.
93
+ - `DateField`, `MonthYearField`, `AmountField`, `SuggestField`, `TextareaField` — специализированные поля той же группы `TextFields`.
94
+
95
+ ---
96
+
97
+ ## Stories
98
+
99
+ Основные истории: `stories/MaskedField/MaskedField.stories.tsx`
100
+ Файлы примеров: `stories/MaskedField/examples/`
101
+
102
+ | Story | Example file | Что демонстрирует |
103
+ |---|---|---|
104
+ | `Playground` | `Playground.tsx` | Интерактивный контроль size / status / label / prefix / postfix / description / counter и выбор маски из пресетов |
105
+ | `Default` | `Default.tsx` | Минимальное контролируемое поле с маской телефона |
106
+ | `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG на пустом поле |
107
+ | `Statuses` | `Statuses.tsx` | Статусы default / error / warning / disabled на пустом поле |
108
+ | `WithPlaceholderMask` | `WithPlaceholderMask.tsx` | Подсказка формата: поле с `placeholderMask` против поля без него |
109
+ | `PassRefToInput` | `PassRefToInput.tsx` | Проброс ref на input через `maskedInputProps.forwardedRef` |
110
+ | `AllMasks` | `AllMasks.tsx` | Все пресеты масок `FormFieldMaskedInput.presets.masks` с подписями |
111
+ | `Production` | `Production.tsx` | Production-композиция (`Example: production`): телефон, кнопка очистки и `HelpBox` в постфиксе, description со ссылкой |
112
+ | `VisualTests` | `VisualTests.tsx` | Скриншот-регрессия: фокус с подсказкой маски (через `play`), частично заполненная маска, заполненные размеры, prefix/postfix, description/counter, статусы |
113
+
114
+ ---
115
+
116
+ ## История изменений
117
+
118
+ | Дата | Изменение |
119
+ |---|---|
120
+ | 2026-08-07 | Создан документ (TRI-56). AI-рефакторинг MaskedField, unit-тесты, миграция stories на modern pattern |
121
+ | 2026-08-11 | Тип `maskedInputProps.ref` исправлен на `React.RefObject<HTMLDivElement>` — соответствует фактическому ref-таргету (правки по ревью PR #515) |
122
+ | 2026-08-12 | Исправления в `FormFieldMaskedInput` по ревью PR #515: убрана мутация prop `value` в `getValue()`, `defaultValue` больше не уходит в `<input>` (предупреждение React о controlled/uncontrolled) и исключён из `IFormFieldMaskedInputProps` |
123
+
124
+ ---
125
+
126
+ ## Примеры использования
127
+
128
+ ### Default
129
+
130
+ `stories/MaskedField/examples/Default.tsx`
131
+
132
+ ```tsx
133
+ import React, { useState } from "react";
134
+ import { MaskedField, FormFieldMaskedInput } from "@sberbusiness/triplex-next";
135
+
136
+ export const Default = () => {
137
+ const [value, setValue] = useState("");
138
+
139
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setValue(event.target.value);
140
+
141
+ return (
142
+ <div style={{ maxWidth: "300px" }}>
143
+ <MaskedField
144
+ label="Label"
145
+ maskedInputProps={{
146
+ mask: FormFieldMaskedInput.presets.masks.phone,
147
+ value,
148
+ onChange: handleChange,
149
+ }}
150
+ />
151
+ </div>
152
+ );
153
+ };
154
+ ```
155
+
156
+ ### Sizes
157
+
158
+ `stories/MaskedField/examples/Sizes.tsx`
159
+
160
+ ```tsx
161
+ import React, { useState } from "react";
162
+ import { MaskedField, FormFieldMaskedInput, EComponentSize } from "@sberbusiness/triplex-next";
163
+
164
+ interface ISizeItemProps {
165
+ size: EComponentSize;
166
+ }
167
+
168
+ const SizeItem = ({ size }: ISizeItemProps) => {
169
+ const [value, setValue] = useState("");
170
+
171
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setValue(event.target.value);
172
+
173
+ return (
174
+ <div>
175
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
176
+ <MaskedField
177
+ size={size}
178
+ label="Label"
179
+ maskedInputProps={{
180
+ mask: FormFieldMaskedInput.presets.masks.phone,
181
+ value,
182
+ onChange: handleChange,
183
+ }}
184
+ />
185
+ </div>
186
+ );
187
+ };
188
+
189
+ const SIZES = Object.values(EComponentSize);
190
+
191
+ export const Sizes = () => (
192
+ <div style={{ maxWidth: "300px", display: "flex", flexDirection: "column", gap: "16px" }}>
193
+ {SIZES.map((size) => (
194
+ <SizeItem key={size} size={size} />
195
+ ))}
196
+ </div>
197
+ );
198
+ ```
199
+
200
+ ### Statuses
201
+
202
+ `stories/MaskedField/examples/Statuses.tsx`
203
+
204
+ ```tsx
205
+ import React, { useState } from "react";
206
+ import { MaskedField, FormFieldMaskedInput, EFormFieldStatus } from "@sberbusiness/triplex-next";
207
+
208
+ interface IStatusItemProps {
209
+ status: EFormFieldStatus;
210
+ }
211
+
212
+ const StatusItem = ({ status }: IStatusItemProps) => {
213
+ const [value, setValue] = useState("");
214
+
215
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setValue(event.target.value);
216
+
217
+ return (
218
+ <div>
219
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{status.toUpperCase()}</div>
220
+ <MaskedField
221
+ status={status}
222
+ label="Label"
223
+ maskedInputProps={{
224
+ mask: FormFieldMaskedInput.presets.masks.phone,
225
+ value,
226
+ onChange: handleChange,
227
+ }}
228
+ />
229
+ </div>
230
+ );
231
+ };
232
+
233
+ const STATUSES = Object.values(EFormFieldStatus);
234
+
235
+ export const Statuses = () => (
236
+ <div style={{ maxWidth: "300px", display: "flex", flexDirection: "column", gap: "16px" }}>
237
+ {STATUSES.map((status) => (
238
+ <StatusItem key={status} status={status} />
239
+ ))}
240
+ </div>
241
+ );
242
+ ```
243
+
244
+ ### WithPlaceholderMask
245
+
246
+ `stories/MaskedField/examples/WithPlaceholderMask.tsx`
247
+
248
+ ```tsx
249
+ import React, { useState } from "react";
250
+ import { MaskedField, FormFieldMaskedInput } from "@sberbusiness/triplex-next";
251
+
252
+ /**
253
+ * placeholderMask подсказывает формат ввода: незаполненная часть маски отображается
254
+ * за введённым значением. Без него незаполненная часть заполняется символом placeholderChar.
255
+ */
256
+ export const WithPlaceholderMask = () => {
257
+ const [valueWithMask, setValueWithMask] = useState("12");
258
+ const [valueWithoutMask, setValueWithoutMask] = useState("12");
259
+
260
+ const handleWithMaskChange = (event: React.ChangeEvent<HTMLInputElement>) => setValueWithMask(event.target.value);
261
+
262
+ const handleWithoutMaskChange = (event: React.ChangeEvent<HTMLInputElement>) =>
263
+ setValueWithoutMask(event.target.value);
264
+
265
+ return (
266
+ <div style={{ maxWidth: "300px", display: "flex", flexDirection: "column", gap: "16px" }}>
267
+ <div>
268
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>С placeholderMask</div>
269
+ <MaskedField
270
+ label="Дата"
271
+ maskedInputProps={{
272
+ mask: FormFieldMaskedInput.presets.masks.date,
273
+ placeholderMask: FormFieldMaskedInput.presets.placeholderMasks.date,
274
+ value: valueWithMask,
275
+ onChange: handleWithMaskChange,
276
+ }}
277
+ />
278
+ </div>
279
+
280
+ <div>
281
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Без placeholderMask</div>
282
+ <MaskedField
283
+ label="Дата"
284
+ maskedInputProps={{
285
+ mask: FormFieldMaskedInput.presets.masks.date,
286
+ value: valueWithoutMask,
287
+ onChange: handleWithoutMaskChange,
288
+ }}
289
+ />
290
+ </div>
291
+ </div>
292
+ );
293
+ };
294
+ ```
295
+
296
+ ### PassRefToInput
297
+
298
+ `stories/MaskedField/examples/PassRefToInput.tsx`
299
+
300
+ ```tsx
301
+ import React, { useRef, useState } from "react";
302
+ import { MaskedField, FormFieldMaskedInput } from "@sberbusiness/triplex-next";
303
+
304
+ /** Ссылка на элемент input передаётся через maskedInputProps.forwardedRef. */
305
+ export const PassRefToInput = () => {
306
+ const [value, setValue] = useState("");
307
+ const inputRef = useRef<HTMLInputElement>(null);
308
+
309
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setValue(event.target.value);
310
+
311
+ return (
312
+ <div style={{ maxWidth: "300px" }}>
313
+ <MaskedField
314
+ label="Label"
315
+ maskedInputProps={{
316
+ forwardedRef: inputRef,
317
+ mask: FormFieldMaskedInput.presets.masks.phone,
318
+ value,
319
+ onChange: handleChange,
320
+ }}
321
+ />
322
+ </div>
323
+ );
324
+ };
325
+ ```
326
+
327
+ ### AllMasks
328
+
329
+ `stories/MaskedField/examples/AllMasks.tsx`
330
+
331
+ ```tsx
332
+ import React, { useState } from "react";
333
+ import { MaskedField, FormFieldMaskedInput, TFormFieldMaskedInputMask } from "@sberbusiness/triplex-next";
334
+
335
+ const { masks, placeholderMasks } = FormFieldMaskedInput.presets;
336
+
337
+ interface IMaskItem {
338
+ /** Ключ пресета, используется как ключ значения в state. */
339
+ key: string;
340
+ /** Лейбл поля. */
341
+ label: string;
342
+ /** Маска из пресетов FormFieldMaskedInput. */
343
+ mask: TFormFieldMaskedInputMask;
344
+ /** Плейсхолдер маски, если он есть в пресетах. */
345
+ placeholderMask?: string;
346
+ }
347
+
348
+ const MASK_ITEMS: Array<IMaskItem> = [
349
+ { key: "account", label: "Номер счёта", mask: masks.account },
350
+ { key: "bic", label: "БИК", mask: masks.bic },
351
+ { key: "carNumber", label: "Номер автомобиля", mask: masks.carNumber, placeholderMask: placeholderMasks.carNumber },
352
+ { key: "cardNumber", label: "Номер карты", mask: masks.cardNumber, placeholderMask: placeholderMasks.cardNumber },
353
+ { key: "date", label: "Дата", mask: masks.date, placeholderMask: placeholderMasks.date },
354
+ { key: "driversLicense", label: "Водительское удостоверение", mask: masks.driversLicense },
355
+ { key: "inn", label: "ИНН", mask: masks.inn, placeholderMask: placeholderMasks.inn },
356
+ { key: "kbk", label: "КБК", mask: masks.kbk },
357
+ { key: "kpp", label: "КПП", mask: masks.kpp },
358
+ { key: "latitude", label: "Широта", mask: masks.latitude },
359
+ { key: "longitude", label: "Долгота", mask: masks.longitude },
360
+ { key: "ogrn", label: "ОГРН", mask: masks.ogrn },
361
+ { key: "oktmo", label: "ОКТМО", mask: masks.oktmo },
362
+ { key: "passportSeries", label: "Серия паспорта", mask: masks.passport.series },
363
+ { key: "passportNumber", label: "Номер паспорта", mask: masks.passport.number },
364
+ { key: "passportDepartmentCode", label: "Код подразделения", mask: masks.passport.departmentCode },
365
+ { key: "phone", label: "Номер телефона", mask: masks.phone },
366
+ {
367
+ key: "phoneExtension",
368
+ label: "Добавочный номер",
369
+ mask: masks.phoneExtension,
370
+ placeholderMask: placeholderMasks.phoneExtension,
371
+ },
372
+ { key: "postalCode", label: "Почтовый индекс", mask: masks.postalCode },
373
+ { key: "snils", label: "СНИЛС", mask: masks.snils },
374
+ { key: "swiftCode", label: "SWIFT-код", mask: masks.swiftCode, placeholderMask: placeholderMasks.swiftCode },
375
+ { key: "time", label: "Время", mask: masks.time, placeholderMask: placeholderMasks.time },
376
+ { key: "uin", label: "УИН", mask: masks.uin, placeholderMask: placeholderMasks.uin },
377
+ {
378
+ key: "zhkuAccount",
379
+ label: "Лицевой счёт ЖКУ",
380
+ mask: masks.zhkuAccount,
381
+ placeholderMask: placeholderMasks.zhkuAccount,
382
+ },
383
+ { key: "zhkuId", label: "Идентификатор ЖКУ", mask: masks.zhkuId, placeholderMask: placeholderMasks.zhkuId },
384
+ {
385
+ key: "zhkuPaymentDocumentId",
386
+ label: "Платёжный документ ЖКУ",
387
+ mask: masks.zhkuPaymentDocumentId,
388
+ placeholderMask: placeholderMasks.zhkuPaymentDocumentId,
389
+ },
390
+ ];
391
+
392
+ export const AllMasks = () => {
393
+ const [values, setValues] = useState<Record<string, string>>({});
394
+
395
+ const handleChange = (key: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
396
+ const { value } = event.target;
397
+
398
+ setValues((prevValues) => ({ ...prevValues, [key]: value }));
399
+ };
400
+
401
+ return (
402
+ <div
403
+ style={{
404
+ display: "grid",
405
+ gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
406
+ gap: "24px",
407
+ maxWidth: "800px",
408
+ }}
409
+ >
410
+ {MASK_ITEMS.map(({ key, label, mask, placeholderMask }) => (
411
+ <MaskedField
412
+ key={key}
413
+ label={label}
414
+ maskedInputProps={{
415
+ mask,
416
+ placeholderMask,
417
+ value: values[key] || "",
418
+ onChange: handleChange(key),
419
+ }}
420
+ />
421
+ ))}
422
+ </div>
423
+ );
424
+ };
425
+ ```
426
+
427
+ ### Production
428
+
429
+ `stories/MaskedField/examples/Production.tsx`
430
+
431
+ ```tsx
432
+ import React, { useState } from "react";
433
+ import {
434
+ MaskedField,
435
+ FormFieldMaskedInput,
436
+ FormFieldClear,
437
+ HelpBox,
438
+ Text,
439
+ Link,
440
+ ETooltipSize,
441
+ ETooltipPreferPlace,
442
+ ETextSize,
443
+ EFontType,
444
+ } from "@sberbusiness/triplex-next";
445
+
446
+ export const Production = () => {
447
+ const [value, setValue] = useState("");
448
+
449
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => setValue(event.target.value);
450
+
451
+ return (
452
+ <div style={{ maxWidth: "300px" }}>
453
+ <MaskedField
454
+ label="Номер телефона"
455
+ description={
456
+ <Text tag="div" size={ETextSize.B4} type={EFontType.SECONDARY}>
457
+ (21) Description{" "}
458
+ <Link href="#" onClick={(event) => event.preventDefault()}>
459
+ Link text
460
+ </Link>
461
+ </Text>
462
+ }
463
+ postfix={
464
+ <>
465
+ <FormFieldClear onClick={() => setValue("")} />
466
+ <HelpBox tooltipSize={ETooltipSize.SM} preferPlace={ETooltipPreferPlace.ABOVE}>
467
+ Text
468
+ </HelpBox>
469
+ </>
470
+ }
471
+ maskedInputProps={{
472
+ mask: FormFieldMaskedInput.presets.masks.phone,
473
+ value,
474
+ onChange: handleChange,
475
+ }}
476
+ />
477
+ </div>
478
+ );
479
+ };
480
+ ```
481
+