@sberbusiness/triplex-next-mcp-server 1.48.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 (77) hide show
  1. package/bundle/components/Amount.md +2 -1
  2. package/bundle/components/Badge.md +2 -1
  3. package/bundle/components/BodyPage.md +2 -1
  4. package/bundle/components/Button.md +5 -1
  5. package/bundle/components/ButtonDropdownExtended.md +3 -1
  6. package/bundle/components/ButtonIcon.md +3 -1
  7. package/bundle/components/Calendar.md +3 -1
  8. package/bundle/components/CardAction.md +1 -0
  9. package/bundle/components/CheckboxXGroup.md +2 -1
  10. package/bundle/components/Chip.md +2 -1
  11. package/bundle/components/ChipMonthYearPicker.md +2 -1
  12. package/bundle/components/ChipMultiselect.md +4 -1
  13. package/bundle/components/ChipOptions.md +2 -1
  14. package/bundle/components/ChipSort.md +4 -1
  15. package/bundle/components/CollapsibleTree.md +2 -1
  16. package/bundle/components/ComposedKeyDownListener.md +4 -3
  17. package/bundle/components/DateField.md +7 -1
  18. package/bundle/components/Divider.md +85 -138
  19. package/bundle/components/DocumentNumberEdit.md +173 -214
  20. package/bundle/components/Dropdown.md +11 -1
  21. package/bundle/components/Ellipsis.md +131 -147
  22. package/bundle/components/FooterPage.md +3 -1
  23. package/bundle/components/FormField.md +2 -1
  24. package/bundle/components/Gap.md +1 -1
  25. package/bundle/components/Header.md +1 -0
  26. package/bundle/components/HeaderPage.md +3 -1
  27. package/bundle/components/HelpBox.md +2 -1
  28. package/bundle/components/IconWrapper.md +10 -6
  29. package/bundle/components/ImageGallery.md +2 -1
  30. package/bundle/components/ImageGalleryExtended.md +2 -1
  31. package/bundle/components/IslandAccordion.md +6 -1
  32. package/bundle/components/KeyDownListener.md +3 -2
  33. package/bundle/components/LightBox.md +7 -1
  34. package/bundle/components/List.md +2 -1
  35. package/bundle/components/ListActionItem.md +2 -1
  36. package/bundle/components/ListItem.md +3 -1
  37. package/bundle/components/ListItemControlsButton.md +2 -1
  38. package/bundle/components/ListItemSelectable.md +2 -1
  39. package/bundle/components/ListMasterFooter.md +2 -1
  40. package/bundle/components/ListMasterHeader.md +2 -1
  41. package/bundle/components/ListSortable.md +2 -1
  42. package/bundle/components/ListSortableItem.md +2 -1
  43. package/bundle/components/ListTableItem.md +4 -1
  44. package/bundle/components/LoaderMiddle.md +5 -1
  45. package/bundle/components/LoaderScreen.md +7 -1
  46. package/bundle/components/LoaderSmall.md +10 -4
  47. package/bundle/components/MaskedField.md +3 -1
  48. package/bundle/components/MediaWidth.md +2 -1
  49. package/bundle/components/MobileView.md +2 -1
  50. package/bundle/components/ModalWindow.md +4 -1
  51. package/bundle/components/MonthYearField.md +4 -1
  52. package/bundle/components/Notification.md +1 -0
  53. package/bundle/components/Overlay.md +7 -2
  54. package/bundle/components/Pagination.md +1 -7
  55. package/bundle/components/Radio.md +238 -115
  56. package/bundle/components/RadioXGroup.md +259 -0
  57. package/bundle/components/RadioYGroup.md +266 -0
  58. package/bundle/components/SegmentedControl.md +505 -158
  59. package/bundle/components/SelectExtendedField.md +2 -1
  60. package/bundle/components/SelectField.md +4 -1
  61. package/bundle/components/Skeleton.md +140 -69
  62. package/bundle/components/Spoiler.md +245 -63
  63. package/bundle/components/Step.md +257 -68
  64. package/bundle/components/SuggestField.md +30 -18
  65. package/bundle/components/Tabs.md +2 -1
  66. package/bundle/components/TabsLine.md +2 -1
  67. package/bundle/components/TextField.md +8 -4
  68. package/bundle/components/TextareaField.md +2 -1
  69. package/bundle/components/ThemeProvider.md +2 -1
  70. package/bundle/components/Tooltip.md +6 -3
  71. package/bundle/guides/context.md +4 -1
  72. package/bundle/manifest.json +32 -10
  73. package/bundle/release-notes/1.49.0.mdx +78 -0
  74. package/bundle/release-notes/1.50.0.mdx +35 -0
  75. package/bundle/release-notes/1.51.0.mdx +7 -0
  76. package/bundle/tokens.json +161 -11
  77. package/package.json +1 -1
@@ -1,23 +1,21 @@
1
1
  ---
2
2
  component: Step
3
- category: Navigation
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Stepper, Tooltip]
3
+ category: Step
4
+ related: [Stepper, Tooltip, Text]
7
5
  tokens:
