@sberbusiness/triplex-next-mcp-server 0.24.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +124 -0
  2. package/bundle/.gitkeep +0 -0
  3. package/bundle/components/AbstractTree.md +198 -0
  4. package/bundle/components/AccordionBase.md +227 -0
  5. package/bundle/components/Alert.md +317 -0
  6. package/bundle/components/Amount.md +225 -0
  7. package/bundle/components/AmountField.md +289 -0
  8. package/bundle/components/Avatar.md +209 -0
  9. package/bundle/components/Badge.md +277 -0
  10. package/bundle/components/Body.md +167 -0
  11. package/bundle/components/Button.md +230 -0
  12. package/bundle/components/ButtonDropdown.md +159 -0
  13. package/bundle/components/ButtonIcon.md +122 -0
  14. package/bundle/components/Calendar.md +445 -0
  15. package/bundle/components/Card.md +374 -0
  16. package/bundle/components/CarouselExtended.md +250 -0
  17. package/bundle/components/Checkbox.md +126 -0
  18. package/bundle/components/CheckboxTree.md +280 -0
  19. package/bundle/components/CheckboxTreeExtended.md +321 -0
  20. package/bundle/components/CheckboxXGroup.md +68 -0
  21. package/bundle/components/CheckboxYGroup.md +67 -0
  22. package/bundle/components/Chip.md +404 -0
  23. package/bundle/components/ChipGroup.md +179 -0
  24. package/bundle/components/Col.md +221 -0
  25. package/bundle/components/CollapsableTree.md +255 -0
  26. package/bundle/components/Confirm.md +343 -0
  27. package/bundle/components/DateField.md +371 -0
  28. package/bundle/components/DatePickerExtended.md +342 -0
  29. package/bundle/components/DateRange.md +275 -0
  30. package/bundle/components/DesignTokens.md +385 -0
  31. package/bundle/components/Divider.md +196 -0
  32. package/bundle/components/DocumentNumberEdit.md +280 -0
  33. package/bundle/components/Dropdown.md +433 -0
  34. package/bundle/components/Ellipsis.md +208 -0
  35. package/bundle/components/EmptyView.md +99 -0
  36. package/bundle/components/ExpandAnimation.md +281 -0
  37. package/bundle/components/Footer.md +339 -0
  38. package/bundle/components/FormField.md +326 -0
  39. package/bundle/components/FormGroup.md +99 -0
  40. package/bundle/components/Gap.md +113 -0
  41. package/bundle/components/Header.md +280 -0
  42. package/bundle/components/HelpBox.md +154 -0
  43. package/bundle/components/IconWrapper.md +145 -0
  44. package/bundle/components/Island.md +197 -0
  45. package/bundle/components/IslandAccordion.md +230 -0
  46. package/bundle/components/IslandWidget.md +233 -0
  47. package/bundle/components/KeyDownListener.md +157 -0
  48. package/bundle/components/LightBox.md +322 -0
  49. package/bundle/components/Link.md +161 -0
  50. package/bundle/components/List.md +247 -0
  51. package/bundle/components/ListMaster.md +204 -0
  52. package/bundle/components/Loader.md +161 -0
  53. package/bundle/components/LoaderScreen.md +134 -0
  54. package/bundle/components/Marker.md +127 -0
  55. package/bundle/components/MarkerStatus.md +148 -0
  56. package/bundle/components/MediaWidth.md +169 -0
  57. package/bundle/components/MobileView.md +120 -0
  58. package/bundle/components/ModalWindow.md +308 -0
  59. package/bundle/components/MonthYearField.md +168 -0
  60. package/bundle/components/MultiselectField.md +213 -0
  61. package/bundle/components/Notification.md +233 -0
  62. package/bundle/components/NumberField.md +178 -0
  63. package/bundle/components/OrderedList.md +169 -0
  64. package/bundle/components/Overlay.md +237 -0
  65. package/bundle/components/Page.md +246 -0
  66. package/bundle/components/Pagination.md +298 -0
  67. package/bundle/components/Portal.md +119 -0
  68. package/bundle/components/Radio.md +174 -0
  69. package/bundle/components/Row.md +125 -0
  70. package/bundle/components/SMSField.md +160 -0
  71. package/bundle/components/SegmentedControl.md +235 -0
  72. package/bundle/components/SelectExtendedField.md +322 -0
  73. package/bundle/components/SelectField.md +213 -0
  74. package/bundle/components/Skeleton.md +129 -0
  75. package/bundle/components/Slider.md +157 -0
  76. package/bundle/components/SliderExtended.md +210 -0
  77. package/bundle/components/SliderRange.md +160 -0
  78. package/bundle/components/SmallInput.md +101 -0
  79. package/bundle/components/Spoiler.md +121 -0
  80. package/bundle/components/StatusTracker.md +151 -0
  81. package/bundle/components/Step.md +121 -0
  82. package/bundle/components/Stepper.md +163 -0
  83. package/bundle/components/Suggest.md +160 -0
  84. package/bundle/components/SuggestField.md +157 -0
  85. package/bundle/components/SwipeableArea.md +144 -0
  86. package/bundle/components/Table.md +199 -0
  87. package/bundle/components/Tabs.md +152 -0
  88. package/bundle/components/TabsExtended.md +273 -0
  89. package/bundle/components/TabsLine.md +189 -0
  90. package/bundle/components/Tag.md +135 -0
  91. package/bundle/components/TagColor.md +121 -0
  92. package/bundle/components/TagGroup.md +100 -0
  93. package/bundle/components/TextField.md +152 -0
  94. package/bundle/components/TextareaField.md +124 -0
  95. package/bundle/components/ThemeProvider.md +181 -0
  96. package/bundle/components/Tooltip.md +254 -0
  97. package/bundle/components/TopOverlay.md +145 -0
  98. package/bundle/components/TreeView.md +224 -0
  99. package/bundle/components/Triggers.md +121 -0
  100. package/bundle/guides/codestyle.md +205 -0
  101. package/bundle/guides/commits.md +166 -0
  102. package/bundle/guides/context.md +277 -0
  103. package/bundle/guides/stories.md +618 -0
  104. package/bundle/guides/template.md +154 -0
  105. package/bundle/guides/tests.md +301 -0
  106. package/bundle/icons.json +4156 -0
  107. package/bundle/manifest.json +796 -0
  108. package/bundle/release-notes/1.1.0.mdx +26 -0
  109. package/bundle/release-notes/1.1.1.mdx +14 -0
  110. package/bundle/release-notes/1.10.0.mdx +43 -0
  111. package/bundle/release-notes/1.11.0.mdx +19 -0
  112. package/bundle/release-notes/1.12.0.mdx +21 -0
  113. package/bundle/release-notes/1.13.0.mdx +43 -0
  114. package/bundle/release-notes/1.14.0.mdx +29 -0
  115. package/bundle/release-notes/1.15.0.mdx +56 -0
  116. package/bundle/release-notes/1.16.0.mdx +55 -0
  117. package/bundle/release-notes/1.17.0.mdx +15 -0
  118. package/bundle/release-notes/1.18.0.mdx +24 -0
  119. package/bundle/release-notes/1.19.0.mdx +48 -0
  120. package/bundle/release-notes/1.19.1.mdx +9 -0
  121. package/bundle/release-notes/1.2.0.mdx +80 -0
  122. package/bundle/release-notes/1.20.0.mdx +48 -0
  123. package/bundle/release-notes/1.21.0.mdx +66 -0
  124. package/bundle/release-notes/1.22.0.mdx +75 -0
  125. package/bundle/release-notes/1.23.0.mdx +50 -0
  126. package/bundle/release-notes/1.24.0.mdx +59 -0
  127. package/bundle/release-notes/1.25.0.mdx +52 -0
  128. package/bundle/release-notes/1.26.0.mdx +11 -0
  129. package/bundle/release-notes/1.3.0.mdx +21 -0
  130. package/bundle/release-notes/1.4.0.mdx +37 -0
  131. package/bundle/release-notes/1.5.0.mdx +24 -0
  132. package/bundle/release-notes/1.6.0.mdx +37 -0
  133. package/bundle/release-notes/1.6.1.mdx +11 -0
  134. package/bundle/release-notes/1.7.0.mdx +29 -0
  135. package/bundle/release-notes/1.8.0.mdx +38 -0
  136. package/bundle/release-notes/1.9.0.mdx +28 -0
  137. package/dist/index.d.ts +2 -0
  138. package/dist/index.js +324 -0
  139. package/dist/index.js.map +1 -0
  140. package/package.json +51 -0
