@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.50.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 (110) hide show
  1. package/README.md +7 -7
  2. package/bundle/components/Amount.md +2 -1
  3. package/bundle/components/AmountField.md +222 -323
  4. package/bundle/components/Avatar.md +138 -146
  5. package/bundle/components/Badge.md +2 -1
  6. package/bundle/components/BodyPage.md +2 -1
  7. package/bundle/components/Button.md +5 -1
  8. package/bundle/components/ButtonDropdownExtended.md +3 -1
  9. package/bundle/components/ButtonIcon.md +3 -1
  10. package/bundle/components/Calendar.md +3 -1
  11. package/bundle/components/CardAction.md +1 -0
  12. package/bundle/components/CardStatic.md +402 -0
  13. package/bundle/components/CheckboxTree.md +157 -215
  14. package/bundle/components/CheckboxTreeExtended.md +7 -7
  15. package/bundle/components/CheckboxXGroup.md +2 -1
  16. package/bundle/components/Chip.md +2 -1
  17. package/bundle/components/ChipGroup.md +1 -1
  18. package/bundle/components/ChipMonthYearPicker.md +6 -5
  19. package/bundle/components/ChipMultiselect.md +4 -1
  20. package/bundle/components/ChipOptions.md +2 -1
  21. package/bundle/components/ChipSort.md +4 -1
  22. package/bundle/components/ChipSuggest.md +7 -6
  23. package/bundle/components/CollapsibleTree.md +10 -5
  24. package/bundle/components/CollapsibleTreeExtended.md +8 -6
  25. package/bundle/components/ComposedKeyDownListener.md +4 -3
  26. package/bundle/components/Confirm.md +346 -281
  27. package/bundle/components/DateField.md +7 -1
  28. package/bundle/components/DateRange.md +351 -210
  29. package/bundle/components/Divider.md +85 -138
  30. package/bundle/components/DocumentNumberEdit.md +173 -214
  31. package/bundle/components/Dropdown.md +11 -1
  32. package/bundle/components/Ellipsis.md +131 -147
  33. package/bundle/components/FooterPage.md +4 -1
  34. package/bundle/components/FormField.md +13 -5
  35. package/bundle/components/FormGroup.md +3 -2
  36. package/bundle/components/Gap.md +1 -1
  37. package/bundle/components/Header.md +1 -0
  38. package/bundle/components/HeaderPage.md +3 -1
  39. package/bundle/components/HelpBox.md +2 -1
  40. package/bundle/components/IconWrapper.md +10 -6
  41. package/bundle/components/ImageGallery.md +2 -1
  42. package/bundle/components/ImageGalleryExtended.md +2 -1
  43. package/bundle/components/IslandAccordion.md +6 -1
  44. package/bundle/components/KeyDownListener.md +3 -2
  45. package/bundle/components/LightBox.md +15 -1
  46. package/bundle/components/List.md +2 -1
  47. package/bundle/components/ListActionItem.md +2 -1
  48. package/bundle/components/ListItem.md +3 -1
  49. package/bundle/components/ListItemControlsButton.md +2 -1
  50. package/bundle/components/ListItemSelectable.md +2 -1
  51. package/bundle/components/ListMaster.md +2 -2
  52. package/bundle/components/ListMasterFooter.md +2 -1
  53. package/bundle/components/ListMasterHeader.md +2 -1
  54. package/bundle/components/ListSortable.md +2 -1
  55. package/bundle/components/ListSortableItem.md +2 -1
  56. package/bundle/components/ListTableItem.md +10 -7
  57. package/bundle/components/LoaderMiddle.md +5 -1
  58. package/bundle/components/LoaderScreen.md +7 -1
  59. package/bundle/components/LoaderSmall.md +10 -4
  60. package/bundle/components/MaskedField.md +3 -1
  61. package/bundle/components/MediaWidth.md +2 -1
  62. package/bundle/components/MobileView.md +2 -1
  63. package/bundle/components/ModalWindow.md +4 -1
  64. package/bundle/components/MonthYearField.md +19 -5
  65. package/bundle/components/Notification.md +1 -0
  66. package/bundle/components/Overlay.md +7 -2
  67. package/bundle/components/Pagination.md +1 -7
  68. package/bundle/components/Radio.md +238 -115
  69. package/bundle/components/RadioXGroup.md +259 -0
  70. package/bundle/components/RadioYGroup.md +266 -0
  71. package/bundle/components/SegmentedControl.md +505 -158
  72. package/bundle/components/SelectExtendedField.md +2 -1
  73. package/bundle/components/SelectField.md +4 -1
  74. package/bundle/components/Skeleton.md +140 -69
  75. package/bundle/components/Slider.md +389 -97
  76. package/bundle/components/SliderRange.md +486 -105
  77. package/bundle/components/Spoiler.md +245 -63
  78. package/bundle/components/StatusTracker.md +386 -94
  79. package/bundle/components/Step.md +257 -68
  80. package/bundle/components/Stepper.md +355 -102
  81. package/bundle/components/StepperExtended.md +633 -0
  82. package/bundle/components/SuggestField.md +1061 -96
  83. package/bundle/components/SwipeableArea.md +7 -7
  84. package/bundle/components/Tabs.md +266 -92
  85. package/bundle/components/TabsExtended.md +18 -18
  86. package/bundle/components/TabsLine.md +302 -127
  87. package/bundle/components/TagColor.md +187 -66
  88. package/bundle/components/TagGroup.md +184 -48
  89. package/bundle/components/TextField.md +8 -4
  90. package/bundle/components/TextareaField.md +279 -70
  91. package/bundle/components/ThemeProvider.md +2 -1
  92. package/bundle/components/Tooltip.md +6 -3
  93. package/bundle/components/TopOverlay.md +17 -1
  94. package/bundle/components/TreeView.md +16 -7
  95. package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
  96. package/bundle/components/UploadZone.md +318 -0
  97. package/bundle/guides/codestyle.md +16 -1
  98. package/bundle/guides/commits.md +47 -1
  99. package/bundle/guides/context.md +4 -1
  100. package/bundle/guides/tests.md +23 -2
  101. package/bundle/manifest.json +78 -29
  102. package/bundle/release-notes/1.47.0.mdx +115 -0
  103. package/bundle/release-notes/1.48.0.mdx +68 -0
  104. package/bundle/release-notes/1.49.0.mdx +85 -0
  105. package/bundle/release-notes/1.50.0.mdx +35 -0
  106. package/bundle/release-notes/1.51.0.mdx +7 -0
  107. package/bundle/tokens.json +163 -13
  108. package/dist/index.js +220 -6
  109. package/dist/index.js.map +1 -1
  110. 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, FormField, FormGroup]
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,221 @@ 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
+ | 2026-09-29 | Аудит симметрии `related` (TRI-156): из `related` убраны несимметричные имена (`MaskedField`, `DateField`, `MonthYearField`). Пояснения к убранным именам сохранены прозой в «Связанные компоненты»; публичный API и поведение не затронуты. |
119
+
120
+ ---
121
+
122
+ ## Примеры использования
123
+
124
+ ### Default
125
+
126
+ `stories/TextareaField/examples/DefaultExample.tsx`
127
+
128
+ ```tsx
129
+ import React, { useState } from "react";
130
+ import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
131
+
132
+ export const DefaultExample = () => {
133
+ const [value, setValue] = useState("");
134
+
135
+ const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
136
+ setValue(event.target.value);
137
+ };
138
+
139
+ return (
140
+ <div style={{ maxWidth: 300 }}>
141
+ <TextareaField
142
+ size={EComponentSize.LG}
143
+ status={EFormFieldStatus.DEFAULT}
144
+ textareaProps={{
145
+ value,
146
+ placeholder: "Type to proceed",
147
+ onChange: handleTextareaChange,
148
+ }}
149
+ label="Label"
150
+ />
151
+ </div>
152
+ );
153
+ };
154
+ ```
155
+
156
+ ### Sizes
157
+
158
+ `stories/TextareaField/examples/SizesExample.tsx`
159
+
160
+ ```tsx
161
+ import React, { useState } from "react";
162
+ import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
163
+
164
+ interface ISizeItemProps {
165
+ size: EComponentSize;
166
+ }
167
+
168
+ const SizeItem = ({ size }: ISizeItemProps) => {
169
+ const [value, setValue] = useState("");
170
+
171
+ const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
172
+ setValue(event.target.value);
173
+ };
174
+
175
+ return (
176
+ <div style={{ maxWidth: 300 }}>
177
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
178
+ <TextareaField
179
+ size={size}
180
+ status={EFormFieldStatus.DEFAULT}
181
+ textareaProps={{
182
+ value,
183
+ placeholder: "Type to proceed",
184
+ onChange: handleTextareaChange,
185
+ }}
186
+ label="Label"
187
+ />
188
+ </div>
189
+ );
190
+ };
191
+
192
+ const SIZES = Object.values(EComponentSize);
193
+
194
+ export const SizesExample = () => (
195
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
196
+ {SIZES.map((size) => (
197
+ <SizeItem key={size} size={size} />
198
+ ))}
199
+ </div>
200
+ );
201
+ ```
202
+
203
+ ### Statuses
204
+
205
+ `stories/TextareaField/examples/StatusesExample.tsx`
206
+
207
+ ```tsx
208
+ import React, { useState } from "react";
209
+ import { TextareaField, EComponentSize, EFormFieldStatus } from "@sberbusiness/triplex-next";
210
+
211
+ interface IStatusItemProps {
212
+ status: EFormFieldStatus;
213
+ }
214
+
215
+ const StatusItem = ({ status }: IStatusItemProps) => {
216
+ const [value, setValue] = useState("");
217
+
218
+ const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
219
+ setValue(event.target.value);
220
+ };
221
+
222
+ return (
223
+ <div style={{ maxWidth: 300 }}>
224
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
225
+ <TextareaField
226
+ size={EComponentSize.LG}
227
+ status={status}
228
+ textareaProps={{
229
+ value,
230
+ placeholder: "Type to proceed",
231
+ onChange: handleTextareaChange,
232
+ }}
233
+ label="Label"
234
+ />
235
+ </div>
236
+ );
237
+ };
238
+
239
+ const STATUSES = Object.values(EFormFieldStatus);
240
+
241
+ export const StatusesExample = () => (
242
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
243
+ {STATUSES.map((status) => (
244
+ <StatusItem key={status} status={status} />
245
+ ))}
246
+ </div>
247
+ );
248
+ ```
249
+
250
+ ### Production
251
+
252
+ `stories/TextareaField/examples/ProductionExample.tsx`
253
+
254
+ ```tsx
255
+ import React, { useState, useRef } from "react";
256
+ import {
257
+ TextareaField,
258
+ FormFieldClear,
259
+ Text,
260
+ HelpBox,
261
+ Link,
262
+ EComponentSize,
263
+ EFormFieldStatus,
264
+ ETextSize,
265
+ EFontType,
266
+ ETooltipSize,
267
+ } from "@sberbusiness/triplex-next";
268
+
269
+ const postfixStyles: React.CSSProperties = {
270
+ alignSelf: "flex-start",
271
+ marginTop: -4, // 20px from top
272
+ display: "flex",
273
+ alignItems: "center",
274
+ gap: 8,
275
+ };
276
+
277
+ const MAX_LENGTH = 201;
278
+
279
+ export const ProductionExample = () => {
280
+ const [value, setValue] = useState("");
281
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
282
+
283
+ const handleTextareaChange: React.ChangeEventHandler<HTMLTextAreaElement> = (event) => {
284
+ setValue(event.target.value);
285
+ };
286
+
287
+ const handleClearButtonClick: React.MouseEventHandler<HTMLButtonElement> = () => {
288
+ setValue("");
289
+ textareaRef.current?.focus();
290
+ };
291
+
292
+ const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
293
+ event.preventDefault();
294
+ };
295
+
296
+ return (
297
+ <div style={{ maxWidth: 300 }}>
298
+ <TextareaField
299
+ size={EComponentSize.LG}
300
+ status={EFormFieldStatus.DEFAULT}
301
+ textareaProps={{
302
+ value,
303
+ placeholder: "Type to proceed",
304
+ maxLength: MAX_LENGTH,
305
+ onChange: handleTextareaChange,
306
+ ref: textareaRef,
307
+ }}
308
+ label="Label"
309
+ postfix={
310
+ <div style={postfixStyles}>
311
+ <FormFieldClear aria-label="clear value" onClick={handleClearButtonClick} />
312
+ <HelpBox tooltipSize={ETooltipSize.SM}>Helpful details appear here</HelpBox>
313
+ </div>
314
+ }
315
+ description={
316
+ <Text size={ETextSize.B4} type={EFontType.SECONDARY}>
317
+ (21) Description{" "}
318
+ <Link href="#" onClick={handleLinkClick}>
319
+ Link text
320
+ </Link>
321
+ </Text>
322
+ }
323
+ counter={
324
+ <Text size={ETextSize.B4} type={EFontType.SECONDARY}>
325
+ {value.length}/{MAX_LENGTH}
326
+ </Text>
327
+ }
328
+ />
329
+ </div>
330
+ );
331
+ };
332
+ ```
333
+
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: ThemeProvider
3
3
  category: ThemeProvider