8
- - --triplex-next-Step-Background_Default
9
- - --triplex-next-Step-Background_Active
10
- - --triplex-next-Step-Background_Done
11
- - --triplex-next-Step-Background_Warning
12
- - --triplex-next-Step-Background_Error
13
- - --triplex-next-Step-Background_Disabled
14
- - --triplex-next-Step-Color_Default
15
- - --triplex-next-Step-Color_Active
16
- - --triplex-next-Step-Color_Done
17
- - --triplex-next-Step-Color_Warning
18
- - --triplex-next-Step-Color_Error
19
- - --triplex-next-Step-Color_Disabled
20
- stories: ""
6
+ - Step.Background_Active
7
+ - Step.Background_Default
8
+ - Step.Background_Disabled
9
+ - Step.Background_Done
10
+ - Step.Background_Error
11
+ - Step.Background_Warning
12
+ - Step.Color_Active
13
+ - Step.Color_Default
14
+ - Step.Color_Disabled
15
+ - Step.Color_Done
16
+ - Step.Color_Error
17
+ - Step.Color_Warning
18
+ stories: stories/Step/Step.stories.tsx
21
19
  version: "1.0"
22
20
  ---
23
21
 
@@ -25,92 +23,182 @@ version: "1.0"
25
23
 
26
24
  ## Назначение
27
25
 
28
- Визуальный «кружок с номером шага» для stepper-интерфейсов. Показывает статус
29
- (`default`/`active`/`done`/`warning`/`error`/`disabled`) через цветовой стейт.
30
- Опционально оборачивается в `Tooltip` (hover), если передан `children`.
26
+ Круглый индикатор одного шага: номер внутри кружка, цвет фона и цвет номера кодируют статус
27
+ (`DEFAULT` / `DONE` / `ACTIVE` / `ERROR` / `WARNING` / `DISABLED`). Компонент рисует ровно один
28
+ шаг — ряд шагов и связь между ними собирает потребитель. Если передан `children`, кружок
29
+ оборачивается в `Tooltip`, раскрывающийся по наведению.
31
30
 
32
- Используй когда: нужен отдельный индикатор шага (например, внутри кастомного
33
- stepper-а). Для готового пошагового виджета — `Stepper`.
34
- Не используй когда: нужен текстовый label шага — добавь его рядом, `Step`
35
- отрисовывает только номер в кружке.
31
+ Используй когда: нужно показать номер и состояние отдельного шага внутри своей вёрстки
32
+ пошагового сценария — так его использует `IslandAccordion` для нумерации секций.
36
33
 
37
- ---
38
-
39
- ## Файловая структура
40
-
41
- ```text
42
- src/components/Step/
43
- ├── Step.tsx # FC
44
- ├── enums.ts # EStepStatus, EStepPosition
45
- ├── index.ts
46
- └── styles/Step.module.less
47
- ```
34
+ Не используй когда:
48
35
 
49
- Также экспортируется утилита `calcPosition(stepCount, i)` — вычисляет
50
- `EStepPosition` (первый/последний/в середине) для списка шагов.
36
+ - Нужен готовый горизонтальный степпер с переключением шагов, подписями и клавиатурной
37
+ навигацией — возьми `Stepper` (или `StepperExtended` для произвольного состава шагов).
38
+ `Step` в реализацию `Stepper` не входит: у того собственный `StepperStep`.
39
+ - Нужен кликабельный элемент. `Step` рендерит `<div>` без роли, фокуса и обработчиков —
40
+ интерактивность придётся навешивать снаружи, вместе с ролью и клавиатурой.
41
+ - Нужно показать статус без номера — возьми `Marker` или `MarkerStatus`.
51
42
 
52
43
  ---
53
44
 
54
45
  ## Варианты и props
55
46
 
56
- ### `IStepProps`
47
+ ### Обязательные props
48
+
49
+ | Prop | Тип | Описание |
50
+ |---|---|---|
51
+ | `step` | `number` | Номер шага. Рендерится внутри кружка через `Text` (`B1`, semibold). Компонент не форматирует и не ограничивает значение: двузначные номера помещаются, кружок под содержимое не растягивается |
52
+ | `status` | `EStepStatus` | Состояние шага. Определяет **только** пару цветов — фон кружка и цвет номера |
57
53
 
58
- | Prop | Тип | Default | Описание |
54
+ ### Опциональные props
55
+
56
+ | Prop | Тип | По умолчанию | Описание |
59
57
  |---|---|---|---|
60
- | `step` | `number` | — | Число, отображаемое внутри кружка. |
61
- | `status` | `EStepStatus` | — | Цветовой стейт (см. ниже). |
62
- | `position?` | `EStepPosition` | `Default` | Влияет на `alignTip` опционального Tooltip'а. |
63
- | `children?` | `ReactNode` | — | Контент Tooltip'а. Если не передан — Tooltip не рендерится. |
64
- | `...HTMLAttributes<HTMLDivElement>` | — | — | Прокидываются на корневой `<div>`. |
58
+ | `size` | `EComponentSize` | `EComponentSize.MD` | Размер кружка. SM и MD дают 28px, LG — 32px |
59
+ | `position` | `EStepPosition` | `EStepPosition.Default` | Позиция шага в ряду. Влияет **только** на выравнивание указателя подсказки и потому имеет смысл лишь вместе с `children` |
60
+ | `children` | `React.ReactNode` | — | Содержимое подсказки. Без него подсказка не создаётся и `Tooltip` не монтируется вовсе |
65
61
 
66
- ### `EStepStatus`
62
+ Остальные props — стандартные атрибуты `div`
63
+ (`IStepProps extends React.HTMLAttributes<HTMLDivElement>`), уходят на корневой элемент
64
+ через `...rest`.
67
65
 
68
- `DEFAULT` / `DONE` / `ACTIVE` / `ERROR` / `WARNING` / `DISABLED`.
66
+ ### Размер и статус независимы
69
67
 
