@sberbusiness/triplex-next-mcp-server 1.45.0-beta → 1.48.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardAction.md +615 -0
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/Chip.md +7 -6
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +344 -0
- package/bundle/components/ChipMultiselect.md +1251 -0
- package/bundle/components/ChipOptions.md +329 -0
- package/bundle/components/ChipSort.md +333 -0
- package/bundle/components/ChipSuggest.md +588 -0
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateField.md +222 -303
- package/bundle/components/DatePickerExtended.md +3 -1
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +14 -5
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/HelpBox.md +7 -1
- package/bundle/components/IslandAccordion.md +443 -152
- package/bundle/components/IslandWidget.md +622 -157
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MaskedField.md +2 -1
- package/bundle/components/MonthYearField.md +18 -5
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +92 -24
- package/bundle/release-notes/1.46.0.mdx +59 -0
- package/bundle/release-notes/1.47.0.mdx +122 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: TextareaField
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
figma-file: ""
|
|
6
|
-
related: [TextField, FormField, FormFieldTextarea, TextFieldBase]
|
|
3
|
+
category: TextFields
|
|
4
|
+
related: [TextField, MaskedField, FormField, FormGroup, DateField, MonthYearField]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/TextareaField/TextareaField.stories.tsx
|
|
9
7
|
version: "1.0"
|
|
@@ -13,107 +11,101 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
|
|
17
|
-
вставляющая `FormFieldTextarea` вместо `FormFieldInput`. Получает все
|
|
18
|
-
возможности `TextFieldBase` (label, description, prefix/postfix, counter,
|
|
19
|
-
ошибки, клонирование пропсов через FormField).
|
|
14
|
+
Компонент многострочного текстового ввода — то же поле, что `TextField`, но вместо `<input>` внутри используется `FormFieldTextarea` (`<textarea>`): плавающий лейбл, префикс/постфикс, описание и счётчик символов в одном компоненте. Композиция над семейством `FormField` (`FormGroup` → `FormField` → `FormFieldTextarea` + `FormFieldLabel`/`FormFieldPrefix`/`FormFieldPostfix`/`FormFieldDescription`/`FormFieldCounter`).
|
|
20
15
|
|
|
21
|
-
Используй когда:
|
|
22
|
-
Для однострочного ввода — `TextField`.
|
|
16
|
+
Используй когда: значение — свободный текст, который может занимать несколько строк: комментарий, назначение платежа, описание, обращение в поддержку. Обычно вместе с лимитом длины (`textareaProps.maxLength`) и счётчиком в `counter`.
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
## Файловая структура
|
|
27
|
-
|
|
28
|
-
```text
|
|
29
|
-
src/components/TextareaField/
|
|
30
|
-
├── TextareaField.tsx # FC<ITextareaFieldProps>; <TextFieldBase><FormFieldTextarea/></TextFieldBase>
|
|
31
|
-
├── types.ts # ITextareaFieldProps
|
|
32
|
-
├── __tests__/
|
|
33
|
-
└── index.ts
|
|
34
|
-
```
|
|
18
|
+
Не используй когда: значение однострочное (`TextField`), имеет фиксированный формат (`MaskedField`), это денежная сумма (`AmountField`), дата (`DateField`) или месяц (`MonthYearField`), нужен выбор из подсказок (`SuggestField`).
|
|
35
19
|
|
|
36
20
|
---
|
|
37
21
|
|
|
38
|
-
##
|
|
22
|
+
## Варианты и props
|
|
39
23
|
|
|
40
|
-
|
|
24
|
+
`TextareaField` — тонкая обёртка без собственного state: `textareaProps` уходит в `FormFieldTextarea`, остальные props — в `TextFieldBase` (и далее в `FormField`). Логика поля (генерация `id`, отслеживание фокуса и заполненности, реакция на автозаполнение) живёт в `FormFieldTextarea`.
|
|
41
25
|
|
|
42
|
-
|
|
43
|
-
extends Omit<ITextFieldBaseProps, "children">
|
|
44
|
-
{
|
|
45
|
-
textareaProps: IFormFieldTextareaProps & DataAttributes & RefAttributes<HTMLTextAreaElement>;
|
|
46
|
-
}
|
|
47
|
-
```
|
|
26
|
+
### Обязательные props
|
|
48
27
|
|
|
49
28
|
| Prop | Тип | Описание |
|
|
50
29
|
|---|---|---|
|
|
51
|
-
| `textareaProps` | `IFormFieldTextareaProps & DataAttributes & RefAttributes<HTMLTextAreaElement>` |
|
|
52
|
-
| `label?` | `ReactNode` | Лейбл (`FormFieldLabel`). От `TextFieldBase`. |
|
|
53
|
-
| `description?` | `ReactNode` | Описание / ошибка (`FormFieldDescription`). |
|
|
54
|
-
| `counter?` | `ReactNode` | Счётчик символов (`FormFieldCounter`), внутри description. |
|
|
55
|
-
| `prefix?` / `postfix?` | `ReactNode` | Префикс/постфикс (`FormFieldPrefix`/`FormFieldPostfix`). |
|
|
56
|
-
| `...IFormFieldProps` | — | Все props `FormField` (size, status, disabled, focused и т.д.). |
|
|
30
|
+
| `textareaProps` | `IFormFieldTextareaProps & DataAttributes & React.RefAttributes<HTMLTextAreaElement>` | Свойства `<textarea>`: стандартные атрибуты, **кроме `disabled`** (`value`, `onChange`, `placeholder`, `maxLength`, `rows`, `name`, `required`…), data-атрибуты и `ref` на сам элемент `<textarea>`. Блокировка задаётся через `status={EFormFieldStatus.DISABLED}`, см. «Особенности поведения». Может быть пустым объектом. |
|
|
57
31
|
|
|
58
|
-
|
|
32
|
+
### Опциональные props
|
|
59
33
|
|
|
60
|
-
|
|
34
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| `size` | `EComponentSize` | `LG` | Размер поля: SM / MD / LG. Задаёт шрифт, отступы и `min-height` области ввода. |
|
|
37
|
+
| `status` | `EFormFieldStatus` | `DEFAULT` | Статус: default / error / warning / disabled. `DISABLED` автоматически ставит `disabled` на `<textarea>`. |
|
|
38
|
+
| `active` | `boolean` | `false` | Принудительно активное (focused) визуальное состояние рамки. |
|
|
39
|
+
| `label` | `ReactNode` | — | Плавающий лейбл; связывается с `<textarea>` через `htmlFor`. |
|
|
40
|
+
| `prefix` | `ReactNode` | — | Контент слева от поля, рендерится в `FormFieldPrefix`. |
|
|
41
|
+
| `postfix` | `ReactNode` | — | Контент справа от поля (`FormFieldClear`, `HelpBox`, иконка), рендерится в `FormFieldPostfix`. |
|
|
42
|
+
| `description` | `ReactNode` | — | Описание под полем (`FormFieldDescription`). |
|
|
43
|
+
| `counter` | `ReactNode` | — | Счётчик символов справа под полем (`FormFieldCounter`). Блок описания рендерится, если задан `description` ИЛИ `counter`. |
|
|
61
44
|
|
|
62
|
-
|
|
45
|
+
Остальные неперечисленные props (`className`, data-атрибуты, div-атрибуты) пробрасываются на корневой `FormField` (div).
|
|
63
46
|
|
|
64
|
-
|
|
65
|
-
<FormGroup>
|
|
66
|
-
<FormField {...formFieldProps}>
|
|
67
|
-
{prefix && <FormFieldPrefix>{prefix}</FormFieldPrefix>}
|
|
68
|
-
<FormFieldTextarea {...textareaProps} />
|
|
69
|
-
{label && <FormFieldLabel>{label}</FormFieldLabel>}
|
|
70
|
-
{postfix && <FormFieldPostfix>{postfix}</FormFieldPostfix>}
|
|
71
|
-
</FormField>
|
|
72
|
-
{(description || counter) && (
|
|
73
|
-
<FormFieldDescription>
|
|
74
|
-
{description}
|
|
75
|
-
{counter && <FormFieldCounter>{counter}</FormFieldCounter>}
|
|
76
|
-
</FormFieldDescription>
|
|
77
|
-
)}
|
|
78
|
-
</FormGroup>
|
|
79
|
-
```
|
|
47
|
+
### Особенности поведения
|
|
80
48
|
|
|
81
|
-
|
|
82
|
-
|
|
49
|
+
- **Поле не растёт по содержимому.** У `<textarea>` выставлен `resize: none`, автоувеличения высоты нет — размер области ввода задаёт только `size` (`min-height` 64px для SM, 60px для MD и LG). Увеличить высоту можно через `textareaProps.rows`; уменьшить — только своим классом на `textareaProps.className`, переопределив `min-height` с достаточной специфичностью (правило задано селектором `.formFieldTextarea.lg`).
|
|
50
|
+
- **`disabled` задаётся только через `status`.** `FormFieldTextarea` выставляет `disabled={status === EFormFieldStatus.DISABLED}` после спреда остальных props, поэтому передать `disabled` в `textareaProps` нельзя — атрибут исключён из `IFormFieldTextareaProps` через `Omit`, и попытка его передать будет ошибкой компиляции. Для блокировки поля используй `status={EFormFieldStatus.DISABLED}`.
|
|
51
|
+
- **`placeholder` виден только в активном состоянии.** Пока поле не активно (нет фокуса и не передан `active`), плейсхолдер скрыт (`opacity: 0`) — его место занимает опущенный `label`. Когда поле становится активным, лейбл уезжает наверх и плейсхолдер проявляется. Поэтому плейсхолдер не заменяет лейбл: если `label` не задан, у пустого неактивного поля не будет видимой подписи.
|
|
52
|
+
- **Поле работает и контролируемым, и неконтролируемым.** Состояние «заполнено» (от него зависит положение лейбла) синхронизируется на маунте из `value ?? defaultValue`, далее — из `value` для контролируемого поля и из событий `change`/`focus`/`blur` для неконтролируемого.
|
|
53
|
+
- **Автозаполнение браузером отслеживается** через CSS-хуки `animationstart` (`autofill-applied-hook` / `autofill-cancelled-hook`), поэтому лейбл поднимается и при подстановке значения браузером. Свой `textareaProps.onAnimationStart` при этом вызывается.
|
|
54
|
+
- **Счётчик не встроен**: `maxLength` передаётся в `textareaProps`, а текст счётчика (`{value.length}/{maxLength}`) считает и рендерит потребитель в `counter`.
|
|
55
|
+
- Кнопка очистки не встроена — передаётся потребителем как `postfix={<FormFieldClear onClick={...} />}`. Блок постфикса позиционируется по центру поля по вертикали (`top: 50%` + `translateY(-50%)` в `FormFieldPostfix`), что для многострочного поля обычно не то, что нужно: чтобы прижать содержимое к верху, оберни его в div с `alignSelf: "flex-start"` и отрицательным `marginTop`, зависящим от `size` (см. `ProductionExample.tsx` и `PlaygroundExample.tsx`).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Дизайн-токены
|
|
60
|
+
|
|
61
|
+
Собственных CSS-токенов и стилей у `TextareaField` нет — поэтому `tokens` во frontmatter пустой. Внешний вид наследуется от семейства `FormField`; сам `<textarea>` красится токенами `FormField.Input_Color_Default`, `FormField.Input_Color_Disabled` и `FormField.Placeholder_Color`.
|
|
83
62
|
|
|
84
63
|
---
|
|
85
64
|
|
|
86
65
|
## Инварианты
|
|
87
66
|
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
67
|
+
- `forwardRef` на `TextareaField` — не убирать. Внешний `ref` указывает на корневой `<div>` `FormField`, ref на сам `<textarea>` передаётся через `textareaProps.ref`.
|
|
68
|
+
- Публичный API (`ITextareaFieldProps`: `textareaProps` + `label`/`prefix`/`postfix`/`description`/`counter` + унаследованные от `IFormFieldProps` `size`/`status`/`active`) — изменение имён/типов/значений enum — breaking change.
|
|
69
|
+
- `textareaProps` обязателен: у поля нет дефолтных атрибутов ввода. Минимально допустимое значение — пустой объект `{}`.
|
|
70
|
+
- Уникальный `id` для связки label↔textarea генерируется в `FormFieldTextarea` через `uniqueId("textarea_")` из `lodash-es` — не заменять на `useId` (React 17 совместимость через `release-0`).
|
|
71
|
+
- Высота и отступы области ввода завязаны на `size` через классы `sm`/`md`/`lg` в `FormFieldTextarea.module.less` — эти имена читаются unit-тестом и не переименовываются в одиночку.
|
|
93
72
|
|
|
94
73
|
---
|
|
95
74
|
|
|
96
75
|
## Accessibility
|
|
97
76
|
|
|
98
|
-
- `<
|
|
99
|
-
- `
|
|
100
|
-
-
|
|
77
|
+
- Лейбл — настоящий `<label htmlFor>`, связанный с `id` поля (`id` генерируется автоматически или берётся из `textareaProps.id`).
|
|
78
|
+
- `status={EFormFieldStatus.DISABLED}` выставляет нативный атрибут `disabled` — поле штатно выпадает из таб-обхода.
|
|
79
|
+
- Семантика ошибки (`aria-invalid`, `aria-describedby` на `description`) не хардкодится — при необходимости потребитель передаёт атрибуты через `textareaProps`.
|
|
80
|
+
- Плейсхолдер визуально скрыт до фокуса, но остаётся в DOM и читается скринридером. Смысловую подпись поля задавай через `label`, а не через `placeholder`.
|
|
81
|
+
- Собственной клавиатурной логики нет: `Enter` вставляет перенос строки нативно, форма по `Enter` из `<textarea>` не отправляется.
|
|
82
|
+
- Кнопке очистки в `postfix` доступное имя задаёт потребитель (`aria-label`) — библиотека мультиязычная и строк не хардкодит.
|
|
101
83
|
|
|
102
84
|
---
|
|
103
85
|
|
|
104
86
|
## Связанные компоненты
|
|
105
87
|
|
|
106
|
-
- `
|
|
107
|
-
- `
|
|
108
|
-
- `
|
|
109
|
-
- `
|
|
88
|
+
- `TextFieldBase` (внутренний, не в barrel) — базовая раскладка `FormGroup`/`FormField` + слоты `prefix`/`children`/`label`/`postfix` и блок `description`/`counter`. Описан в `TextField-ai.md`.
|
|
89
|
+
- `FormFieldTextarea` — сам элемент `<textarea>` со всей логикой поля. Используется напрямую, когда нужна нестандартная композиция.
|
|
90
|
+
- `FormGroup` / `FormField` — контейнеры раскладки поля и описания.
|
|
91
|
+
- `FormFieldClear` — кнопка очистки, передаётся в `postfix`.
|
|
92
|
+
- `HelpBox` — подсказка в `postfix`.
|
|
93
|
+
- `TextField`, `MaskedField`, `AmountField`, `SuggestField`, `DateField`, `MonthYearField` — специализированные поля той же группы `TextFields`.
|
|
110
94
|
|
|
111
95
|
---
|
|
112
96
|
|
|
113
97
|
## Stories
|
|
114
98
|
|
|
115
99
|
Основные истории: `stories/TextareaField/TextareaField.stories.tsx`
|
|
116
|
-
|
|
100
|
+
Файлы примеров: `stories/TextareaField/examples/`
|
|
101
|
+
|
|
102
|
+
| Story | Example file | Что демонстрирует |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль size / status / label / active / textareaProps и переключатели постфикса, описания и счётчика |
|
|
105
|
+
| `Default` | `DefaultExample.tsx` | Минимальное контролируемое поле с label и placeholder |
|
|
106
|
+
| `Sizes` | `SizesExample.tsx` | Размеры SM / MD / LG |
|
|
107
|
+
| `Statuses` | `StatusesExample.tsx` | Статусы default / error / warning / disabled |
|
|
108
|
+
| `Production` | `ProductionExample.tsx` | Production-композиция (`Example: production`): лимит длины со счётчиком, кнопка очистки и `HelpBox` в постфиксе, description со ссылкой |
|
|
117
109
|
|
|
118
110
|
---
|
|
119
111
|
|
|
@@ -121,4 +113,220 @@ extends Omit<ITextFieldBaseProps, "children">
|
|
|
121
113
|
|
|
122
114
|
| Дата | Изменение |
|
|
123
115
|
|---|---|
|
|
124
|
-
| 2026-
|
|
116
|
+
| 2026-09-17 | Создан документ (TRI-96). AI-рефакторинг `TextareaField`: проброс `ref` на корневой `<div>` через `forwardRef`, `displayName`, unit-тесты вместо mock-заглушек |
|
|
117
|
+
| 2026-09-17 | Ломающее изменение: `disabled` убран из `IFormFieldTextareaProps` (`Omit`), поэтому его больше нельзя передать в `textareaProps` — блокировка только через `status`. Правка по ревью PR #629, зафиксирована в release notes 1.47.0 |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Примеры использования
|
|
122
|
+
|
|
123
|
+
### Default
|
|
124
|
+
|
|
125
|
+
`stories/TextareaField/examples/DefaultExample.tsx`
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import React, { useState } from "react";
|
|
129
|
+
import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
130
|
+
|
|
131
|
+
export const DefaultExample = () => {
|
|
132
|
+
const [value, setValue] = useState("");
|
|
133
|
+
|
|
134
|
+
const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
|
|
135
|
+
setValue(event.target.value);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div style={{ maxWidth: 300 }}>
|
|
140
|
+
<TextareaField
|
|
141
|
+
size={EComponentSize.LG}
|
|
142
|
+
status={EFormFieldStatus.DEFAULT}
|
|
143
|
+
textareaProps={{
|
|
144
|
+
value,
|
|
145
|
+
placeholder: "Type to proceed",
|
|
146
|
+
onChange: handleTextareaChange,
|
|
147
|
+
}}
|
|
148
|
+
label="Label"
|
|
149
|
+
/>
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Sizes
|
|
156
|
+
|
|
157
|
+
`stories/TextareaField/examples/SizesExample.tsx`
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import React, { useState } from "react";
|
|
161
|
+
import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
162
|
+
|
|
163
|
+
interface ISizeItemProps {
|
|
164
|
+
size: EComponentSize;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const SizeItem = ({ size }: ISizeItemProps) => {
|
|
168
|
+
const [value, setValue] = useState("");
|
|
169
|
+
|
|
170
|
+
const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
|
|
171
|
+
setValue(event.target.value);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<div style={{ maxWidth: 300 }}>
|
|
176
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
|
|
177
|
+
<TextareaField
|
|
178
|
+
size={size}
|
|
179
|
+
status={EFormFieldStatus.DEFAULT}
|
|
180
|
+
textareaProps={{
|
|
181
|
+
value,
|
|
182
|
+
placeholder: "Type to proceed",
|
|
183
|
+
onChange: handleTextareaChange,
|
|
184
|
+
}}
|
|
185
|
+
label="Label"
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const SIZES = Object.values(EComponentSize);
|
|
192
|
+
|
|
193
|
+
export const SizesExample = () => (
|
|
194
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
195
|
+
{SIZES.map((size) => (
|
|
196
|
+
<SizeItem key={size} size={size} />
|
|
197
|
+
))}
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Statuses
|
|
203
|
+
|
|
204
|
+
`stories/TextareaField/examples/StatusesExample.tsx`
|
|
205
|
+
|
|
206
|
+
```tsx
|
|
207
|
+
import React, { useState } from "react";
|
|
208
|
+
import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
|
|
209
|
+
|
|
210
|
+
interface IStatusItemProps {
|
|
211
|
+
status: EFormFieldStatus;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const StatusItem = ({ status }: IStatusItemProps) => {
|
|
215
|
+
const [value, setValue] = useState("");
|
|
216
|
+
|
|
217
|
+
const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
|
|
218
|
+
setValue(event.target.value);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<div style={{ maxWidth: 300 }}>
|
|
223
|
+
<div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
|
|
224
|
+
<TextareaField
|
|
225
|
+
size={EComponentSize.LG}
|
|
226
|
+
status={status}
|
|
227
|
+
textareaProps={{
|
|
228
|
+
value,
|
|
229
|
+
placeholder: "Type to proceed",
|
|
230
|
+
onChange: handleTextareaChange,
|
|
231
|
+
}}
|
|
232
|
+
label="Label"
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const STATUSES = Object.values(EFormFieldStatus);
|
|
239
|
+
|
|
240
|
+
export const StatusesExample = () => (
|
|
241
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
242
|
+
{STATUSES.map((status) => (
|
|
243
|
+
<StatusItem key={status} status={status} />
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Production
|
|
250
|
+
|
|
251
|
+
`stories/TextareaField/examples/ProductionExample.tsx`
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import React, { useState, useRef } from "react";
|
|
255
|
+
import {
|
|
256
|
+
TextareaField,
|
|
257
|
+
FormFieldClear,
|
|
258
|
+
Text,
|
|
259
|
+
HelpBox,
|
|
260
|
+
Link,
|
|
261
|
+
EComponentSize,
|
|
262
|
+
EFormFieldStatus,
|
|
263
|
+
ETextSize,
|
|
264
|
+
EFontType,
|
|
265
|
+
ETooltipSize,
|
|
266
|
+
} from "@sberbusiness/triplex-next";
|
|
267
|
+
|
|
268
|
+
const postfixStyles: React.CSSProperties = {
|
|
269
|
+
alignSelf: "flex-start",
|
|
270
|
+
marginTop: -4, // 20px from top
|
|
271
|
+
display: "flex",
|
|
272
|
+
alignItems: "center",
|
|
273
|
+
gap: 8,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const MAX_LENGTH = 201;
|
|
277
|
+
|
|
278
|
+
export const ProductionExample = () => {
|
|
279
|
+
const [value, setValue] = useState("");
|
|
280
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
281
|
+
|
|
282
|
+
const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
|
|
283
|
+
setValue(event.target.value);
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const handleClearButtonClick: React.MouseEventHandler<HTMLButtonElement> = () => {
|
|
287
|
+
setValue("");
|
|
288
|
+
textareaRef.current?.focus();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
|
|
292
|
+
event.preventDefault();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
<div style={{ maxWidth: 300 }}>
|
|
297
|
+
<TextareaField
|
|
298
|
+
size={EComponentSize.LG}
|
|
299
|
+
status={EFormFieldStatus.DEFAULT}
|
|
300
|
+
textareaProps={{
|
|
301
|
+
value,
|
|
302
|
+
placeholder: "Type to proceed",
|
|
303
|
+
maxLength: MAX_LENGTH,
|
|
304
|
+
onChange: handleTextareaChange,
|
|
305
|
+
ref: textareaRef,
|
|
306
|
+
}}
|
|
307
|
+
label="Label"
|
|
308
|
+
postfix={
|
|
309
|
+
<div style={postfixStyles}>
|
|
310
|
+
<FormFieldClear aria-label="clear value" onClick={handleClearButtonClick} />
|
|
311
|
+
<HelpBox tooltipSize={ETooltipSize.SM}>Helpful details appear here</HelpBox>
|
|
312
|
+
</div>
|
|
313
|
+
}
|
|
314
|
+
description={
|
|
315
|
+
<Text size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
316
|
+
(21) Description{" "}
|
|
317
|
+
<Link href="#" onClick={handleLinkClick}>
|
|
318
|
+
Link text
|
|
319
|
+
</Link>
|
|
320
|
+
</Text>
|
|
321
|
+
}
|
|
322
|
+
counter={
|
|
323
|
+
<Text size={ETextSize.B4} type={EFontType.SECONDARY}>
|
|
324
|
+
{value.length}/{MAX_LENGTH}
|
|
325
|
+
</Text>
|
|
326
|
+
}
|
|
327
|
+
/>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
};
|
|
331
|
+
```
|
|
332
|
+
|
|
@@ -110,7 +110,10 @@ Runtime CSS-переменная (задаётся компонентом чер
|
|
|
110
110
|
|
|
111
111
|
Кроме токена компонент читает служебные CSS-переменные `LightBox`:
|
|
112
112
|
`--lightBox-screen-top` (в TS, для позиционирования), `--lightBox-screen-left` и
|
|
113
|
-
`--lightBox-screen-width` (в LESS, для растяжки маски по экрану лайтбокса)
|
|
113
|
+
`--lightBox-screen-width` (в LESS, для растяжки маски по экрану лайтбокса),
|
|
114
|
+
`--lightBox-content-padding-x` (боковые отступы контента лайтбокса — их повторяет
|
|
115
|
+
обёртка панели) и `--lightBox-content-max-width` (потолок ширины колонки страницы —
|
|
116
|
+
его повторяет сама панель).
|
|
114
117
|
|
|
115
118
|
---
|
|
116
119
|
|
|
@@ -135,6 +138,18 @@ Runtime CSS-переменная (задаётся компонентом чер
|
|
|
135
138
|
- Props потребителя во вложенный `Overlay` не уходят: `className` и `direction`
|
|
136
139
|
задаются ему явно, а `ref`, `className`, `style` и DOM-атрибуты остаются на
|
|
137
140
|
обёртке. Спреда `restProps` в `Overlay` больше нет — не возвращать.
|
|
141
|
+
- Ширина панели рассчитана на то, что ближайший позиционированный предок обёртки —
|
|
142
|
+
`.lightBoxContent` (`LightBox.Content`): от него берутся боковые отступы
|
|
143
|
+
(`--lightBox-content-padding-x`). Потолок ширины (`--lightBox-content-max-width`)
|
|
144
|
+
приходит наследованием с корневого `.lightBox`, где он и объявлен. Внутри другого
|
|
145
|
+
позиционированного контейнера отступы отсчитаются от него и панель уедет.
|
|
146
|
+
Вне `LightBox` обе переменные не определены: отступы дают фолбэк 0, а `max-width`
|
|
147
|
+
схлопывается в `none`, то есть ширину панель занимает всю. Боковые паддинги 32px
|
|
148
|
+
и авто-маргины при этом остаются — содержимое отступает от краёв панели.
|
|
149
|
+
- Внутри `LightBox.SideOverlay` панель по колонке `Page` **не** выравнивается: сайд-оверлей
|
|
150
|
+
задаёт странице свою ширину (`@sideOverlay{SM,MD,LG}Width`), а панель ограничена шириной
|
|
151
|
+
колонки лайтбокса. Расхождение маскирует `Confirm` с `parentComponent`; для произвольного
|
|
152
|
+
содержимого его нужно ограничивать самому.
|
|
138
153
|
- Родительскому `LightBox` потребитель обязан передать `isTopOverlayOpened` —
|
|
139
154
|
иначе контролы лайтбокса не приглушатся под открытой панелью.
|
|
140
155
|
- Классы `.topOverlayMask`, `.topOverlayPanel`, `.topOverlayWrapper` и `.topOverlay`
|
|
@@ -202,6 +217,7 @@ Runtime CSS-переменная (задаётся компонентом чер
|
|
|
202
217
|
|
|
203
218
|
| Дата | Изменение |
|
|
204
219
|
|---|---|
|
|
220
|
+
| 2026-09-21 | Панель выровнена по колонке контента `LightBox`: обёртка сводится к колонке через `--lightBox-content-padding-x`, панель повторяет `--lightBox-content-max-width` и боковые паддинги `Page`. |
|
|
205
221
|
| 2026-08-27 | `TopOverlay` переведён на `forwardRef`, `ITopOverlayProps` расширен атрибутами `<div>`: `ref`, `className`, `style` и DOM-атрибуты адресуют обёртку. Монтирование сразу с `opened={true}` запускает цикл открытия (ловушка фокуса, `onOpen`) — исправлено в `OverlayBase`, см. `Overlay-ai.md`; пересчёт позиции на маунте намеренно не делается. Рассчитанная позиция переехала из inline-`top` в runtime-переменную `--triplex-next-runtime-TopOverlay-Top`. |
|
|
206
222
|
| 2026-08-25 | Создан документ. AI-рефакторинг: позиционирование вынесено в `utils.ts`, добавлен `displayName`, unit-тесты на расчёт позиции и ловушку фокуса; добавлены stories `stories/TopOverlay/` |
|
|
207
223
|
|
|
@@ -32,12 +32,14 @@ JSX-разметке, управление `tabIndex` и перемещение
|
|
|
32
32
|
|
|
33
33
|
### `TreeView`
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
стандартные атрибуты
|
|
35
|
+
`forwardRef`-обёртка над внутренним class-компонентом `TreeViewBase`.
|
|
36
|
+
Собственных props, кроме `children`, нет — остальное это стандартные атрибуты
|
|
37
|
+
`<ul>`.
|
|
37
38
|
|
|
38
39
|
| Prop | Тип | По умолчанию | Описание |
|
|
39
40
|
|---|---|---|---|
|
|
40
41
|
| `children` | `React.ReactNode` | — | Ноды дерева: `TreeView.Node` напрямую или компоненты-обёртки над ним |
|
|
42
|
+
| `ref` | `React.Ref<HTMLUListElement>` | — | Указывает на корневой `<ul role="tree">` |
|
|
41
43
|
| `...HTMLUListElementAttributes` | — | — | Все стандартные атрибуты `<ul>`; `className` мерджится с собственным классом |
|
|
42
44
|
|
|
43
45
|
Статические свойства: `TreeView.Node` (= `TreeViewNode`),
|
|
@@ -68,13 +70,19 @@ render-prop `children` с пятью provide-props, контракт по `focus
|
|
|
68
70
|
|
|
69
71
|
## Инварианты
|
|
70
72
|
|
|
71
|
-
- **`
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
- **`ref` на `TreeView` отдаёт корневой `<ul role="tree">`.** Наружу компонент
|
|
74
|
+
экспортируется как `React.ForwardRefExoticComponent`: реализация осталась
|
|
75
|
+
классом `TreeViewBase`, а публичный `TreeView` — обёртка `forwardRef`, которая
|
|
76
|
+
передаёт внешний `ref` в класс свойством `forwardedRef`. Класс нельзя обернуть
|
|
77
|
+
`forwardRef` напрямую, отсюда и эта прослойка. До 1.47.0 `ref` отдавал инстанс
|
|
78
|
+
класса — см. release notes 1.47.0.
|
|
79
|
+
- **`TreeViewBase` и `TreeViewNodeWithContext` — class-компоненты.** Это legacy;
|
|
80
|
+
перевод на функциональные компоненты не делается без отдельного решения
|
|
81
|
+
мейнтейнера. `TreeViewBase` наружу не экспортируется.
|
|
75
82
|
- **Barrel `src/components/TreeView/index.ts`** реэкспортирует модули семейства
|
|
76
83
|
целиком (`export * from ...`), поэтому публично всё, что они экспортируют:
|
|
77
|
-
`TreeView` и `
|
|
84
|
+
`TreeView`, `ITreeViewProps` и `ITreeViewFC`, `TreeViewGroup` и
|
|
85
|
+
`ITreeViewGroupProps`, а от
|
|
78
86
|
ноды — состав, перечисленный в
|
|
79
87
|
[TreeViewNode-ai.md](./TreeViewNode-ai.md) (там же и класс
|
|
80
88
|
`TreeViewNodeWithContext`). Состав и имена менять нельзя — от них зависят
|
|
@@ -163,6 +171,7 @@ render-prop `children` с пятью provide-props, контракт по `focus
|
|
|
163
171
|
|
|
164
172
|
| Дата | Изменение |
|
|
165
173
|
|---|---|
|
|
174
|
+
| 2026-09-17 | TRI-22: `ref` на `TreeView` переведён с инстанса класса на корневой `<ul>`; реализация вынесена во внутренний `TreeViewBase`, наружу экспортируется обёртка `forwardRef` со статикой `Node` / `Group` |
|
|
166
175
|
| 2026-08-26 | Описание `TreeView.Node` вынесено в отдельный `TreeViewNode-ai.md`, здесь оставлена ссылка |
|
|
167
176
|
| 2026-08-25 | Слушатель `keydown` подписан только пока в дереве есть активная нода: после ухода фокуса из дерева стрелки снова достаются странице. `role` и `aria-expanded` перенесены после `{...props}` — потребитель больше не может их переопределить |
|
|
168
177
|
| 2026-08-24 | `ArrowUp` с первой ноды дерева больше не заходит внутрь свёрнутой последней ветки — активной становится последняя видимая нода (`TreeViewAbstractNodeUtils.getPrevNode`) |
|