@@ -0,0 +1,339 @@
1
+ ---
2
+ component: Footer
3
+ category: Layout
4
+ figma-node: ""
5
+ figma-file: ""
6
+ related: [Page, Header, Button, TableBasic]
7
+ tokens: []
8
+ stories: ""
9
+ version: "1.0"
10
+ ---
11
+
12
+ # Footer
13
+
14
+ ## Назначение
15
+
16
+ Разметочный футер страницы/формы — контейнер под подвал с описанием и
17
+ кнопками действий. **Compound-компонент**: корневой `Footer` — тонкая
18
+ обёртка-`<div>` с `data-tx`; основная логика разметки — в
19
+ `Footer.Description` (flex-контейнер) + его под-компоненты
20
+ `Footer.Description.Content` (растяжимый контент слева) и
21
+ `Footer.Description.Controls` (фиксированная группа кнопок справа).
22
+
23
+ Типовой кейс: нижняя часть формы/LightBox'а/страницы, где слева —
24
+ пояснение или статус, справа — действия (`Сохранить`, `Отменить`).
25
+ На мобильных устройствах Controls переносится на следующую строку.
26
+
27
+ Используй когда: нужна стандартизированная разметка подвала
28
+ "контент + действия" с корректным мобильным wrap-поведением и
29
+ отступами, согласованными с `Button` (компенсация margin'ами).
30
+ Не используй как: универсальный flex-контейнер (возьми `Row`/собственный
31
+ layout).
32
+
33
+ ---
34
+
35
+ ## Файловая структура
36
+
37
+ ```text
38
+ src/components/Footer/
39
+ ├── Footer.tsx # корень, compound-root, forwardRef + data-tx
40
+ ├── index.ts # Barrel: Footer, FooterDescription, FooterDescriptionContent, FooterDescriptionControls
41
+ ├── components/
42
+ │ ├── FooterDescription.tsx # Object.assign → Content/Controls
43
+ │ ├── FooterDescriptionContent.tsx # flex-grow: 1; margin-right: auto
44
+ │ └── FooterDescriptionControls.tsx # margin-left: auto; padding-left: 24px
45
+ ├── styles/
46
+ │ └── Footer.module.less # flex/space-between + mobile wrap
47
+ └── __tests__/
48
+ └── Footer.test.tsx # smoke: root, ref, arbitrary props
49
+ ```
50
+
51
+ Особенности:
52
+ - **`forwardRef`** — есть на всех 4 уровнях.
53
+ - **`data-tx`** — только на корне `Footer`. `Description`/`Content`/
54
+ `Controls` не проставляют (они внутренние под-компоненты одной
55
+ compound-семьи).
56
+ - **`IFooterDescriptionControlsProps`** импортируется из
57
+ `@sberbusiness/triplex-next/components/Table/TableBasic/types` —
58
+ исторический кросс-модульный импорт (type определён в types.ts
59
+ Table, но используется здесь). Не трогать без осознанного
60
+ рефакторинга обоих мест.
61
+ - **Нет собственных stories** — Footer демонстрируется через
62
+ `stories/Page/FooterPage.stories.tsx` и как часть Table/LightBox
63
+ story-групп.
64
+ - **Тесты** покрывают только корень `Footer` (smoke-уровень —
65
+ рендер, ref, props-passthrough).
66
+
67
+ ---
68
+
69
+ ## Compound-структура
70
+
71
+ ```tsx
72
+ <Footer>
73
+ <Footer.Description>
74
+ <Footer.Description.Content>
75
+ Комментарий / описание / статус
76
+ </Footer.Description.Content>
77
+ <Footer.Description.Controls>
78
+ <Button>Отменить</Button>
79
+ <Button>Сохранить</Button>
80
+ </Footer.Description.Controls>
81
+ </Footer.Description>
82
+ </Footer>
83
+ ```
84
+
85
+ **Реализация compound-а через `Object.assign`:**
86
+
87
+ ```tsx
88
+ export const Footer = Object.assign(
89
+ React.forwardRef<HTMLDivElement, IFooterProps>(function Footer({ children, ...rest }, ref) { ... }),
90
+ { Description: FooterDescription },
91
+ );
92
+
93
+ export const FooterDescription = Object.assign(
94
+ React.forwardRef<HTMLDivElement, IFooterDescriptionProps>(function FooterDescription(...) { ... }),
95
+ {
96
+ Content: FooterDescriptionContent,
97
+ Controls: FooterDescriptionControls,
98
+ },
99
+ );
100
+ ```
101
+
102
+ **Два уровня compound'а:** `Footer.Description` + вложенный
103
+ `Description.Content`/`Description.Controls`. Это редкий паттерн в
104
+ библиотеке — большинство компонентов имеют один уровень.
105
+
106
+ **Все под-компоненты также экспортируются напрямую через barrel:**
107
+ `FooterDescription`, `FooterDescriptionContent`, `FooterDescriptionControls`.
108
+ Можно использовать либо compound-обращение (`Footer.Description.Content`),
109
+ либо прямой импорт. Предпочтение — compound для читаемости.
110
+
111
+ ---
112
+
113
+ ## Варианты и props
114
+
115
+ ### Footer — `IFooterProps extends React.HTMLAttributes<HTMLDivElement>`
116
+
117
+ | Prop | Тип | Default | Описание |
118
+ |---|---|---|---|
119
+ | `children` | `ReactNode` | — | Обычно ровно один `Footer.Description`. |
120
+ | `className`, `style`, `...HTMLDivAttributes` | — | — | Стандартные. |
121
+
122
+ **Корневой `Footer` — минимальный**: не добавляет flex/padding, только
123
+ data-tx. Никаких собственных классов. Все визуальные свойства —
124
+ в `Description`.
125
+
126
+ ### FooterDescription — `IFooterDescriptionProps extends React.HTMLAttributes<HTMLDivElement>`
127
+
128
+ | Prop | Тип | Default | Описание |
129
+ |---|---|---|---|
130
+ | `children` | `ReactNode` | — | Обычно `Content` + `Controls` или только `Controls`. |
131
+ | `className` | `string` | — | Мержится с `styles.footerDescription` через `clsx`. |
132
+ | `...HTMLDivAttributes` | — | — | Стандартные. |
133
+
134
+ Класс `.footerDescription`:
135
+ ```less
136
+ display: flex;
137
+ justify-content: space-between;
138
+ align-items: center;
139
+ ```
140
+
141
+ ### FooterDescriptionContent — `IFooterDescriptionContentProps extends React.HTMLAttributes<HTMLDivElement>`
142
+
143
+ | Prop | Тип | Default | Описание |
144
+ |---|---|---|---|
145
+ | `children` | `ReactNode` | — | Текст/описание/статус. |
146
+ | `className` | `string` | — | Мержится через `clsx`. |
147
+ | `...HTMLDivAttributes` | — | — | Стандартные. |
148
+
149
+ Класс `.footerDescriptionContent`:
150
+ ```less
151
+ flex-grow: 1;
152
+ margin-right: auto;
153
+ ```
154
+
155
+ **`margin-right: auto`** + `flex-grow: 1` — занимает всё свободное
156
+ место слева, выталкивая `Controls` вправо.
157
+
158
+ ### FooterDescriptionControls — `IFooterDescriptionControlsProps extends React.HTMLAttributes<HTMLDivElement>`
159
+
160
+ | Prop | Тип | Default | Описание |
161
+ |---|---|---|---|
162
+ | `children` | `ReactNode` | — | Обычно `<Button>...</Button>`-группа. |
163
+ | `className` | `string` | — | Мержится через `clsx`. |
164
+ | `...HTMLDivAttributes` | — | — | Стандартные. |
165
+
166
+ Класс `.footerDescriptionControls`:
167
+ ```less
168
+ margin-left: auto;
169
+ white-space: nowrap;
170
+ padding-left: 24px;
171
+ ```
172
+
173
+ **`white-space: nowrap`** — не даёт кнопкам переноситься на новую строку
174
+ внутри Controls (на desktop). **`padding-left: 24px`** — минимальный
175
+ gap между Content и Controls.
176
+
177
+ ---
178
+
179
+ ## Ключевые особенности реализации
180
+
181
+ ### Корень `Footer` — "прозрачный" контейнер
182
+
183
+ ```tsx
184
+ <div {...rest} data-tx={process.env.npm_package_version} ref={ref}>
185
+ {children}
186
+ </div>
187
+ ```
188
+
189
+ Без класса, без padding. Смысл — только как "маркер" (data-tx) и
190
+ стабильная точка для ref потребителя. Визуальная логика делегирована
191
+ `Description`. Если потребитель хочет padding/border — добавляет через
192
+ `className`/`style` в props.
193
+
194
+ ### Mobile wrap — `flex-wrap: wrap` + перераскладка Controls
195
+
196
+ ```less
197
+ @media (max-width: @screen-sm-max) {
198
+ .footerDescription { flex-wrap: wrap; align-items: normal; }
199
+ .footerDescriptionContent { width: 100%; }
200
+ .footerDescriptionControls {
201
+ padding-left: 0;
202
+ white-space: normal; // разрешаем перенос на mobile
203
+ margin-top: -16px; // компенсация Button margin
204
+ margin-left: -16px;
205
+ > button {
206
+ margin-left: 16px;
207
+ margin-top: 16px;
208
+ }
209
+ }
210
+ .footerDescriptionContent + .footerDescriptionControls {
211
+ margin-top: 0; // если Content есть — полагаемся на его высоту
212
+ }
213
+ }
214
+ ```
215
+
216
+ **На mobile Controls:**
217
+ - Переносится на новую строку (flex-wrap).
218
+ - `padding-left: 0` — убирает отступ (строка уже новая).
219
+ - `white-space: normal` — кнопки могут переноситься, если места мало.
220
+ - **`margin-top/-left: -16px` + `button > margin: 16px`** — трюк:
221
+ компенсирует отступы Button'а, чтобы визуально gap между кнопками
222
+ оставался 16px, но внешние края выравнивались к краю контейнера.
223
+ - **`Content + Controls { margin-top: 0 }`** — когда Content есть,
224
+ использует его нижний отступ; когда Controls стоит одна (без Content),
225
+ применяется полноценный -16px для верхнего смещения.
226
+
227
+ ### `.footer` класс в медиа — orphan
228
+
229
+ ```less
230
+ @media (max-width: @screen-sm-max) {
231
+ .footer { padding: 16px 0; }
232
+ }
233
+ ```
234
+
235
+ **Этот класс объявлен, но НЕ применяется** — в `Footer.tsx` нет
236
+ `className={styles.footer}`. Legacy-остаток. Не удалять без
237
+ подтверждения, что нигде не импортируется напрямую через module-import.
238
+
239
+ ### `IFooterDescriptionControlsProps` — кросс-модульный тип
240
+
241
+ ```ts
242
+ import { IFooterDescriptionControlsProps } from "@sberbusiness/triplex-next/components/Table/TableBasic/types";
243
+ ```
244
+
245
+ Тип живёт в `src/components/Table/TableBasic/types.ts`, но
246
+ используется `FooterDescriptionControls`. Исторический артефакт. Не
247
+ дублировать, не переносить без согласованного обновления Table.
248
+ `Table` импортирует этот же тип и разметку для своего Table-Footer.
249
+
250
+ ### `React.HTMLAttributes<HTMLDivElement>` — пустое extension
251
+
252
+ ```ts
253
+ export interface IFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
254
+ ```
255
+
256
+ Все 4 Props-интерфейса — пустые extensions HTML-атрибутов. Это
257
+ сознательно: интерфейсы существуют как "именованные точки", чтобы
258
+ можно было в будущем добавить пропсы без breaking change, а также
259
+ чтобы TS отображал именно `IFooterDescriptionProps` в hover'ах.
260
+
261
+ ---
262
+
263
+ ## Инварианты
264
+
265
+ - **`Footer` — минимальный контейнер**: только data-tx, без собственных
266
+ стилей. Не добавлять padding/border в компонент — оставить на
267
+ потребителя.
268
+ - **`data-tx`** — только на корневом `Footer`, не на под-компонентах.
269
+ - **Compound через `Object.assign`** — `Footer.Description.Content`,
270
+ `Footer.Description.Controls`. Не превращать в children-API.
271
+ - **`Description` использует flex `space-between` + `align-items: center`**
272
+ — desktop. Не менять на grid (сломает mobile wrap).
273
+ - **`Content` с `flex-grow: 1; margin-right: auto`** — занимает место,
274
+ выталкивает `Controls`.
275
+ - **`Controls` с `margin-left: auto; white-space: nowrap; padding-left: 24px`**
276
+ — правый край, кнопки в одну строку.
277
+ - **Mobile (`max-width: @screen-sm-max`)** — `flex-wrap: wrap`, Content
278
+ на 100%, Controls на новой строке с Button-margin-компенсацией.
279
+ Не менять без обновления Button'а.
280
+ - **`.footer` в медиа-селекторе — orphan**. Не использовать; не
281
+ удалять без grep'а потребителей.
282
+ - **`IFooterDescriptionControlsProps` — импорт из `Table/TableBasic/types`**,
283
+ не дублировать локально.
284
+ - **`forwardRef`** — на всех 4 компонентах.
285
+
286
+ ---
287
+
288
+ ## Accessibility
289
+
290
+ - **`<div>`-контейнеры** — семантика не задаётся (нет `<footer>`-тега).
291
+ Потребитель должен выбрать корректный semantic-элемент сам, передав
292
+ нужные ARIA-роли через `...rest`. Для "подвала страницы" ожидается
293
+ `<Footer role="contentinfo">` или оборачивание во внешний `<footer>`.
294
+ - **Нет `role="toolbar"` на Controls** — группа кнопок не анонсируется
295
+ как toolbar. Если нужно — потребитель добавляет через `role`/`aria-label`.
296
+ - **Tab-навигация** — естественная (от Content к Controls). Mobile
297
+ сохраняет tab-order (flex-wrap не меняет DOM-порядок).
298
+ - **Focus ring** — на дочерних `Button`/`Link`, сам Footer не focusable.
299
+
300
+ ---
301
+
302
+ ## Дизайн-токены
303
+
304
+ **Нет собственных.** Все визуальные свойства — от дочерних компонентов
305
+ (`Button`, `Link`, `Typography`). Footer — только layout.
306
+
307
+ ---
308
+
309
+ ## Связанные компоненты
310
+
311
+ - `Page` (`src/components/Page`) — типичный контейнер, где используется
312
+ Footer как подвал страницы (см. `stories/Page/FooterPage.stories.tsx`).
313
+ - `Header` (`src/components/Header`) — верхняя часть страницы, парная
314
+ к Footer.
315
+ - `LightBox`/`ModalWindow`/`SideOverlay` — часто имеют Footer в нижней
316
+ части модалки с action-buttons.
317
+ - `Button` — основной дочерний элемент в `Controls`. Mobile-стили
318
+ специально рассчитаны на отступы Button'а (`margin: 16px` compensation).
319
+ - `Table`/`TableBasic` — переиспользует `IFooterDescriptionControlsProps`
320
+ тип.
321
+
322
+ ---
323
+
324
+ ## Stories
325
+
326
+ **Нет собственных stories.** Footer демонстрируется через:
327
+ - `stories/Page/FooterPage.stories.tsx` — в составе страницы.
328
+ - Stories Table/LightBox/ModalWindow — как часть layout'а модалок.
329
+
330
+ Добавление своих stories — AI-Ready debt (подобные примитивы обычно
331
+ имеют базовый showcase).
332
+
333
+ ---
334
+
335
+ ## История изменений
336
+
337
+ | Дата | Изменение |
338
+ |---|---|
339
+ | 2026-04-18 | Создан документ |
@@ -0,0 +1,326 @@
1
+ ---
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]
7
+ tokens:
8
+ - --triplex-next-FormField-Background_Default
9
+ - --triplex-next-FormField-Background_Hover
10
+ - --triplex-next-FormField-Background_Active
11
+ - --triplex-next-FormField-Background_Disabled
12
+ - --triplex-next-FormField-Background_Error
13
+ - --triplex-next-FormField-Background_Error_Hover
14
+ - --triplex-next-FormField-Background_Warning
15
+ - --triplex-next-FormField-Background_Warning_Hover
16
+ - --triplex-next-FormField-Shadow_Default
17
+ - --triplex-next-FormField-Shadow_Active
18
+ - --triplex-next-FormField-Shadow_Error_Active
19
+ - --triplex-next-FormField-Shadow_Warning_Active
20
+ - --triplex-next-FormField-Input_Color_Default
21
+ - --triplex-next-FormField-Input_Color_Disabled
22
+ - --triplex-next-FormField-Label_Color_Default
23
+ - --triplex-next-FormField-Label_Color_Disabled
24
+ - --triplex-next-FormField-Placeholder_Color
25
+ - --triplex-next-FormField-Target_Color_Default
26
+ - --triplex-next-FormField-Target_Color_Disabled
27
+ - --triplex-next-FormField-Target_PlaceholderColor_Default
28
+ stories: stories/FormField/FormField.stories.tsx
29
+ version: "1.0"
30
+ ---
31
+
32
+ # FormField
33
+
34
+ ## Назначение
35
+
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`).
42
+
43
+ Используй `FormField` когда: строишь новый тип поля ввода, которого нет в
44
+ готовых обёртках (`TextField`, `NumberField`, `DateField` и т.п.).
45
+ Не используй напрямую когда: для типового текстового поля подходит
46
+ `TextField` — он уже собирает `FormField` + `FormFieldInput` + `FormFieldLabel`.
47
+
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
+ ```
81
+
82
+ ---
83
+
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
+ ```
104
+
105
+ Корневой `FormField` — provider `FormFieldContext`. Контекст хранит `size`,
106
+ `status`, `targetId`, `labelId`, `prefixWidth`, `postfixWidth`, `filled`,
107
+ `focused`, `active` и сеттеры. Под-компоненты читают/пишут это состояние.
108
+
109
+ ---
110
+
111
+ ## Варианты и props
112
+
113
+ ### FormField — `IFormFieldProps extends React.HTMLAttributes<HTMLDivElement>`
114
+
115
+ | Prop | Тип | Default | Описание |
116
+ |---|---|---|---|
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
123
+
124
+ | Значение | Эффект |
125
+ |---|---|
126
+ | `DEFAULT` | Обычное состояние. |
127
+ | `DISABLED` | Визуально disabled; `FormFieldInput`/`FormFieldTextarea` проставляют `disabled`, `FormFieldTarget` получает `tabIndex=-1` и `aria-disabled`. |
128
+ | `ERROR` | Красный фон/шейдоу. |
129
+ | `WARNING` | Жёлтый фон/шейдоу. |
130
+
131
+ ### Под-компоненты — ключевые props
132
+
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 (для корректного расположения текста и счётчика). |
143
+
144
+ ---
145
+
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
155
+
156
+ `FormField` пишет в inline style:
157
+ ```ts
158
+ style={{ paddingLeft: prefixWidth, paddingRight: postfixWidth, ...style }}
159
+ ```
160
+ `FormFieldPrefix`/`FormFieldPostfix` после рендера делают `getBoundingClientRect().width` → пишут в контекст → корень перерендеривается с новым padding. Если prefix/postfix меняют ширину динамически (например, текстом) — padding обновится на следующем рендер-цикле.
161
+
162
+ `FormFieldLabel` использует те же `prefixWidth`/`postfixWidth` для вычисления `left`/`right`, чтобы плавающий label был ровно над инпутом, не под иконками.
163
+
164
+ Дефолт `TARGET_PADDING_X_DEFAULT = 12` — стартовое значение до первого измерения префикса/постфикса (и когда их нет).
165
+
166
+ ### Floating label — `filled || active`
167
+
168
+ Label плавает наверх, когда:
169
+ - поле заполнено (`filled`), либо
170
+ - поле активно (`active === active-prop || focused`).
171
+
172
+ `FormFieldInput`/`FormFieldTextarea` вычисляют `filled` через `isFilled(value)`:
173
+ - `undefined` → `false`
174
+ - `number` → `true`
175
+ - строка → `value.length !== 0`
176
+
177
+ Для select-подобных обёрток `FormFieldTarget` пишет `filled = children.length !== 0`.
178
+
179
+ Принудительно задать `floating` можно prop'ом (для кейсов, где лейбл всегда сверху — например, поле с картинками).
180
+
181
+ ### Autofill detection через CSS-анимации
182
+
183
+ `FormFieldInput`/`FormFieldTextarea` слушают `onAnimationStart` и проверяют `animationName`:
184
+ - `autofill-applied-hook*` → `setFilled(true)`
185
+ - `autofill-cancelled-hook*` → `syncFilled(value)`
186
+
187
+ Это единственный способ отловить браузерное автозаполнение, которое не триггерит `onChange`. Стороны анимации объявлены в CSS. Не ломать имена — они парсятся в JS.
188
+
189
+ ### `FormFieldInput.render` — точка расширения
190
+
191
+ Через `render` пробрасываются все общие props (стили, id, `onFocus/onBlur`, `onAnimationStart`, `onChange`, `disabled`, `size`) и ref. Используется, например, в `FormFieldMaskedInput` — чтобы обернуть `react-text-mask`.
192
+
193
+ ### `FormFieldMaskedInput` — рендерит ДВА инпута
194
+
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).
199
+
200
+ ### `FormFieldClear` — `preventDefault` на mouseDown
201
+
202
+ Кнопка очистки лежит в `FormFieldPostfix`. При клике `onMouseDown` вызывает `event.preventDefault()` — иначе поле теряет фокус (= `focused = false`, = label падает, = filled пересчитывается по пустому значению). Не удалять этот обработчик.
203
+
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 (редкий кейс) — видимость нужно задавать вручную.
213
+
214
+ ### ID-связка label ↔ target
215
+
216
+ - Каждый target-компонент (`Input`/`Textarea`/`Target`) генерирует `id` через `uniqueId("input_"/"textarea_"/"target_")`, если не передан снаружи, и пишет его в `FormFieldContext.targetId` через `useLayoutEffect`.
217
+ - `FormFieldLabel` берёт `targetId` из контекста и ставит в `htmlFor`.
218
+ - `FormFieldLabel` сам генерирует `id` и пишет в `labelId` → `FormFieldTarget` использует его как `aria-labelledby`.
219
+
220
+ Из-за `uniqueId` (lodash-es, модульный счётчик) id'ы не SSR-safe. Это осознанный legacy-выбор; при SSR используй `id` prop с явным значением.
221
+
222
+ ---
223
+
224
+ ## Инварианты
225
+
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 отключён специально. Не включать зависимости.
235
+
236
+ ---
237
+
238
+ ## Accessibility
239
+
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 инпута.
250
+
251
+ ---
252
+
253
+ ## Дизайн-токены
254
+
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
+ ```
268
+
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
276
+ ```
277
+
278
+ Border-radius задан литералами в `.sm` / `.md` / `.lg`:
279
+ - `SM` → 6px
280
+ - `MD` → 8px
281
+ - `LG` → 10px
282
+
283
+ ---
284
+
285
+ ## Связанные компоненты
286
+
287
+ ### Высокоуровневые обёртки (используют FormField внутри)
288
+ - `TextField`, `TextareaField`, `SMSField`, `NumberField`, `AmountField`, `DateField`, `MonthYearField`, `DocumentNumberEdit` — типовые input-поля.
289
+ - `SelectField`, `SelectExtendedField`, `SuggestField`, `MultiselectField` — select-подобные (внутри обычно `FormFieldTarget`).
290
+
291
+ ### Утилитарные
292
+ - `FormGroup` — группа `FormField`'ов в форме.
293
+ - `Button` / `ButtonIcon` — `FormFieldClear` наследует `ButtonIcon`.
294
+ - `Loader` — может рендериться в `FormFieldPostfix` для состояния загрузки.
295
+
296
+ ### Кросс-модульный импорт
297
+ - `FormFieldClear` импортирует `ButtonIcon` из `../Button/ButtonIcon` — не дублировать.
298
+ - `FormFieldMaskedInput` импортирует `FormFieldInput` по абсолютному пути `@sberbusiness/triplex-next/components/FormField/components/FormFieldInput` — исторически, работает.
299
+
300
+ ---
301
+
302
+ ## Stories
303
+
304
+ Основные: `stories/FormField/FormField.stories.tsx`
305
+ Примеры: `stories/FormField/examples/`
306
+
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-регрессия. |
319
+
320
+ ---
321
+
322
+ ## История изменений
323
+
324
+ | Дата | Изменение |
325
+ |---|---|
326
+ | 2026-04-18 | Создан документ |