70
- Маппинг на CSS-классы 1-к-1 через `statusToClassNameMap`.
68
+ Две оси не пересекаются: `size` отвечает за геометрию, `status` — за пару цветов. Любая
69
+ комбинация допустима, поэтому матрицу 3×6 целиком покрывает story `VisualTests`,
70
+ а документационные stories показывают оси по отдельности.
71
71
 
72
- ### `EStepPosition`
72
+ ### `position` и подсказка
73
73
 
74
- | Value | Поведение (alignTip) |
75
- |---|---|
76
- | `XFirst` | `ETooltipAlign.START` |
77
- | `Default` | `ETooltipAlign.CENTER` |
78
- | `XLast` | `ETooltipAlign.END` |
74
+ `position` отображается на `alignTip` подсказки: `XFirst` → `START`, `Default` → `CENTER`,
75
+ `XLast` → `END`. Для крайних шагов ряда указатель прижимается к краю, чтобы подсказка
76
+ не выходила за границы ряда. Без `children` prop ни на что не влияет.
77
+
78
+ Значение для ряда шагов считает экспортируемый хелпер `calcPosition(stepCount, i)`:
79
+ `XFirst` для `i === 0`, `XLast` для последнего, иначе `Default`. Для ряда из одного шага
80
+ возвращает `XFirst` — проверка на первый шаг идёт первой.
81
+
82
+ ---
83
+
84
+ ## Дизайн-токены
79
85
 
