@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.
Files changed (60) hide show
  1. package/README.md +7 -7
  2. package/bundle/components/AmountField.md +222 -323
  3. package/bundle/components/Avatar.md +138 -146
  4. package/bundle/components/CardAction.md +615 -0
  5. package/bundle/components/CardStatic.md +402 -0
  6. package/bundle/components/CheckboxTree.md +157 -215
  7. package/bundle/components/CheckboxTreeExtended.md +7 -7
  8. package/bundle/components/Chip.md +7 -6
  9. package/bundle/components/ChipGroup.md +1 -1
  10. package/bundle/components/ChipMonthYearPicker.md +344 -0
  11. package/bundle/components/ChipMultiselect.md +1251 -0
  12. package/bundle/components/ChipOptions.md +329 -0
  13. package/bundle/components/ChipSort.md +333 -0
  14. package/bundle/components/ChipSuggest.md +588 -0
  15. package/bundle/components/CollapsibleTree.md +8 -4
  16. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  17. package/bundle/components/Confirm.md +346 -281
  18. package/bundle/components/DateField.md +222 -303
  19. package/bundle/components/DatePickerExtended.md +3 -1
  20. package/bundle/components/DateRange.md +351 -210
  21. package/bundle/components/FooterPage.md +1 -0
  22. package/bundle/components/FormField.md +14 -5
  23. package/bundle/components/FormGroup.md +3 -2
  24. package/bundle/components/HelpBox.md +7 -1
  25. package/bundle/components/IslandAccordion.md +443 -152
  26. package/bundle/components/IslandWidget.md +622 -157
  27. package/bundle/components/LightBox.md +8 -0
  28. package/bundle/components/ListMaster.md +2 -2
  29. package/bundle/components/ListTableItem.md +6 -6
  30. package/bundle/components/MaskedField.md +2 -1
  31. package/bundle/components/MonthYearField.md +18 -5
  32. package/bundle/components/Slider.md +389 -97
  33. package/bundle/components/SliderRange.md +486 -105
  34. package/bundle/components/StatusTracker.md +386 -94
  35. package/bundle/components/Stepper.md +355 -102
  36. package/bundle/components/StepperExtended.md +633 -0
  37. package/bundle/components/SuggestField.md +1049 -96
  38. package/bundle/components/SwipeableArea.md +7 -7
  39. package/bundle/components/Tabs.md +265 -92
  40. package/bundle/components/TabsExtended.md +18 -18
  41. package/bundle/components/TabsLine.md +301 -127
  42. package/bundle/components/TagColor.md +187 -66
  43. package/bundle/components/TagGroup.md +184 -48
  44. package/bundle/components/TextareaField.md +278 -70
  45. package/bundle/components/TopOverlay.md +17 -1
  46. package/bundle/components/TreeView.md +16 -7
  47. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  48. package/bundle/components/UploadZone.md +318 -0
  49. package/bundle/guides/codestyle.md +16 -1
  50. package/bundle/guides/commits.md +47 -1
  51. package/bundle/guides/tests.md +23 -2
  52. package/bundle/manifest.json +92 -24
  53. package/bundle/release-notes/1.46.0.mdx +59 -0
  54. package/bundle/release-notes/1.47.0.mdx +122 -0
  55. package/bundle/release-notes/1.48.0.mdx +68 -0
  56. package/bundle/release-notes/1.49.0.mdx +7 -0
  57. package/bundle/tokens.json +3 -3
  58. package/dist/index.js +220 -6
  59. package/dist/index.js.map +1 -1
  60. package/package.json +1 -1
@@ -1,9 +1,7 @@
1
1
  ---
2
2
  component: TextareaField
3
- category: Form Controls
4
- figma-node: ""
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
- Многострочное текстовое поле: тонкая обёртка над `TextFieldBase`,
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
- ## Публичный API
22
+ ## Варианты и props
39
23
 
40
- ### `ITextareaFieldProps`
24
+ `TextareaField` — тонкая обёртка без собственного state: `textareaProps` уходит в `FormFieldTextarea`, остальные props — в `TextFieldBase` (и далее в `FormField`). Логика поля (генерация `id`, отслеживание фокуса и заполненности, реакция на автозаполнение) живёт в `FormFieldTextarea`.
41
25
 
42
- ```ts
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>` | **Обязателен**. Прокидывается на `FormFieldTextarea`. Включая `ref` на textarea. |
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
- `IFormFieldProps` приходит из `FormField` (см. FormField-AI.md, если существует).
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
- ## Структура DOM
45
+ Остальные неперечисленные props (`className`, data-атрибуты, div-атрибуты) пробрасываются на корневой `FormField` (div).
63
46
 
64
- ```tsx
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
- Реализация полностью наследуется от `TextFieldBase` — изменения в нём
82
- автоматически распространяются на `TextareaField`.
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
- - **`FC, не forwardRef`** — для ref'а на DIV-обёртку используется `TextFieldBase` (forwardRef'нутый), но `TextareaField` сам ref не пробрасывает на корневой элемент. Ref на `<textarea>` — через `textareaProps.ref`.
89
- - **`textareaProps` обязателен** (нет default'а).
90
- - **Label рендерится ПОСЛЕ children** в DOM — это требование `FormField` (для CSS-эффекта плавающего лейбла); порядок не менять.
91
- - **`description` и `counter` объединены в одну `FormFieldDescription`** — counter внутри `<FormFieldCounter>`. Не дублировать.
92
- - **Структура зашита** — нет children API. Для кастомного содержимого `FormField` использовать `TextFieldBase` напрямую.
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
- - `<textarea>` приходит из `FormFieldTextarea` — он отвечает за `id`, `aria-describedby`, `aria-invalid` и т.п.
99
- - `label` — `<label>` с правильным `htmlFor` от `FormField`/`FormFieldLabel`.
100
- - Ошибки — через `description` + `status` `FormField`'а.
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
- - `TextField` — однострочный аналог.
107
- - `TextFieldBase` — общая база.
108
- - `FormFieldTextarea` — реальный `<textarea>` с обёрткой стилей.
109
- - `FormField` / `FormGroup` / `FormFieldLabel` / `FormFieldDescription` / `FormFieldCounter` / `FormFieldPrefix` / `FormFieldPostfix` — внутренние составляющие.
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
- Примеры: `stories/TextareaField/examples/`
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-04-18 | Создан документ |
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
- Class-компонент. Собственных props, кроме `children`, нет — остальное это
36
- стандартные атрибуты `<ul>`.
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
- - **`TreeView` и `TreeViewNodeWithContext` — class-компоненты.** Это legacy,
72
- зафиксированный публичным API: `ref` на `TreeView` отдаёт инстанс класса.
73
- Перевод на функциональные компоненты и добавление `forwardRef` — breaking
74
- change поведения `ref`. Не мигрировать без отдельного решения мейнтейнера.
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` и `ITreeViewProps`, `TreeViewGroup` и `ITreeViewGroupProps`, а от
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`) |