@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,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: AmountField
|
|
3
|
+
category: TextFields
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [TextField, NumberField, Amount, FormField, FormFieldInput]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/TextFields/AmountField.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# AmountField
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Поле ввода денежной суммы с автоматическим форматированием: разделители групп
|
|
17
|
+
(пробел), фиксированная дробная часть, запятая как десятичный разделитель,
|
|
18
|
+
отображение валюты справа. Управляет позицией каретки при вводе/удалении так,
|
|
19
|
+
чтобы она не попадала на разделители, и блокирует ввод нецифровых символов
|
|
20
|
+
(кроме запятой/точки).
|
|
21
|
+
|
|
22
|
+
Построен поверх `TextFieldBase` и `FormFieldInput` (получает визуальное оформление
|
|
23
|
+
и поддержку label/status/prefix/postfix от FormField-стека). Логика форматирования
|
|
24
|
+
инкапсулирована в классе `AmountBaseInputCore`, который внутренне делит работу на
|
|
25
|
+
парсер (`AmountBaseInputParser`) и форматер (`AmountBaseInputFormatter`).
|
|
26
|
+
|
|
27
|
+
Используй когда: пользователь вводит денежную сумму с валютой и фиксированной
|
|
28
|
+
дробной частью.
|
|
29
|
+
Не используй когда: нужен просто числовой ввод без валюты (используй `NumberField`);
|
|
30
|
+
нужно только отобразить сумму без ввода (используй `Amount`).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Файловая структура
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
src/components/AmountField/
|
|
38
|
+
├── AmountField.tsx # React.forwardRef, главный компонент
|
|
39
|
+
├── AmountBaseInputCore.ts # Класс-оркестратор: parse → format, хранит caret и cache
|
|
40
|
+
├── AmountBaseInputParser.ts # Класс-парсер: нормализует value, trimLeadingZeros, splits integer/fractional
|
|
41
|
+
├── AmountBaseInputFormatter.ts # Класс-форматер: добавляет пробелы между группами, запятую
|
|
42
|
+
├── utils.ts # createPlaceholder, setFallbackCaret
|
|
43
|
+
├── index.ts # Barrel (только AmountField)
|
|
44
|
+
└── __tests__/ # Unit-тесты
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Особенности:
|
|
48
|
+
- **Нет LESS** — компонент визуально идентичен `TextField`, стили наследуются из
|
|
49
|
+
`TextFieldBase`/`FormFieldInput`.
|
|
50
|
+
- **Нет `stories/AmountField/`** — stories лежат в `stories/TextFields/AmountField.stories.tsx`
|
|
51
|
+
(логически относится к группе TextFields).
|
|
52
|
+
- Core-логика разделена на 3 класса намеренно — parser, formatter, core (оркестратор).
|
|
53
|
+
Такое разделение нужно, чтобы можно было тестировать parse/format изолированно.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Варианты и props
|
|
58
|
+
|
|
59
|
+
### `IAmountFieldProps extends Omit<ITextFieldBaseProps, "children">`
|
|
60
|
+
|
|
61
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `inputProps` | `Omit<IFormFieldInputProps, "type" \| "maxLength" \| "onChange" \| "inputMode" \| "autoComplete"> & { value: string; onChange: (value: string) => void }` | — (обязателен) | Всё для `<input>`, кроме полей, которые AmountField задаёт сам. **`onChange` — нестандартный**: получает `value: string` (ЧИСТОЕ значение без форматирования), не event. |
|
|
64
|
+
| `currency` | `string` | — | Название валюты (например, "RUB"). Рендерится справа как постфикс через `Text` с типом `SECONDARY`/`DISABLED`. Не указано — постфикса нет. |
|
|
65
|
+
| `maxIntegerDigits` | `number` | `16` | Максимум цифр до запятой. Ввод сверх лимита игнорируется. |
|
|
66
|
+
| `fractionDigits` | `number` | `2` | Цифр после запятой. Используется для placeholder, parse, format. При `0` — компонент становится integer-only. |
|
|
67
|
+
| `onClear` | `() => void` | — | Если указан — справа перед валютой появляется `FormFieldClear` (крестик). Коллбек при клике. |
|
|
68
|
+
| `postfix` | `React.ReactNode` | — | Дополнительный постфикс (рядом с валютой и clear-кнопкой). Ограничение: ставится после валюты в визуальном порядке. |
|
|
69
|
+
| `...TextFieldBaseProps` | — | — | `label`, `status`, `size`, `prefix`, `description`, `counter`, `data-test-id`, и т.д. |
|
|
70
|
+
|
|
71
|
+
### Что AmountField заставляет у `inputProps`
|
|
72
|
+
|
|
73
|
+
- `type`: всегда `"text"` (задаётся TextFieldBase/FormFieldInput по умолчанию; в AmountField не передаётся).
|
|
74
|
+
- `maxLength`: не используется — AmountField сам ограничивает через `maxIntegerDigits` + `fractionDigits`.
|
|
75
|
+
- `onChange`: перехватывается. Снаружи получаешь чистое значение `string` без пробелов и с `.` как разделителем.
|
|
76
|
+
- `inputMode`: всегда `"decimal"` — подсказывает мобильной клавиатуре показать цифры и запятую.
|
|
77
|
+
- `autoComplete`: всегда `"off"` — браузерный автокомплит отключен.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Ключевые особенности реализации
|
|
82
|
+
|
|
83
|
+
### Разделение value и formattedValue
|
|
84
|
+
|
|
85
|
+
- **`value`** (то, что во внешнем state) — нормализованная строка: только цифры и `.`
|
|
86
|
+
как разделитель (например, `"1234.56"`).
|
|
87
|
+
- **`formattedValue`** (то, что показывается в `<input>`) — с пробелами и запятой
|
|
88
|
+
(например, `"1 234,56"`).
|
|
89
|
+
|
|
90
|
+
`AmountField` рендерит `formattedValue`, но во внешний `onChange` отдаёт `value`.
|
|
91
|
+
Это значит: внешнее состояние всегда совместимо с `big.js` / `parseFloat` /
|
|
92
|
+
backend API без дополнительной очистки.
|
|
93
|
+
|
|
94
|
+
### Ленивая инициализация core
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
const core = useRef<AmountBaseInputCore>();
|
|
98
|
+
if (core.current === undefined) core.current = new AmountBaseInputCore(maxIntegerDigits, fractionDigits);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Создаётся **один раз при первом рендере**. Изменение `maxIntegerDigits`/`fractionDigits`
|
|
102
|
+
применяется через `core.current.maxIntegerDigits = ...` в `getFormattedValue`, а не
|
|
103
|
+
через пересоздание инстанса.
|
|
104
|
+
|
|
105
|
+
### Синхронизация внешнего value с core
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
if (inputProps.value != core.current.value || ...) {
|
|
109
|
+
core.current.apply(inputProps.value, inputProps.value.length);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
На каждом рендере проверяется, изменилось ли значение извне (controlled re-render)
|
|
114
|
+
или параметры (`max/fractionDigits`). Если да — `core.apply()` пересчитывает
|
|
115
|
+
`formattedValue`. Это гарантирует, что компонент работает как controlled: внешний
|
|
116
|
+
state — единственный источник истины.
|
|
117
|
+
|
|
118
|
+
### Управление кареткой: `useLayoutEffect` + `setCaretPosition`
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
useLayoutEffect(() => {
|
|
122
|
+
if (core.current && refInput.current == document.activeElement)
|
|
123
|
+
setCaretPosition(refInput.current, Math.max(core.current.caret, 0));
|
|
124
|
+
}, [inputProps.value]);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
После каждого изменения value, если input в фокусе, восстанавливается позиция
|
|
128
|
+
каретки в `core.caret`. Это компенсирует то, что React при controlled input
|
|
129
|
+
сбрасывает каретку в конец после rerender.
|
|
130
|
+
|
|
131
|
+
**`useLayoutEffect`, не `useEffect`** — каретка ставится синхронно до отрисовки,
|
|
132
|
+
чтобы не было визуального "мигания".
|
|
133
|
+
|
|
134
|
+
### Fallback caret через `setFallbackCaret`
|
|
135
|
+
|
|
136
|
+
Если `onChange` **не меняет значение** (невалидный ввод, идентичное значение) —
|
|
137
|
+
React не перерендерится, и `useLayoutEffect` не сработает. Для этого случая
|
|
138
|
+
`handleChange` вызывает `setFallbackCaret(event.target, core, fractionDigits)`,
|
|
139
|
+
которая обрабатывает спецкейсы:
|
|
140
|
+
- Каретка перед разделителем `,` → сдвиг вперёд.
|
|
141
|
+
- Backspace/Delete возле разделителей → сдвиг.
|
|
142
|
+
- Обратное направление выделения → каретка в конец.
|
|
143
|
+
|
|
144
|
+
Эта утилита — единственное место, где компонент напрямую дёргает input DOM API
|
|
145
|
+
(`setSelectionRange`).
|
|
146
|
+
|
|
147
|
+
### Кеш для восстановления контекста
|
|
148
|
+
|
|
149
|
+
`core.cache` хранит: последнее `formattedValue`, `key` нажатой клавиши,
|
|
150
|
+
`selectionStart`/`End`/`Direction` на момент последнего select-event. Это даёт
|
|
151
|
+
парсеру/формате́ру/fallback-caret полную картину "что сделал пользователь", чтобы
|
|
152
|
+
правильно обработать ввод.
|
|
153
|
+
|
|
154
|
+
`handleSelect` обновляет `cache.selectionStart/End/Direction` на каждом
|
|
155
|
+
событии выделения — значит, к моменту `handleChange` эти поля актуальны.
|
|
156
|
+
|
|
157
|
+
### `setRef` вручную
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
const setRef = (instance) => {
|
|
161
|
+
refInput.current = instance; // локальный ref
|
|
162
|
+
if (typeof ref === "function") ref(instance);
|
|
163
|
+
else if (ref) ref.current = instance;
|
|
164
|
+
};
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Компоненту нужен **и** локальный ref (для caret-манипуляций), **и** внешний
|
|
168
|
+
forwarded ref. `setRef` передаёт instance в оба. Не заменять на единственный
|
|
169
|
+
внешний ref.
|
|
170
|
+
|
|
171
|
+
### `createPlaceholder(fractionDigits)`
|
|
172
|
+
|
|
173
|
+
Если потребитель не передал `inputProps.placeholder`, генерируется автоматически:
|
|
174
|
+
- `fractionDigits=0` → `"0"`.
|
|
175
|
+
- `fractionDigits=2` → `"0,00"`.
|
|
176
|
+
- `fractionDigits=4` → `"0,0000"`.
|
|
177
|
+
|
|
178
|
+
### `parseFractionalPart` восстанавливает целый ноль
|
|
179
|
+
|
|
180
|
+
Если пользователь ввёл `.5` (без целой части) и дробная часть ненулевая, парсер
|
|
181
|
+
восстанавливает `0.5`. Если дробная часть только из нулей (`"00"`) — целая часть
|
|
182
|
+
остаётся пустой (парсинг останавливается), итоговое значение `""`.
|
|
183
|
+
|
|
184
|
+
### `trimLeadingZeros` — не агрессивен
|
|
185
|
+
|
|
186
|
+
Удаляет ведущие нули **только если следующая цифра ненулевая**. Значение `"0"` или
|
|
187
|
+
`"00"` остаётся целой частью `""` или `"0"` в зависимости от дробной. Это
|
|
188
|
+
обеспечивает корректное поведение при вводе `0,5` (пользователь пишет с нуля).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Инварианты
|
|
193
|
+
|
|
194
|
+
- **`forwardRef`** на `AmountField` — обязателен, ref идёт на `<input>`. Не убирать.
|
|
195
|
+
- **`inputMode="decimal"`** — не менять на `numeric` или `text`. `decimal` даёт
|
|
196
|
+
правильную мобильную клавиатуру с запятой.
|
|
197
|
+
- **`autoComplete="off"`** — не убирать. Браузерный автокомплит ломает
|
|
198
|
+
форматирование.
|
|
199
|
+
- **`onChange` отдаёт `string` (чистое значение), не event** — это **контракт**.
|
|
200
|
+
Потребители полагаются на нормализованный формат. Не менять сигнатуру без
|
|
201
|
+
breaking-change.
|
|
202
|
+
- **Разделение parser/formatter/core** — не сливать в один класс. Тесты
|
|
203
|
+
построены вокруг парсера/формате́ра в изоляции.
|
|
204
|
+
- **Запятая как разделитель, точка тоже принимается** — парсер
|
|
205
|
+
(`StringUtils.isDecimalSeparator`) принимает и `.`, и `,`. Formatter всегда
|
|
206
|
+
выдаёт `,`. Не менять.
|
|
207
|
+
- **Пробел как групповой разделитель** — не менять на NBSP или другой символ
|
|
208
|
+
(parser полагается на `StringUtils.isDigit` для определения не-цифр).
|
|
209
|
+
- **Core создаётся один раз** — не пересоздавать при каждом изменении props,
|
|
210
|
+
потеряется кэш.
|
|
211
|
+
- **`useLayoutEffect` (не `useEffect`)** для caret-позиции — не менять.
|
|
212
|
+
- **`setFallbackCaret`** — обязателен в `handleChange`. Без него каретка
|
|
213
|
+
теряется при идентичном/невалидном вводе.
|
|
214
|
+
- **`data-test-id` суффиксы** (`__input`, `__unit`) — публичный контракт для
|
|
215
|
+
тестов потребителей. Не переименовывать.
|
|
216
|
+
- **`maxIntegerDigits=16` по умолчанию** — покрывает квадриллионы, но не больше
|
|
217
|
+
(bigint). Если лимит мал — увеличивать через prop, не в коде.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Accessibility
|
|
222
|
+
|
|
223
|
+
- **Нативный `<input>`** — клавиатурная навигация работает из коробки.
|
|
224
|
+
- **`inputMode="decimal"`** — на мобильных устройствах показывается числовая
|
|
225
|
+
клавиатура с десятичным разделителем.
|
|
226
|
+
- **`autoComplete="off"`** — отключает автозаполнение (важно для сумм —
|
|
227
|
+
браузерный autofill не подходит под формат).
|
|
228
|
+
- **Валюта рендерится как отдельный `<Text>`** — screen reader прочитает её как
|
|
229
|
+
обычный текст (например, "RUB"). Для полного названия валюты — потребитель
|
|
230
|
+
может передать `aria-label` на самом поле через `inputProps`.
|
|
231
|
+
- **Для отрицательных чисел компонент НЕ делает замену `-` → `\u2212`** (в отличие
|
|
232
|
+
от display-компонента `Amount`). Это корректно: в input'е нужно настоящее
|
|
233
|
+
значение, которое можно скопировать и переиспользовать; символ `\u2212` ломает
|
|
234
|
+
parsing извне.
|
|
235
|
+
- **`onClear` + `FormFieldClear`** — кнопка с иконкой внутри поля. Потребитель
|
|
236
|
+
обязан убедиться, что у кнопки есть `aria-label` (через внутреннюю реализацию
|
|
237
|
+
FormFieldClear) — проверь при расширении.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Дизайн-токены
|
|
242
|
+
|
|
243
|
+
Нет собственных — все цвета и размеры наследуются из `TextFieldBase`/
|
|
244
|
+
`FormFieldInput`/`Typography`. См. их документацию для списка токенов.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Связанные компоненты
|
|
249
|
+
|
|
250
|
+
- `TextFieldBase` (`src/components/TextField/TextFieldBase.tsx`) — базовый
|
|
251
|
+
контейнер поля, предоставляет label/status/size/prefix/postfix.
|
|
252
|
+
- `FormField` / `FormFieldInput` (`src/components/FormField`) — нативный `<input>`
|
|
253
|
+
с визуальным оформлением формы.
|
|
254
|
+
- `FormFieldClear` (`src/components/FormField/components/FormFieldClear.tsx`) —
|
|
255
|
+
кнопка очистки (крестик). Используется, если передан `onClear`.
|
|
256
|
+
- `Typography` (`Text`, `ETextSize.B2`, `EFontType.SECONDARY/DISABLED`) —
|
|
257
|
+
рендер валюты.
|
|
258
|
+
- `Amount` (`src/components/Amount`) — display-аналог. Форматирование разное:
|
|
259
|
+
AmountField использует свою реализацию (через parser/formatter классы),
|
|
260
|
+
Amount — `formatAmount` из `utils/amountUtils.ts` на `big.js`. Результат
|
|
261
|
+
визуально совпадает, но алгоритмы различны.
|
|
262
|
+
- `NumberField` (`src/components/NumberField`) — числовой ввод без валюты.
|
|
263
|
+
- `AmountConst` (`src/consts/AmountConst`) — общие константы разделителей.
|
|
264
|
+
- Утилиты `utils/inputUtils.ts` (`setCaretPosition`), `utils/keyboard.ts` (`isKey`),
|
|
265
|
+
`utils/stringUtils.ts` (`StringUtils.isDigit`, `isDecimalSeparator`).
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Stories
|
|
270
|
+
|
|
271
|
+
`stories/TextFields/AmountField.stories.tsx`
|
|
272
|
+
Файлы примеров: `stories/TextFields/examples/AmountField/`
|
|
273
|
+
|
|
274
|
+
| Story | Example file | Что демонстрирует |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `status`, `size`, `label`, `currency`, `prefix`, `postfix`, `description`, `counter`, `maxIntegerDigits`, `fractionDigits` |
|
|
277
|
+
| `Basic` | `BasicExample.tsx` | Базовое controlled-использование |
|
|
278
|
+
| `Sizes` | `SizesExample.tsx` | Все размеры `EComponentSize` |
|
|
279
|
+
| `Statuses` | `StatusesExample.tsx` | Все `EFormFieldStatus` |
|
|
280
|
+
| `WithClearButton` | `WithClearButtonExample.tsx` | С крестиком очистки |
|
|
281
|
+
| `Example` | `ExampleExample.tsx` | Композиция с другими компонентами (формы) |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## История изменений
|
|
286
|
+
|
|
287
|
+
| Дата | Изменение |
|
|
288
|
+
|---|---|
|
|
289
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Avatar
|
|
3
|
+
category: Avatars
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [Badge, IconWrapper]
|
|
7
|
+
tokens:
|
|
8
|
+
- --triplex-next-Avatar-Background
|
|
9
|
+
stories: stories/Avatar/Avatar.stories.tsx
|
|
10
|
+
version: "1.0"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Avatar
|
|
14
|
+
|
|
15
|
+
## Назначение
|
|
16
|
+
|
|
17
|
+
Контейнер-"плитка" для представления пользовательской идентичности: картинки
|
|
18
|
+
профиля, инициалов, иконки или любого другого ReactNode. Задаёт квадратные
|
|
19
|
+
размеры, скруглённые углы и фоновый цвет. Сам по себе никакого контента не
|
|
20
|
+
рендерит — только обёртка со стилями; визуальное наполнение — через children.
|
|
21
|
+
|
|
22
|
+
Размер шкалы (XXS → XXL) покрывает от маленькой иконки в списке до большого
|
|
23
|
+
hero-аватара в профиле.
|
|
24
|
+
|
|
25
|
+
Используй когда: нужно представить пользователя/сущность в виде стандартного
|
|
26
|
+
квадратного блока с картинкой или инициалами.
|
|
27
|
+
Не используй когда: нужен только круглый avatar — в этой реализации формы
|
|
28
|
+
фиксированы (squircle с разными radius), круг не предусмотрен. Если нужны
|
|
29
|
+
инициалы с авто-генерацией — это задача потребителя, компонент не рендерит текст.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Файловая структура
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
src/components/Avatar/
|
|
37
|
+
├── Avatar.tsx # React.forwardRef, минимальная обёртка
|
|
38
|
+
├── enums.ts # EAvatarSize (XXS, XS, SM, MD, LG, XL, XXL)
|
|
39
|
+
├── types.ts # IAvatarProps
|
|
40
|
+
├── index.ts # Barrel: enums + types + Avatar
|
|
41
|
+
├── styles/Avatar.module.less # Размеры + border-radius + фон
|
|
42
|
+
└── __tests__/Avatar.test.tsx
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Особенности:
|
|
46
|
+
- **Типы и enum вынесены в отдельные файлы** (`types.ts`, `enums.ts`) —
|
|
47
|
+
это паттерн более крупных компонентов, здесь избыточен, но сохранён для
|
|
48
|
+
единообразия. Не сводить в один файл без общего рефакторинга стайл-гайда.
|
|
49
|
+
- **Нет helpers, нет subcomponents** — компонент максимально тонкий.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Варианты и props
|
|
54
|
+
|
|
55
|
+
### `IAvatarProps extends React.HTMLAttributes<HTMLDivElement>`
|
|
56
|
+
|
|
57
|
+
| Prop | Тип | Обязателен | Описание |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `size` | `EAvatarSize` | **Да** | Размер + скругление углов. Нет значения по умолчанию — потребитель обязан указать. |
|
|
60
|
+
| `children` | `React.ReactNode` | Нет | Содержимое (через `...HTMLAttributes`). Обычно `<img>`, `<svg>`, `<Text>` или их комбинация. Overflow скрывается (`overflow: hidden`) — контент обрезается по форме. |
|
|
61
|
+
| `className` | `string` | Нет | Объединяется с `styles.avatar + sizeClass` через `clsx`. |
|
|
62
|
+
| `...HTMLDivAttributes` | — | — | Все стандартные атрибуты `<div>`, включая `onClick`, `role`, `aria-*`, `style`. |
|
|
63
|
+
|
|
64
|
+
### `EAvatarSize` — полная шкала
|
|
65
|
+
|
|
66
|
+
| Значение | Width/Height | Border-radius |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| `XXS` | 28px | 6px |
|
|
69
|
+
| `XS` | 32px | 8px |
|
|
70
|
+
| `SM` | 40px | 10px |
|
|
71
|
+
| `MD` | 56px | 16px |
|
|
72
|
+
| `LG` | 80px | 20px |
|
|
73
|
+
| `XL` | 96px | 24px |
|
|
74
|
+
| `XXL` | 128px | 28px |
|
|
75
|
+
|
|
76
|
+
Радиусы непропорциональны размеру (MD=56px с radius=16px ≈ 28%, XXL=128px с
|
|
77
|
+
radius=28px ≈ 22%). Это **дизайн-решение**, а не формула — не пересчитывать.
|
|
78
|
+
Уменьшение относительного радиуса на больших размерах даёт более "квадратный"
|
|
79
|
+
squircle-вид на крупных аватарах.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Ключевые особенности реализации
|
|
84
|
+
|
|
85
|
+
### flex-центрирование контента
|
|
86
|
+
|
|
87
|
+
```less
|
|
88
|
+
.avatar {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Любой children центрируется автоматически. Для `<img>` потребитель обычно задаёт
|
|
97
|
+
`width: 100%; height: 100%; object-fit: cover;` чтобы заполнить всю область —
|
|
98
|
+
compon компонент не делает этого сам, намеренно: потребитель может показать
|
|
99
|
+
маленькую иконку по центру, а не растянутое изображение.
|
|
100
|
+
|
|
101
|
+
### `overflow: hidden` обрезает по форме
|
|
102
|
+
|
|
103
|
+
Любой child обрезается по squircle-границам. Это ключевое свойство — картинка
|
|
104
|
+
автоматически получает скруглённые углы без собственного `border-radius`.
|
|
105
|
+
|
|
106
|
+
### `sizeToClassNameMap` — прямой маппинг enum → className
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const sizeToClassNameMap = {
|
|
110
|
+
[EAvatarSize.XXS]: styles.xxs,
|
|
111
|
+
// ...
|
|
112
|
+
};
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Объект, не функция. Полный охват всех значений enum (TypeScript проверяет через
|
|
116
|
+
Record-тип на ключах). При добавлении нового размера — **обновлять обе точки**:
|
|
117
|
+
enum и map.
|
|
118
|
+
|
|
119
|
+
### Фон через CSS-переменную
|
|
120
|
+
|
|
121
|
+
`background: var(--triplex-next-Avatar-Background)` — единственный токен
|
|
122
|
+
компонента. Фон виден, когда children прозрачен или меньше области. Потребитель
|
|
123
|
+
может переопределить через `style` или `className` (например, для цветных
|
|
124
|
+
avatar'ов с инициалами).
|
|
125
|
+
|
|
126
|
+
### `forwardRef` на `<div>`
|
|
127
|
+
|
|
128
|
+
Ref идёт на root div. Используется для анимаций, позиционирования Tooltip и т.д.
|
|
129
|
+
Не убирать.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Инварианты
|
|
134
|
+
|
|
135
|
+
- **`forwardRef`** — обязателен. Не убирать.
|
|
136
|
+
- **`size` обязателен** — не делать defaultValue, чтобы потребитель явно выбирал
|
|
137
|
+
масштаб (избежать случайного использования мелких аватаров в hero-контексте).
|
|
138
|
+
- **`EAvatarSize` значения** — публичное API, не переименовывать.
|
|
139
|
+
- **Размеры и radius в LESS** — не менять без согласования с дизайнером.
|
|
140
|
+
Значения подобраны под токены отступов сетки (28, 32, 40, 56, 80, 96, 128 —
|
|
141
|
+
кратны 4/8).
|
|
142
|
+
- **`overflow: hidden`** на root — не убирать, от этого зависит скругление
|
|
143
|
+
контента.
|
|
144
|
+
- **`background: var(--triplex-next-Avatar-Background)`** — единственный
|
|
145
|
+
дизайн-токен, не хардкодить цвета.
|
|
146
|
+
- **`flex` центрирование** — не менять на grid без согласования, это затрагивает
|
|
147
|
+
все потребительские стили внутри Avatar.
|
|
148
|
+
- **Компонент — тонкая обёртка, НЕ рендерит children-специфику** (например, не
|
|
149
|
+
генерирует инициалы из имени). Всё — на стороне потребителя. Не усложнять.
|
|
150
|
+
- **Типы в отдельном файле (`types.ts`)** — сохранять паттерн для консистентности
|
|
151
|
+
с другими компонентами, даже если выглядит избыточным.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Accessibility
|
|
156
|
+
|
|
157
|
+
- **Никаких ARIA-ролей по умолчанию** — `<div>` без роли. Если Avatar
|
|
158
|
+
интерактивный (clickable), потребитель обязан:
|
|
159
|
+
- Передать `role="button"` + `tabIndex={0}` + `onKeyDown` для клавиатуры.
|
|
160
|
+
- Или обернуть в `<button>` / `<a>` (с учётом того, что Avatar — div, а div
|
|
161
|
+
внутри button валиден, но button внутри div — нет).
|
|
162
|
+
- **`<img>` внутри** — alt-текст задаёт потребитель. Avatar не добавляет
|
|
163
|
+
`aria-label`, не озвучивает children.
|
|
164
|
+
- **Инициалы как children (`<Text>`)** — screen reader прочитает их как текст.
|
|
165
|
+
Для полного имени — потребитель может скрыть инициалы от screen reader
|
|
166
|
+
(`aria-hidden`) и добавить `aria-label="Иван Иванов"` на Avatar.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Дизайн-токены
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
--triplex-next-Avatar-Background # Фон контейнера
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Единственный токен. Цвет контента (иконки, текста) определяется самим
|
|
177
|
+
контентом, не компонентом.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Связанные компоненты
|
|
182
|
+
|
|
183
|
+
- `Badge` (`src/components/Badge`) — часто комбинируется с Avatar (индикатор
|
|
184
|
+
статуса / счётчика в углу). Позиционирование — задача потребителя.
|
|
185
|
+
- `IconWrapper` (`src/components/IconWrapper`) — может быть использован внутри
|
|
186
|
+
Avatar как children для показа иконки вместо фото.
|
|
187
|
+
- `Text` / Typography — используется для рендера инициалов внутри Avatar.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Stories
|
|
192
|
+
|
|
193
|
+
`stories/Avatar/Avatar.stories.tsx`
|
|
194
|
+
Файлы примеров: `stories/Avatar/examples/`
|
|
195
|
+
|
|
196
|
+
| Story | Example file | Что демонстрирует |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `size` |
|
|
199
|
+
| `Default` | `DefaultExample.tsx` | Базовое использование (XXL) |
|
|
200
|
+
| `Sizes` | `SizesExample.tsx` | Все 7 размеров на одной шкале |
|
|
201
|
+
| `Example` | `ExampleUsage.tsx` | Композиция с другими компонентами |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## История изменений
|
|
206
|
+
|
|
207
|
+
| Дата | Изменение |
|
|
208
|
+
|---|---|
|
|
209
|
+
| 2026-04-18 | Создан документ |
|