80
- `calcPosition(total, i)` возвращает `XFirst`/`XLast`/`Default` для удобства
81
- сборки списка шагов.
86
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
87
+ «Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/Step.ts`.
88
+
89
+ ```text
90
+ Step.Background_Default
91
+ Step.Background_Done
92
+ Step.Background_Active
93
+ Step.Background_Error
94
+ Step.Background_Warning
95
+ Step.Background_Disabled
96
+ Step.Color_Default
97
+ Step.Color_Done
98
+ Step.Color_Active
99
+ Step.Color_Error
100
+ Step.Color_Warning
101
+ Step.Color_Disabled
102
+ ```
103
+
104
+ По паре токенов (`Background_*` + `Color_*`) на каждое значение `EStepStatus`; добавление
105
+ статуса означает и два новых токена. Цвет номера задаётся селектором `& > span` — он попадает
106
+ на элемент, который рендерит `Text`.
107
+
108
+ Размеры (28 / 32px, `border-radius: 50%`) заданы литералами в `styles/Step.module.less` — они
109
+ специфичны для компонента. Брейкпоинт адаптива берётся из LESS-переменной `@screen-sm-max`.
82
110
 
83
111
  ---
84
112
 
85
113
  ## Инварианты
86
114
 
87
- - **Рендер числа через `<Text size=B1 weight=SEMIBOLD>`** — фикс типографики для совпадения с дизайном.
88
- - **Tooltip рендерится только при наличии `children`** — без children компонент возвращает голый `<div>`.
89
- - **Tooltip: `toggleType="hover"`, `size=SM`** — не менять без согласования.
90
- - **Step — `FC`**, ref не пробрасывается (корневой div доступен только через обёртку Tooltip — по-разному в двух ветках рендера).
91
- - **Публичные enums** (`EStepStatus`, `EStepPosition`) + функция `calcPosition` — часть публичного API.
115
+ - `forwardRef<HTMLDivElement>` — не убирать. Ref всегда указывает на корневой `<div class="step">`,
116
+ и в варианте с подсказкой тоже: внешний ref мерджится с внутренним через
117
+ `setForwardedRef` (`src/helpers/setForwardedRef.ts`).
118
+ - Внутренний ref на корневой `<div>` обязателен: он уходит в `Tooltip` как `targetRef` —
119
+ относительно этого узла считается позиция подсказки. Убрать внутренний ref или перевесить
120
+ его на другой элемент нельзя.
121
+ - Корневой элемент — `<div>`. Кружок центрует содержимое через `display: flex`; смена тега
122
+ сломает вёрстку и типизацию props.
123
+ - `displayName` — `"Step"`. Не менять.
124
+ - Значения `EStepStatus` (`"default"` / `"done"` / `"active"` / `"error"` / `"warning"` /
125
+ `"disabled"`) совпадают с именами CSS-классов в `Step.module.less` и с суффиксами токенов
126
+ `Step.Background_*` / `Step.Color_*`. Переименование значения enum — ломающее изменение
127
+ сразу в трёх местах.
128
+ - Значения `EStepPosition` — `PascalCase` (`Default` / `XFirst` / `XLast`), в отличие от
129
+ остальных enum'ов библиотеки. Это часть публичного API, приводить к общему виду нельзя.
130
+ - Подсказка монтируется только при непустом `children`. Оборачивать кружок в `Tooltip`
131
+ безусловно нельзя: пустая подсказка перехватывала бы наведение.
132
+ - `EStepStatus.DISABLED` — только цвет. Ни `pointer-events`, ни `aria-disabled` компонент
133
+ не выставляет, подсказка у выключенного шага продолжает работать.
134
+ - Экспорты `Step`, `IStepProps`, `calcPosition`, `EStepStatus` и `EStepPosition` идут в barrel
135
+ `src/components/Step/index.ts` — сохранять. `calcPosition` внутри компонента не используется,
136
+ но входит в публичный API: он нужен потребителю, который собирает ряд шагов.
137
+ - `className` деструктурируется из props и мерджится через `clsx`, поэтому в `...rest`
138
+ его нет и перезатереть базовые классы он не может.
92
139
 
93
140
  ---
94
141
 
95
142
  ## Accessibility
96
143
 
97
- - Компонент — чисто визуальный (`<div>` с числом); сам по себе ARIA-ролью не наделён.
98
- - В составе stepper-а потребитель обязан обеспечить семантику (`role="list"` / `role="listitem"` / `aria-current="step"` на активном шаге). См. `Stepper-AI`.
99
- - Tooltip показывается только на hover — для пользователей клавиатуры текст подсказки недоступен. Если содержимое tooltip важно, дублируй его видимым лейблом рядом со Step.
144
+ - Собственной ARIA-роли нет: корневой `<div>` — неинтерактивный контейнер, в порядок фокуса
145
+ не попадает, обработчиков клавиатуры у компонента нет.
146
+ - Номер шага — единственное, что читает скринридер. Смысл шага в разметку не попадает:
147
+ текст подсказки живёт в `Tooltip`, который раскрывается по наведению или клику мышью
148
+ и с клавиатуры недоступен. Если шаг должен быть понятен без мыши, опиши его видимым
149
+ текстом рядом в своей вёрстке.
150
+ - Статус кодируется только цветом. Дублируй его видимым текстом на стороне потребителя.
151
+ - Одного `aria-label` на `Step` недостаточно: корневой `<div>` без роли получает роль
152
+ `generic`, которой [спецификация HTML-AAM](https://www.w3.org/TR/html-aria/) запрещает
153
+ доступное имя, — атрибут попадёт в разметку через `...rest`, но скринридер его не
154
+ озвучит. Если имя нужно именно на шаге, передай вместе с ним роль, допускающую
155
+ именование: `<Step step={2} status={EStepStatus.ERROR} role="img" aria-label="Шаг 2, ошибка" />`.
156
+ - Ряд шагов семантики списка не создаёт: если нужна структура, оберни шаги в `<ol>`/`<li>`
157
+ на стороне потребителя.
158
+ - Компонент не хардкодит текстовые строки: содержимое подсказки и любые `aria-label` задаёт
159
+ потребитель. Библиотека мультиязычная.
100
160
 
101
161
  ---
102
162
 
103
163
  ## Связанные компоненты
104
164
 
105
- - `Stepper` — готовый виджет пошаговой навигации, использует Step внутри через `StepperStep` / `StepperStepIcon`.
106
- - `Tooltip` — оборачивает Step при наличии children.
107
- - `Typography.Text` — используется для рендера номера.
165
+ - `Stepper` — готовый горизонтальный степпер с подписями, выбором шага и клавиатурной
166
+ навигацией. Альтернатива, когда нужен весь сценарий целиком, а не отдельный индикатор.
167
+ Общего кода со `Step` нет: у `Stepper` собственный `StepperStep`.
168
+ - `Tooltip` — рендерится вокруг кружка, когда передан `children`. `Step` фиксирует
169
+ `size={ETooltipSize.SM}` и `toggleType="hover"`, а из props потребителя прокидывает
170
+ только выравнивание указателя (`alignTip`) — остальное настроить нельзя. `disableAdaptiveMode`
171
+ не передаётся, поэтому на экранах до `SM_MAX` включительно подсказка открывается
172
+ в адаптивном (полноэкранном) режиме.
173
+ - `Text` — рендерит номер шага (`ETextSize.B1`, `EFontWeightText.SEMIBOLD`). Цвет номера
174
+ задаётся снаружи, селектором `& > span` в стилях `Step`.
175
+ - `IslandAccordion` — потребитель: нумерует секции аккордеона, передавая `Step` номер,
176
+ статус и `size`. Опирается на то, что `Step` рисует только кружок, без внешних отступов.
108
177
 
109
178
  ---
110
179
 
111
180
  ## Stories
112
181
 
113
- Отдельных stories нет (используется внутри stories `Stepper`).
182
+ Основные истории: `stories/Step/Step.stories.tsx`
183
+ Файлы примеров: `stories/Step/examples/`
184
+
185
+ | Story | Example file | Что демонстрирует |
186
+ |---|---|---|
187
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `step`, `status`, `size`, `position` и текста подсказки |
188
+ | `Default` | `Default.tsx` | Минимальный шаг: обязательны только `step` и `status` |
189
+ | `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG при одном статусе |
190
+ | `Statuses` | `Statuses.tsx` | Все шесть значений `EStepStatus` при одном размере |
191
+ | `WithTooltip` | `WithTooltip.tsx` | Шаг с подсказкой: `children` становится её содержимым |
192
+ | `Example` | `Example.tsx` | Ряд шагов платёжного сценария, `position` считается через `calcPosition` |
193
+ | `VisualTests` | `VisualTests.tsx` | Матрица размеров × статусов, двузначные номера и раскрытая подсказка (открывается `play`) — для скриншот-тестов |
194
+
195
+ Из скриншот-тестов исключены (`testRunner: { skip: true }`) `Playground`, `Default` и
196
+ `WithTooltip`: первые два визуально дублируют `Statuses`, а закрытая подсказка
197
+ неотличима от обычного шага — раскрытая снимается в `VisualTests`, где её открывает
198
+ `play`. Не добавляй им скриншоты: это дубли, которые придётся поддерживать.
199
+
200
+ Story ID участвуют в именах baseline-скриншотов (`step--sizes--xs.png` и т.п.), поэтому
201
+ переименование story требует перегенерации baseline и удаления осиротевших файлов.
114
202
 
115
203
  ---
116
204
 
@@ -118,4 +206,105 @@ src/components/Step/
118
206
 