4
- related: [useToken]
4
+ related: []
5
5
  tokens: []
6
6
  stories: stories/ThemeProvider/ThemeProvider.stories.tsx
7
7
  version: "1.0"
@@ -194,6 +194,7 @@ const App = () => {
194
194
  | 2026-08-05 | Создан документ. AI-рефакторинг: JSDoc на props и экспортах, ключ тега стилей вынесен в хелпер, упрощён эффект `ThemeProviderView`, добавлены unit-тесты `ThemeProviderView` и `useToken`, stories переведены на modern pattern |
195
195
  | 2026-08-05 | `related` сокращён до `useToken`. Удалён неактуальный комментарий про относительный импорт в `ThemeProviderView` |
196
196
  | 2026-08-31 | Добавлен раздел «Как переопределять токены»: путь `{Группа}.{Токен}`, плоская форма prop `tokens`, `value` / `ref` |
197
+ | 2026-09-29 | Аудит симметрии `related` (TRI-156): из `related` убраны имена без строки в `docs/ai/ROADMAP.md` (`useToken`). Пояснения к убранным именам сохранены прозой в «Связанные компоненты»; публичный API и поведение не затронуты. |
197
198
 
198
199
  ---
199
200
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: Tooltip
3
3
  category: Tooltip
4
- related: [HelpBox, SMSField, Dropdown, DropdownMobile, Portal, MobileView, ThemeProvider, ButtonIcon]
4
+ related: [HelpBox, Dropdown, Portal, MobileView, ThemeProvider, ButtonIcon]
5
5
  tokens:
6
6
  - Tooltip.Background
7
7
  - Tooltip.Color
@@ -160,12 +160,14 @@ Tooltip.Link_Mobile_Shadow_Focus
160
160
  - `TooltipContext` (`TootlipContext.ts`) — разобранные слоты, состояние открытости и сеттер между частями.
161
161
  - `utils/Positioning.ts` — чистые функции выбора зоны и расчёта координат.
162
162
  - `utils/useTooltipTheme.ts` — класс темы на контейнере портала со счётчиком использования.
163
- - `Portal`, `MobileView`, `DropdownMobile` — инфраструктура рендера.
163
+ - `Portal`, `MobileView` — инфраструктура рендера.
164
+ - `Dropdown` — мобильная версия подсказки построена на его оверлее (`DropdownMobile`, `DropdownMobileClose`).
165
+ - `ThemeProvider` — источник темы: `useTooltipTheme` берёт `scopeClassName` через `useToken` и ставит класс на контейнере портала.
164
166
 
165
167
  **Потребители внутри библиотеки:**
166
168
 
167
169
  - `HelpBox` — готовая иконка-подсказка поверх `Tooltip`.
168
- - `SMSField.Tooltip` — подсказка об ошибке у поля СМС-кода.
170
+ - `SMSField` — `SMSField.Tooltip` показывает подсказку об ошибке у поля СМС-кода.
169
171
 
170
172
  ---
171
173
 
@@ -197,6 +199,7 @@ Tooltip.Link_Mobile_Shadow_Focus
197
199
  | 2026-08-03 | Правки по ревью PR #496. Исправлено: класс темы не снимается с контейнера, если в течение задержки закрытия подсказку открыли снова или контейнер занял другой `Tooltip` (в `useTooltipTheme` появились учёт собственного инкремента и перепроверка счётчика). Исправлено: в адаптивном режиме `size` и `alignTip` больше не уходят в DOM (React предупреждал о нераспознанном атрибуте `alignTip`). Исправлено: при размонтировании открытой подсказки с `toggleType="hover"` обработчики наведения снимаются и с самой подсказки. В `collectTooltipElements` приведения через `as` заменены на typed-guard'ы. Публичный API не изменён. |
198
200
  | 2026-08-03 | Правки по второму ревью PR #496 (документация и stories, кода не касаются): в «Ограничения» добавлено, что `onKeyDown` на десктопе не доходит до корневого элемента подсказки, а в адаптивном режиме доходит; уточнено, что в адаптивном режиме кнопка закрытия рендерится всегда и без `Tooltip.XButton` остаётся без `aria-label`; в `VisualTests` добавлен блок с `alignTip: end` — раньше это значение enum не было покрыто ни примером, ни тестом. |
199
201
  | 2026-08-03 | Правки по третьему ревью PR #496. Исправлено: при смене `toggleType` на лету hover-обработчики синхронизируются и на самой подсказке, а не только на целевом элементе (`setTooltipRef` повторно не вызывается, поэтому раньше на ноде подсказки оставались устаревшие слушатели). Тесты: `Tooltip.Link` в десктопном хелпере вынесен в прямые дети `Tooltip` — раньше слот `elements.link` не исполнялся ни в одном десктопном кейсе; добавлены кейсы на ветку `disableAdaptiveMode`. Публичный API не изменён. |
202
+ | 2026-09-29 | Аудит симметрии `related` (TRI-156): из `related` убраны имена без строки в `docs/ai/ROADMAP.md` (`DropdownMobile`) и потребитель `SMSField` — `SMSField.Tooltip` рендерит `Tooltip`, ссылка стоит с его стороны. Пояснения к убранным именам сохранены прозой в «Связанные компоненты»; публичный API и поведение не затронуты. |
200
203
 
201
204
  ---
202
205
 
@@ -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`) |