@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.
- package/README.md +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- 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 | Создан документ |
|