119
207
  | Дата | Изменение |
120
208
  |---|---|
121
- | 2026-04-18 | Создан документ |
209
+ | 2026-09-24 | Создан документ. AI-рефакторинг: `Step` переведён с `React.FC` на `forwardRef` (внешний ref мерджится с внутренним, который уходит в `Tooltip` как `targetRef`), JSDoc на компоненте и дефолтах props, `renderContent()` заменён на вычисляемый один раз элемент, приведён к порядку LESS. Добавлены unit-тесты (`__tests__/Step.test.tsx`), stories созданы по modern pattern (`stories/Step/`). |
210
+
211
+ ---
212
+
213
+ ## Примеры использования
214
+
215
+ ### Default
216
+
217
+ `stories/Step/examples/Default.tsx`
218
+
219
+ ```tsx
220
+ import React from "react";
221
+ import { Step, EStepStatus } from "@sberbusiness/triplex-next";
222
+
223
+ export const Default = () => <Step step={1} status={EStepStatus.DEFAULT} />;
224
+ ```
225
+
226
+ ### Sizes
227
+
228
+ `stories/Step/examples/Sizes.tsx`
229
+
230
+ ```tsx
231
+ import React from "react";
232
+ import { Step, EComponentSize, EStepStatus } from "@sberbusiness/triplex-next";
233
+
234
+ const SIZES = Object.values(EComponentSize);
235
+
236
+ export const Sizes = () => (
237
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
238
+ {SIZES.map((size, index) => (
239
+ <div key={size}>
240
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{size.toUpperCase()}</div>
241
+ <Step step={index + 1} status={EStepStatus.ACTIVE} size={size} />
242
+ </div>
243
+ ))}
244
+ </div>
245
+ );
246
+ ```
247
+
248
+ ### Statuses
249
+
250
+ `stories/Step/examples/Statuses.tsx`
251
+
252
+ ```tsx
253
+ import React from "react";
254
+ import { Step, EStepStatus } from "@sberbusiness/triplex-next";
255
+
256
+ const STATUSES = Object.values(EStepStatus);
257
+
258
+ export const Statuses = () => (
259
+ <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
260
+ {STATUSES.map((status, index) => (
261
+ <div key={status}>
262
+ <div style={{ marginBottom: 8, fontSize: 16, fontWeight: 700 }}>{status.toUpperCase()}</div>
263
+ <Step step={index + 1} status={status} />
264
+ </div>
265
+ ))}
266
+ </div>
267
+ );
268
+ ```
269
+
270
+ ### WithTooltip
271
+
272
+ `stories/Step/examples/WithTooltip.tsx`
273
+
274
+ ```tsx
275
+ import React from "react";
276
+ import { Step, EStepStatus } from "@sberbusiness/triplex-next";
277
+
278
+ export const WithTooltip = () => (
279
+ <Step step={2} status={EStepStatus.ACTIVE}>
280
+ Заполните реквизиты получателя
281
+ </Step>
282
+ );
283
+ ```
284
+
285
+ ### Example
286
+
287
+ `stories/Step/examples/Example.tsx`
288
+
289
+ ```tsx
290
+ import React from "react";
291
+ import { Step, calcPosition, EStepStatus } from "@sberbusiness/triplex-next";
292
+
293
+ const STEPS = [
294
+ { hint: "Реквизиты получателя заполнены", status: EStepStatus.DONE },
295
+ { hint: "Проверьте сумму и назначение платежа", status: EStepStatus.ACTIVE },
296
+ { hint: "Подписание доступно после проверки", status: EStepStatus.DEFAULT },
297
+ { hint: "Отправка доступна после подписания", status: EStepStatus.DISABLED },
298
+ ];
299
+
300
+ export const Example = () => (
301
+ <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
302
+ {STEPS.map(({ hint, status }, index) => (
303
+ <Step key={hint} step={index + 1} status={status} position={calcPosition(STEPS.length, index)}>
304
+ {hint}
305
+ </Step>
306
+ ))}
307
+ </div>
308
+ );
309
+ ```
310
+
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: SuggestField
3
3
  category: TextFields
4
- related: [Suggest, SelectField, TextField, FormField, Dropdown]
4
+ related: [Suggest, SelectField, MultiselectField, SelectExtendedField, TextField, FormField, Dropdown, LoaderSmall]
5
5
  tokens: []
6
6
  stories: stories/SuggestField/SuggestField.stories.tsx
7
7
  version: "1.0"
@@ -45,7 +45,7 @@ version: "1.0"
45
45
  | `options` | `T[]` | Список опций. Фильтрует потребитель в ответ на `onFilter` — компонент список не фильтрует и не сортирует |
46
46
  | `tooltipHint` | `string` | Текст подсказки («Ничего не найдено», «Введите более 3 символов»). Обязателен, даже если `tooltipOpen` всегда `false` |
47
47
  | `tooltipOpen` | `boolean` | Показывать подсказку. Управляется снаружи: компонент сам не решает, когда список «пустой по делу» |
48
- | `onSelect` | `(value: T \| undefined) => void` | Опция выбрана. Вызывается с `undefined`, когда значение сбрасывается. На десктопе это blur с пустым полем и кнопка очистки; на мобильном — только закрытие дропдауна с пустым вводом, кнопка очистки там зовёт лишь `onClear` (см. «Инварианты»), так что сброс значения ложится на потребителя |
48
+ | `onSelect` | `(value: T \| undefined) => void` | Опция выбрана. Вызывается с `undefined`, когда значение сбрасывается, — на обеих платформах только кнопкой очистки (см. «Инварианты»). Blur на десктопе и закрытие дропдауна на мобильном значение не сбрасывают даже при пустом вводе. Не сбрасывай значение в `onFilter("")`: при `clearInputOnFocus` десктоп зовёт его уже на фокусе |
49
49
  | `onFilter` | `(value: string) => void` | Текст поля ввода изменился. Ответ — обновлённый `options` |
50
50
  | `inputProps` | `IFormFieldInputProps & { ref?: React.Ref<HTMLInputElement> }` | Свойства поля ввода. **Не опционально:** передавай хотя бы `{}`. Через `inputProps.ref` потребитель получает ref на `<input>` — собственного `forwardRef` у компонента нет |
51
51
 
@@ -91,8 +91,7 @@ version: "1.0"
91
91
  | Ввод текста | `onFilter(value)`, сброс `aria-activedescendant`, список снова разрешён к автооткрытию |
92
92
  | `Escape` при открытом списке | Список закрывается, всплытие останавливается, автооткрытие подавлено до следующего ввода или mousedown |
93
93
  | Выбор опции | `onSelect(option)`, в поле подставляется `label`, автооткрытие подавлено |
94
- | Blur с непустым полем | В поле возвращается `label` текущего `value` |
95
- | Blur с пустым полем и заданным `value` | `onSelect(undefined)` |
94
+ | Blur | В поле возвращается `label` текущего `value` (пустая строка, если `value` не задан). `onSelect` не вызывается: введённый, но не выбранный текст отбрасывается, в том числе пустой |
96
95
  | Клик по кнопке очистки | `onSelect(undefined)` (если было значение), `onFilter("")` (если поле не пустое), затем `onClear(event)` |
97
96
 
98
97
  `inputProps.onFocus` / `onBlur` / `onKeyDown` / `onMouseDown` / `onChange` вызываются
@@ -104,9 +103,14 @@ version: "1.0"
104
103
 
105
104
  Поле-триггер — `readOnly`, по фокусу открывается полноэкранный `Dropdown` с собственным полем
106
105
  ввода (автофокус). При `tooltipOpen` вместо списка показывается `SuggestFieldMobileDropdownHint`
107
- с текстом `tooltipHint`. Закрытие дропдауна с пустым полем ввода и заданным `value` вызывает
108
- `onSelect(undefined)`; закрытие сразу после выбора опции значение не сбрасывает. После закрытия
109
- поле-триггер скроллится в центр экрана (в iOS открытие дропдауна уводит страницу вверх).
106
+ с текстом `tooltipHint`. Закрытие дропдауна без выбора опции значение не сбрасывает, даже при
107
+ пустом поле ввода: на закрытии поле ввода дропдауна возвращается к `label` текущего `value`
108
+ (через `onClose` у `Dropdown`, а не только на автофокусе — при повторном открытии во время
109
+ анимации закрытия содержимое ещё смонтировано и автофокус не срабатывает). При следующем
110
+ открытии с `clearInputOnFocus` поле очищается автофокусом. Кнопка очистки работает как на
111
+ десктопе: `onSelect(undefined)` (если было значение), `onFilter("")` (если у значения непустой
112
+ `label` — его и показывает поле-триггер), затем `onClear(event)`. После закрытия поле-триггер
113
+ скроллится в центр экрана (в iOS открытие дропдауна уводит страницу вверх).
110
114
 
111
115
  ---
112
116
 
@@ -130,13 +134,15 @@ version: "1.0"
130
134
  и переименование наблюдаемо для потребителя.
131
135
  - `SuggestField` — `Object.assign(SuggestFieldBase, { Input: FormFieldInput })`. Дженерик
132
136
  сохраняется только у самой функции; `SuggestField.Input` — часть публичного API.
133
- - **Мобильная ветка получает не все props.** `SuggestField.tsx` передаёт в `SuggestFieldMobile`
134
- явный whitelist, тогда как в `SuggestFieldDesktop` уходит `{...props}`. На мобильной ширине
135
- до поля не доходят `className`, `id`, `data-test-id`, `active` и `renderInput`. Расширение
136
- списка меняет наблюдаемое поведение — только по решению мейнтейнера.
137
- - **`onClear` на мобильном варианте работает иначе:** обработчик вешается на `FormFieldClear`
138
- напрямую, без сброса `value` и фильтра, которые делает десктоп. Выравнивание — изменение
139
- поведения, а не рефакторинг.
137
+ - **Оба варианта получают все props.** `SuggestField.tsx` отдаёт `{...props}` и в
138
+ `SuggestFieldDesktop`, и в `SuggestFieldMobile`. Не возвращай явный список props для мобильной
139
+ ветки: пропущенный prop молча терялся бы на мобильной ширине (так до 1.49.0 терялись
140
+ `className`, `id`, `data-test-id`, `active` и `renderInput`).
141
+ - **Значение сбрасывает только кнопка очистки — на обеих платформах.** Blur на десктопе и
142
+ закрытие дропдауна на мобильном `onSelect(undefined)` не зовут, даже при пустом вводе: иначе
143
+ при `clearInputOnFocus` значение терялось от простого фокуса без выбора. Кнопка очистки
144
+ рендерится только при `onClear`, так что без него встроенного сброса нет. Возврат сброса в
145
+ blur / закрытие дропдауна — изменение поведения, решение мейнтейнера.
140
146
  - Видимость десктопного списка синхронизируется **во время рендера** (`if (inputFocused) {...}`),
141
147
  а не в `useEffect`. Так требует правило `react-hooks/set-state-in-effect`; возврат к эффекту
142
148
  вернёт и ошибку линтера, и лишний кадр рендера.
@@ -201,8 +207,10 @@ version: "1.0"
201
207
  - `Dropdown` — выпадающий список: `DropdownDesktop` + `DropdownList` + `DropdownListItem` на
202
208
  десктопе, `DropdownMobile*` в полноэкранном мобильном варианте. `ISuggestFieldDesktopDropdownProps`
203
209
  наследует `IDropdownDesktopProps`.
204
- - `Tooltip` — подсказка `tooltipHint` у десктопного поля. В `related` не вынесен: односторонний
205
- контракт по рендеру, а ориентир 2–5 имён уже выбран более полезными адресатами.
210
+ - `LoaderSmall` — индикатор подгрузки подсказок: `SuggestFieldDesktop` и `SuggestFieldMobile`
211
+ показывают его с темой `BRAND`, пока `loading`.
212
+ - `Tooltip` — подсказка `tooltipHint` у десктопного поля. В `related` не вынесен: связь узкая,
213
+ а список адресатов и так шире ориентира 2–5.
206
214
  - `MobileView` — переключает десктопный и мобильный варианты по ширине экрана. В `related` не
207
215
  вынесен по той же причине.
208
216
  - `SuggestFieldDesktop` / `SuggestFieldMobile` — платформенные варианты, экспортируются из barrel
@@ -238,6 +246,8 @@ version: "1.0"
238
246
  | Дата | Изменение |
239
247
  |---|---|
240
248
  | 2026-09-23 | Создан документ |
249
+ | 2026-09-29 | Аудит симметрии `related` (TRI-156): добавлены обратные ссылки (`MultiselectField`, `SelectExtendedField`) и контракт по рендеру `LoaderSmall`. Публичный API и поведение не затронуты. |
250
+ | 2026-10-02 | Сброс значения только кнопкой очистки на обеих платформах: при `clearInputOnFocus` значение терялось от фокуса без выбора. Blur на десктопе и закрытие дропдауна на мобильном больше не вызывают `onSelect(undefined)`; мобильная кнопка очистки сама зовёт `onSelect(undefined)` и `onFilter("")` (последний — при непустом `label`), как десктопная; на закрытии мобильного дропдауна его поле ввода возвращается к `label`. `SuggestField` отдаёт мобильному варианту все props, как десктопному: на мобильной ширине больше не теряются `className`, `id`, `data-test-id`, `active` и `renderInput`. Обновлены `onSelect`, поведение обеих платформ и «Инварианты» |
241
251
 
242
252
  ---
243
253
 
@@ -715,7 +725,6 @@ export const ProductionExample = () => {
715
725
  setInputPristine(false);
716
726
 
717
727
  if (inputValue.length === 0) {
718
- setValue(undefined);
719
728
  setOptions(FOOD_OPTIONS);
720
729
  setTooltipOpen(false);
721
730
  } else {
@@ -750,7 +759,10 @@ export const ProductionExample = () => {
750
759
  };
751
760
 
752
761
  const handleClear: React.MouseEventHandler<HTMLButtonElement> = () => {
753
- inputRef.current?.focus();
762
+ // На мобильном фокус открыл бы полноэкранный дропдаун сразу после очистки.
763
+ if (!adaptive) {
764
+ inputRef.current?.focus();
765
+ }
754
766
  };
755
767
 
756
768
  const handleLinkClick: React.MouseEventHandler<HTMLAnchorElement> = (event) => {
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: Tabs
3
3
  category: Tabs
4
- related: [TabsExtended, TabsLine, ButtonDropdown]
4
+ related: [TabsExtended, TabsLine, ButtonDropdown, SegmentedControl]
5
5
  tokens:
6
6
  - Tabs.Type1_Tab_Background_Selected
7
7
  - Tabs.Type2_Tab_Background_Selected
@@ -110,6 +110,7 @@ Tabs.Type2_Tab_Background_Selected
110
110
  - `TabsLine` — альтернативный визуальный паттерн табов (подчёркивание вместо залитой кнопки).
111
111
  - `ButtonDropdown` — кнопка выпадающего списка, которую `Tabs` рендерит сам для не поместившихся табов; тема подбирается по `type` (`DOTS_SECONDARY` / `DOTS_SECONDARY_LIGHT`).
112
112
  - `Badge` — `Badge.Dot` как значок новых уведомлений на кнопке таба (рендерится внутри `TabsExtended.Content.TabButton`).
113
+ - `SegmentedControl` — альтернатива, когда переключатель не управляет панелями контента: компактный набор сегментов без семантики табов, зато с режимом множественного выбора.
113
114
 
114
115
  ---
115
116
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: TabsLine
3
3
  category: Tabs
4
- related: [Tabs, TabsExtended, Dropdown, Badge, Text]
4
+ related: [Tabs, TabsExtended, Dropdown, Badge, Text, SegmentedControl]
5
5
  tokens:
6
6
  - TabsLine.BorderColor_Active
7
7
  - TabsLine.BorderColor_Hover
@@ -144,6 +144,7 @@ TabsLine.Shadow_Focus
144
144
  - `Badge` — значок новых уведомлений на табе (`Badge.Dot`).
145
145
  - `Text` — текст таба; размер выбирается по `size` через `tabsLineSizeToTextSizeMap`.
146
146
  - `MobileView` — переключает десктопный и мобильный варианты по ширине окна (<768px). Собственной разметки не добавляет.
147
+ - `SegmentedControl` — альтернатива, когда переключатель не управляет панелями контента: компактный набор сегментов без семантики табов, зато с режимом множественного выбора.
147
148
 
148
149
  ---
149
150
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: TextField
3
3
  category: TextFields
4
- related: [MaskedField, FormField, FormGroup, TextareaField, MonthYearField]
4
+ related: [MaskedField, SmallInput, TextareaField, MonthYearField, FormField, FormGroup]
5
5
  tokens: []
6
6
  stories: stories/TextField/TextField.stories.tsx
7
7
  version: "1.0"
@@ -62,8 +62,8 @@ version: "1.0"
62
62
 
63
63
  - `forwardRef` на `TextField` и `TextFieldBase` — не убирать. Внешний `ref` указывает на корневой `<div>` `FormField`; ref на сам input — только через `inputProps.ref`.
64
64
  - Публичный API (`ITextFieldProps`: `inputProps` + `label`/`prefix`/`postfix`/`description`/`counter` + унаследованные `size`/`status`/`active`) — изменение имён/типов/значений enum — breaking change.
65
- - `TextFieldBase` намеренно НЕ экспортируется — приватная база.
66
- - Известное ограничение контракта: `inputProps.ref` типизирован как `React.RefObject<HTMLInputElement>` (не `React.Ref`), callback-ref формально не проходит по типам. Исправление — breaking change, требует отдельной задачи.
65
+ - Компонент `TextFieldBase` намеренно НЕ экспортируется — приватная база. Его тип `ITextFieldBaseProps` экспортируется из barrel только как тип (`export type`): `children` в нём обязателен (слот поля ввода), поэтому потребители типизируют props собственных обёрток через `Omit<ITextFieldBaseProps, "children">` — так же, как `TextField`, `MaskedField`, `AmountField` и `TextareaField`. Переименование или изменение полей — breaking change.
66
+ - `inputProps.ref` типизирован как `React.Ref<HTMLInputElement>` (расширен в TRIPLEX-935): принимает и объектный, и callback-ref, `FormFieldInput` отдаёт его прямо на `<input>`.
67
67
  - Уникальный `id` для связки label↔input генерируется через `lodash uniqueId` в `FormFieldInput` — не заменять на `useId` (React 17 совместимость через release-0).
68
68
  - `description || counter` рендерятся в общем блоке `FormFieldDescription` — не разносить по отдельным блокам (изменит DOM и скриншоты).
69
69
 
@@ -80,10 +80,12 @@ version: "1.0"
80
80
 
81
81
  ## Связанные компоненты
82
82
 
83
- - `TextFieldBase` (внутренний, не в barrel) — базовая раскладка `FormGroup`/`FormField` + слоты `prefix`/`children`/`label`/`postfix` и блок `description`/`counter`. Описан здесь, отдельного AI.md не имеет.
83
+ - `TextFieldBase` (внутренний, не в barrel; публичен только тип `ITextFieldBaseProps`) — базовая раскладка `FormGroup`/`FormField` + слоты `prefix`/`children`/`label`/`postfix` и блок `description`/`counter`. Описан здесь, отдельного AI.md не имеет.
84
84
  - `FormField` / `FormFieldInput` и слоты семейства — низкоуровневые строительные блоки; используются напрямую, когда нужна нестандартная композиция.
85
85
  - `FormGroup` — контейнер вертикальной раскладки поля и описания.
86
86
  - `FormFieldClear` — кнопка очистки, передаётся в `postfix`.
87
+ - `MaskedField` — то же поле, но с маской ввода: отличается только элементом ввода (`FormFieldMaskedInput` вместо `FormFieldInput`).
88
+ - `SmallInput` — альтернатива для инлайн-редактирования короткого значения: без лейбла, статусов, описания и счётчика.
87
89
  - `TextareaField`, `AmountField`, `SuggestField`, `DateField`, `MonthYearField` — специализированные поля той же группы `TextFields`.
88
90
 
89
91
  ---
@@ -113,6 +115,8 @@ version: "1.0"
113
115
  | Дата | Изменение |
114
116
  |---|---|
115
117
  | 2026-07-27 | Создан документ (TRI-5). AI-рефакторинг TextField/TextFieldBase, unit-тесты, миграция stories на modern pattern |
118
+ | 2026-09-29 | Тип `ITextFieldBaseProps` экспортирован из barrel (`export type`); компонент `TextFieldBase` остаётся приватным (TRI-158). Удалён устаревший инвариант про `RefObject` у `inputProps.ref` — тип уже `React.Ref` после TRIPLEX-935 |
119
+ | 2026-09-29 | Аудит симметрии `related` (TRI-156): добавлены обратные ссылки (`SmallInput`). Публичный API и поведение не затронуты. |
116
120
 
117
121
  ---
118
122
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  component: TextareaField
3
3
  category: TextFields
4
- related: [TextField, MaskedField, FormField, FormGroup, DateField, MonthYearField]
4
+ related: [TextField, FormField, FormGroup]
5
5
  tokens: []
6
6
  stories: stories/TextareaField/TextareaField.stories.tsx
7
7
  version: "1.0"
@@ -115,6 +115,7 @@ version: "1.0"
115
115
  |---|---|
116
116
  | 2026-09-17 | Создан документ (TRI-96). AI-рефакторинг `TextareaField`: проброс `ref` на корневой `<div>` через `forwardRef`, `displayName`, unit-тесты вместо mock-заглушек |
117
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 и поведение не затронуты. |
118
119
 
119
120
  ---
120
121
 
@@ -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