@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,10 +1,12 @@
1
1
  ---
2
2
  component: TabsLine
3
- category: Navigation
4
- figma-node: ""
5
- figma-file: ""
6
- related: [Tabs, TabsExtended, MobileView, Dropdown]
7
- tokens: []
3
+ category: Tabs
4
+ related: [Tabs, TabsExtended, Dropdown, Badge, Text]
5
+ tokens:
6
+ - TabsLine.BorderColor_Active
7
+ - TabsLine.BorderColor_Hover
8
+ - TabsLine.Separator_Color
9
+ - TabsLine.Shadow_Focus
8
10
  stories: stories/TabsLine/TabsLine.stories.tsx
9
11
  version: "1.0"
10
12
  ---
@@ -13,172 +15,153 @@ version: "1.0"
13
15
 
14
16
  ## Назначение
15
17
 
16
- Линейные табы — кнопки, разделённые тонкой линией снизу, без текстового
17
- содержимого внутри. Отдельная реализация (НЕ поверх `TabsExtended`).
18
- Адаптивная: desktop рендерит `TabsLineDesktop` с overflow в `Dropdown`,
19
- mobile — горизонтальный скролл (`TabsLineMobile`). Переключение через
20
- `MobileView` по media query.
18
+ Линейка табов-подчёркиваний для переключения разделов внутри страницы: активный таб отмечен цветной полосой снизу, а не залитой кнопкой. Состав задаётся массивом `tabs`, а не дочерними элементами. На десктопе табы, не поместившиеся в лимит `maxVisible`, уезжают в выпадающий список, который компонент рендерит сам; на мобильной ширине (<768px) списка нет — все табы остаются в строке с горизонтальной прокруткой.
21
19
 
22
- Используй когда: нужны "линейные" табы Sber-стиля с поддержкой свыше
23
- нескольких табов и автосворачиванием в dropdown. Для табов с кастомным
24
- overflow — `Tabs` (общий стиль) или `TabsExtended` (низкоуровневый).
20
+ Используй когда: нужны табы-подчёркивания в шапке раздела или над таблицей, а состав табов — это плоский список «текст + опциональный значок уведомлений».
25
21
 
26
- ---
27
-
28
- ## Файловая структура
22
+ Не используй когда:
29
23
 
30
- ```text
31
- src/components/TabsLine/
32
- ├── TabsLine.tsx # FC, MobileView switcher + paddingX/withSeparator
33
- ├── types.ts # ITabsLineBaseProps
34
- ├── utils.ts # SIZE→ETextSize map
35
- ├── components/
36
- │ ├── TabsLineDesktop.tsx # FC, расчёт inline/dropdown по maxVisible
37
- │ ├── TabsLineMobile.tsx # FC, горизонтальный скролл
38
- │ ├── TabsLineItem.tsx # forwardRef<HTMLButtonElement>
39
- │ └── TabsLineDropdown.tsx # Dropdown + DropdownList для overflow
40
- ├── styles/
41
- ├── __tests__/
42
- └── index.ts
43
- ```
24
+ - нужны табы-переключатели в залитом контейнере — возьми `Tabs` (или `TabsExtended`, если состав таба нестандартный);
25
+ - в таб нужно положить произвольную разметку (иконку, счётчик, свой компонент кнопки) — `tabs` принимает только `label: string`, здесь нужен `TabsExtended`;
26
+ - переполнение должно считаться по реальной ширине строки — `TabsLine` считает только по числу табов (`maxVisible`), замеров он не делает; автоматическое переполнение по ширине есть у `TabsExtended` / `Tabs`.
44
27
 
45
28
  ---
46
29
 
47
- ## Публичный API
48
-
49
- ### `ITabsLineProps`
50
-
51
- ```ts
52
- extends ITabsLineDesktopProps
53
- ```
54
-
55
- | Prop | Тип | Default | Описание |
56
- |---|---|---|---|
57
- | `tabs` | `ITabsLineItemProps[]` | — | Список табов. |
58
- | `selectedId` | `string` | — | Id выбранного. |
59
- | `onChangeTab` | `(tabId: string) => void` | — | Колбэк выбора. |
60
- | `size?` | `EComponentSize` | — | Размер табов (LG/MD/SM). |
61
- | `maxVisible?` | `number` | — | Максимум inline-табов; остальные → Dropdown (только desktop). |
62
- | `dropdownTargetHtmlAttributes?` | `HTMLAttributes<HTMLButtonElement> & TestProps` | — | Атрибуты кнопки overflow-dropdown. |
63
- | `paddingX?` | `0 \| 8 \| 16 \| 24` | — | Горизонтальный отступ от первого/последнего таба. Записывается в `data-paddingx-size`. |
64
- | `withSeparator?` | `boolean` | — | Нижний бордер контейнера (`styles.withSeparator`). |
65
- | `...HTMLAttributes<HTMLDivElement>` | — | — | На корневой div (role=tablist). |
66
-
67
- ### `ITabsLineItemProps`
68
-
69
- ```ts
70
- extends HTMLAttributes<HTMLButtonElement>, TestProps
71
- ```
72
-
73
- | Поле | Тип | Описание |
74
- |---|---|---|
75
- | `id` | `string` | Уникальный id. |
76
- | `label` | `string` | Текст таба. |
77
- | `selected?` | `boolean` | Подсветка (выставляется родителем по `selectedId === id`). |
78
- | `showNotificationIcon?` | `boolean` | Точка-индикатор (`Badge.Dot`, всегда `EComponentSize.MD`). |
79
- | `size?` | `EComponentSize` | Размер; индивидуально override'ит `size` контейнера. |
30
+ ## Состав
80
31
 
81
- ### `ITabsLineBaseProps`
32
+ `TabsLine` — единая точка входа. Остальные компоненты папки экспортируются через barrel, но рассчитаны на внутреннее использование: собственного AI.md у них нет, их контракт описан здесь.
82
33
 
83
- ```ts
84
- extends Omit<HTMLAttributes<HTMLDivElement>, "children">
85
- { tabs; onChangeTab; selectedId; size? }
86
- ```
87
-
88
- Общая база Desktop/Mobile вариантов.
34
+ | Компонент | Что делает |
35
+ |---|---|
36
+ | `TabsLine` | Корневой `<div role="tablist">`: горизонтальные отступы, разделитель и выбор десктопного / мобильного варианта через `MobileView` |
37
+ | `TabsLineDesktop` | Строка табов, распределение табов между строкой и дропдауном, клавиатурная навигация стрелками |
38
+ | `TabsLineMobile` | Строка табов с горизонтальной прокруткой, без дропдауна |
39
+ | `TabsLineItem` | Кнопка одного таба: `<button role="tab">` с текстом и опциональным значком уведомлений |
40
+ | `TabsLineDropdown` | Кнопка-таб, раскрывающая выпадающий список с не поместившимися табами |
89
41
 
90
42
  ---
91
43
 
92
- ## Поведение Desktop (`TabsLineDesktop`)
44
+ ## Варианты и props
45
+
46
+ `ITabsLineProps` наследует `ITabsLineDesktopProps` → `ITabsLineBaseProps` → `Omit<React.HTMLAttributes<HTMLDivElement>, "children">`. Неизвестные компоненту атрибуты уходят на корневой `<div>`.
93
47
 
94
- ### Распределение inline / dropdown
48
+ ### Обязательные props
95
49
 
96
- `useEffect` на `[tabs, maxVisible]`:
97
- - Если `maxVisible` задан И `i + 1 >= maxVisible` И `tabs.length > maxVisible` → `dropdownTabs`. Иначе → `inlineTabs`.
98
- - Сравнение через `isEqual` (lodash) перед `setState` — избегаем лишних рендеров.
50
+ | Prop | Тип | Описание |
51
+ |---|---|---|
52
+ | `tabs` | `ITabsLineItemProps[]` | Коллекция табов. Порядок в массиве — порядок в строке |
53
+ | `selectedId` | `string` | Id выбранного таба. Компонент полностью управляемый: своего состояния выбора у него нет |
54
+ | `onChangeTab` | `(tabId: string) => void` | Запрос на смену таба. Вызывается на **каждый** клик по табу, включая клик по уже выбранному |
99
55
 
100
- `maxVisible` без overflow (когда `tabs.length <= maxVisible`) — все остаются inline.
56
+ ### Опциональные props
101
57
 
102
- ### Overflow-кнопка (`TabsLineDropdown`)
58
+ | Prop | Тип | По умолчанию | Описание |
59
+ |---|---|---|---|
60
+ | `size` | `EComponentSize` | `MD` | Высота строки и размер текста: SM — 28px, MD — 40px, LG — 56px. До мобильного варианта не доходит |
61
+ | `maxVisible` | `number` | — | Максимальное число элементов строки **вместе с кнопкой дропдауна**: в строке остаётся `maxVisible - 1` табов, остальные уезжают в список. Без prop'а все табы в строке |
62
+ | `paddingX` | `0 \| 8 \| 16 \| 24` | — | Горизонтальный отступ от первого таба слева и последнего справа. Пишется в `data-paddingx-size`, стили подбираются по атрибуту |
63
+ | `withSeparator` | `boolean` | `false` | Разделитель в виде нижнего бордера на всю ширину корневого элемента |
64
+ | `dropdownTargetHtmlAttributes` | `React.HTMLAttributes<HTMLButtonElement> & TestProps` | — | Атрибуты кнопки дропдауна: `data-test-id`, обработчики, `aria-*` |
103
65
 
104
- - Рендерится после inline-табов, если `dropdownTabs.length > 0`.
105
- - `label` — label выбранного таба, если он в overflow; иначе — label первого dropdown-таба.
106
- - `active` — `true`, если выбранный таб в overflow.
107
- - Внутри — `Dropdown` + `DropdownList` (а не ButtonDropdown как в `Tabs`).
108
- - Open/close: ArrowUp/Down открывают; Tab/Esc закрывают; mouseup outside — закрывает.
109
- - ARIA: `role="tab" aria-haspopup="menu" aria-expanded aria-controls aria-activedescendant`.
66
+ ### Поля элемента `tabs` (`ITabsLineItemProps`)
110
67
 
111
- ### Keyboard navigation
68
+ Интерфейс наследует `React.HTMLAttributes<HTMLButtonElement>`, поэтому в элемент массива можно положить любые атрибуты кнопки.
112
69
 
113
- Roving tabindex по `focusableTabIndexRef` (ref, не state):
114
- - `ArrowLeft` / `ArrowRight` (на `event.code`!) — двигают фокус по `inlineTabsRefs.current[index ± 1]`.
115
- - ArrowRight на последнем inline-табе **не** переходит на dropdown-кнопку (`isLastInlineTab` блокирует).
116
- - `onFocus` обновляет `focusableTabIndexRef.current = index`; `onBlur` сбрасывает в `0`.
70
+ | Поле | Тип | По умолчанию | Описание |
71
+ |---|---|---|---|
72
+ | `id` | `string` | — | Идентификатор таба. Сопоставляется с `selectedId` и приходит в `onChangeTab`. **В DOM не попадает** |
73
+ | `label` | `string` | — | Текст таба. Только строка: разметку сюда положить нельзя |
74
+ | `showNotificationIcon` | `boolean` | `false` | Значок новых уведомлений (`Badge.Dot`) в правом верхнем углу таба |
75
+ | `size` | `EComponentSize` | значение `size` компонента | Размер конкретного таба. Переопределяет общий `size` |
76
+ | `selected` | `boolean` | — | Игнорируется: выбранное состояние вычисляется по `selectedId` |
77
+
78
+ ### Ограничения
79
+
80
+ - **`maxVisible` считает кнопку дропдауна.** `maxVisible={3}` при пяти табах оставляет в строке два таба и кнопку. Пока табов не больше `maxVisible`, кнопка не рендерится и все табы остаются в строке.
81
+ - **Переполнение не зависит от ширины.** Решение принимается только по числу табов, замеров DOM нет. При длинных `label` строка может не поместиться в контейнер даже с заданным `maxVisible`.
82
+ - **Кнопка дропдауна подписывается табом, а не служебным текстом.** Если выбранный таб уехал в список — его `label`; иначе `label` первого скрытого таба. Отдельного текста вроде «Ещё» у компонента нет.
83
+ - **Мобильный вариант не поддерживает `size` и `maxVisible`.** `TabsLine` передаёт в `TabsLineMobile` только `tabs`, `selectedId` и `onChangeTab`, поэтому на мобильной ширине все табы остаются в строке, а общий `size` игнорируется — размер таба там дефолтный MD. Размер, заданный на самом элементе `tabs`, при этом применяется: он приходит внутри элемента массива.
84
+ - **`onChangeTab` не сравнивает id.** Клик по уже выбранному табу тоже вызывает обработчик — в отличие от `TabsExtended`.
85
+ - **`onKeyDown` таба перекрывает навигацию стрелками.** Обработчики `onClick`, `onFocus`, `onBlur` из элемента `tabs` вызываются **вместе** с внутренними, а вот `onKeyDown` и `tabIndex` спредятся после внутренних и полностью их заменяют — свой `onKeyDown` на табе отключит переход стрелками и roving `tabIndex`.
86
+ - **`dropdownTargetHtmlAttributes` мерджится с внутренними props кнопки.** `className` дописывается к внутренним классам, а `onClick`, `onKeyDown`, `onFocus`, `onBlur`, `onMouseEnter`, `onMouseLeave` вызываются **вместе** с внутренними обработчиками, не заменяя их.
87
+ - **Порядок спреда у корневого элемента:** `role="tablist"` стоит до `...rest` — переопределяется; `className` деструктурируется из props и дописывается к внутренним классам через `clsx`, поэтому внутренний не теряется; `data-paddingx-size` и `ref` стоят после `...rest` — не переопределяются.
88
+ - **DOM `id` пункта списка не равен `id` таба.** Пункт выпадающего списка получает `id` вида `<instanceId>-item-<index>`, чтобы два `TabsLine` с одинаковыми `id` табов не давали дублирующихся DOM `id` и `aria-activedescendant` не адресовал чужой экземпляр. В `onChangeTab` приходит исходный `id` таба.
89
+ - **Пустой массив `tabs`** — десктопный и мобильный варианты возвращают `null`, но корневой `<div role="tablist">` всё равно отрендерится.
117
90
 
118
91
  ---
119
92
 
120
- ## Поведение Mobile (`TabsLineMobile`)
121
-
122
- - Простая горизонтальная лента (`overflow-x: auto` через CSS).
123
- - НЕТ overflow-меню — табы прокручиваются.
124
- - НЕ принимает `maxVisible`, `paddingX`, `withSeparator`, `size` — игнорируются на mobile.
125
- - Возвращает `null` при пустом `tabs`.
93
+ ## Дизайн-токены
126
94
 
127
- ---
95
+ Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
96
+ «Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/TabsLine.ts`.
128
97
 
129
- ## `TabsLineItem` поведение
98
+ ```text
99
+ TabsLine.BorderColor_Active
100
+ TabsLine.BorderColor_Hover
101
+ TabsLine.Separator_Color
102
+ TabsLine.Shadow_Focus
103
+ ```
130
104
 
131
- - `<button type="button" role="tab" aria-selected>`.
132
- - Внутренний state: `focused`, `hovered` — для смены `EFontType` (`PRIMARY` при selected/focused/hovered, иначе `SECONDARY`).
133
- - `Text` size маппится через `tabsLineSizeToTextSizeMap` (LG=B2, MD=B3, SM=B4).
134
- - Default `size = MD`.
135
- - `Badge.Dot` — всегда `EComponentSize.MD` (НЕ зависит от `size` таба!).
105
+ Цвет текста таба задаётся не токенами группы: `TabsLineItem` и `TabsLineDropdown` рендерят `Text` и переключают его `type` между `EFontType.PRIMARY` (выбран, в фокусе или под курсором) и `EFontType.SECONDARY`.
136
106
 
137
107
  ---
138
108
 
139
109
  ## Инварианты
140
110
 
141
- - **`TabsLine` — `FC`, не `forwardRef`** — ref не пробрасывается.
142
- - **`TabsLineItem` — `forwardRef<HTMLButtonElement>`** — Desktop хранит refs в `inlineTabsRefs` для управления фокусом.
143
- - **`onChangeTab` обязателен** — нет внутреннего state выбора, всегда контролируемый.
144
- - **`paddingX` ограничен 4 значениями** (`0 | 8 | 16 | 24`) — расширять с осторожностью (CSS привязан к `data-paddingx-size`).
145
- - **`withSeparator`** включает `styles.withSeparator` на wrapper'е.
146
- - **Mobile рендерит ВСЕ табы inline** (нет maxVisible) — не пытаться добавить overflow без миграции UX.
147
- - **Keyboard navigation на `event.code`** (не `event.key`) — отличие от `Tabs`. Не унифицировать без проверки тестов.
148
- - **`isEqual` сравнение перед `setInlineTabs`/`setDropdownTabs`** — не убирать, иначе бесконечный ререндер.
149
- - **`Badge.Dot size = MD`** на TabsLineItem — намеренно (не наследует `size` таба).
150
- - **Реализация не использует `TabsExtended`** — отдельная архитектура. Изменения должны быть синхронизированы вручную, если нужна согласованность.
151
- - **`TabsLineDropdown` использует `Dropdown` + `DropdownList`**, не `ButtonDropdown` (как в `Tabs`) — это разные подсистемы.
111
+ - `forwardRef` есть у `TabsLine`, `TabsLineDesktop`, `TabsLineMobile`, `TabsLineItem` и `TabsLineDropdown` — не убирать. Ref указывает на тот же элемент, который получает `className` и `...rest`.
112
+ - У `TabsLineDropdown` ref потребителя объединён с внутренним ref корневого элемента: по этому же элементу позиционируется `Dropdown` (prop `targetRef`). Разрывать связку нельзя — список «оторвётся» от кнопки.
113
+ - `id` таба намеренно не уходит на `<button>`: он остаётся идентификатором в модели данных. Пункты выпадающего списка, наоборот, получают `id` — он нужен `aria-activedescendant`.
114
+ - Корневой элемент `TabsLine` — `<div role="tablist">`, элемент таба — `<button role="tab">`. Кнопка дропдауна — тоже `role="tab"`, а не `button`: она замещает собой скрытые табы.
115
+ - Атрибут `data-paddingx-size` — контракт стилей: значения `paddingX` разворачиваются в селекторы `each(@paddingXsizes, ...)` в `TabsLine.module.less`. Переименование атрибута или расширение набора значений требует правки LESS.
116
+ - `data-size` на корневом элементе `TabsLineDesktop` — тот же контракт для высоты строки.
117
+ - Размер таба приходит в `Badge.Dot` и в `Text` (через `tabsLineSizeToTextSizeMap`): SM → `B4`, MD → `B3`, LG → `B2`. Маппинг типизирован как `Record<EComponentSize, ETextSize>` — новый размер компонента обязан появиться и здесь.
118
+ - `splitTabsByMaxVisible` из `utils.ts` — единственное место, где решается, какие табы уходят в дропдаун. Через barrel не экспортируется.
119
+ - Компонент React 17-совместим: уникальный id дропдауна строится через `uniqueId` из `lodash-es`, а не через `useId`.
152
120
 
153
121
  ---
154
122
 
155
123
  ## Accessibility
156
124
 
157
- - Корень: `<div role="tablist">`.
158
- - Каждый таб: `<button role="tab" aria-selected={selected}>`.
159
- - Overflow-кнопка: `role="tab" aria-haspopup="menu" aria-expanded aria-controls aria-activedescendant`.
160
- - Roving tabindex (`tabIndex = focusableTabIndexRef.current === index ? 0 : -1`).
161
- - ArrowLeft/ArrowRight перемещают фокус. ArrowUp/Down открывают dropdown.
162
- - Tab / Escape закрывают открытый dropdown.
163
- - Mobile: естественный таб-ордер всех кнопок (нет roving tabindex).
125
+ - Корневой элемент — `role="tablist"`, каждый таб — `<button role="tab">` с `aria-selected`. Связку с панелями контента (`aria-controls`, `id` панели, `role="tabpanel"`) компонент **не выставляет** — это на потребителе.
126
+ - **Клавиатура в строке табов (только десктоп):** `ArrowLeft` / `ArrowRight` переводят фокус на соседний таб с `preventDefault`. Реализован roving `tabIndex`: в порядок обхода включён только таб в фокусе, остальные получают `tabIndex={-1}`, на `blur` фокусируемым снова становится первый таб. Поэтому один `Tab` из строки табов ведёт сразу на кнопку дропдауна, а не на следующий таб. Движение по кругу не реализовано: с первого таба `ArrowLeft` никуда не ведёт, с последнего `ArrowRight` не уводит на кнопку дропдауна.
127
+ - **Клавиатура на кнопке дропдауна:** `ArrowUp` / `ArrowDown` раскрывают список (с `preventDefault`), `Escape` и `Tab` закрывают. `Enter` и `Space` работают нативной активацией `<button>`. Раскрытый список также закрывается по `mouseup` вне кнопки и вне списка.
128
+ - Кнопка дропдауна несёт `aria-haspopup="menu"`, `aria-expanded`, `aria-controls` (id списка) и `aria-activedescendant` (id активного пункта, приходит из `DropdownListContext`).
129
+ - Фокус подсвечивается через `:focus-visible` (токен `TabsLine.Shadow_Focus`) — только при клавиатурной навигации. Подчёркивание `::after` при этом реагирует и на `:hover`, и на `:focus`.
130
+ - Компонент не хардкодит текст: значок новых уведомлений — чисто визуальный, доступного имени у него нет. Если статус «есть новое» важен для скринридера, передай `aria-label` в соответствующий элемент `tabs`.
131
+ - Фокус после выбора таба в списке компонент не возвращает на кнопку дропдауна — список просто закрывается.
164
132
 
165
133
  ---
166
134
 
167
135
  ## Связанные компоненты
168
136
 
169
- - `Tabs` — табы с overflow через `ButtonDropdown`.
170
- - `TabsExtended` — низкоуровневая альтернатива (composition slots).
171
- - `MobileView` — переключение desktop/mobile.
172
- - `Dropdown` / `DropdownList` — overflow-меню в desktop-варианте.
173
- - `Badge.Dot` — индикатор уведомлений.
174
- - `Typography.Text` — текст лейбла.
137
+ Отдельного AI.md нет ни у одной части семейства — все они описаны в разделе «Состав». Это область задачи TRI-92 («Scope: только TabsLine»): в таблице `docs/ai/ROADMAP.md` у `TabsLine` одна строка.
138
+
139
+ **Соседние компоненты:**
140
+
141
+ - `Tabs` — табы-переключатели в залитом контейнере поверх `TabsExtended`. Тот же класс задачи, другой визуальный паттерн и автоматическое переполнение по ширине.
142
+ - `TabsExtended` — базовый компонент табов с render-prop'ами: нужен, когда содержимое таба или триггер выпадающего списка нестандартные.
143
+ - `Dropdown` — `TabsLineDropdown` рендерит внутри себя `Dropdown` + `DropdownList` + `DropdownList.Item` и прокидывает в них `size`; ширина списка — `EDropdownWidth.MIN_TARGET`.
144
+ - `Badge` — значок новых уведомлений на табе (`Badge.Dot`).
145
+ - `Text` — текст таба; размер выбирается по `size` через `tabsLineSizeToTextSizeMap`.
146
+ - `MobileView` — переключает десктопный и мобильный варианты по ширине окна (<768px). Собственной разметки не добавляет.
175
147
 
176
148
  ---
177
149
 
178
150
  ## Stories
179
151
 
180
152
  Основные истории: `stories/TabsLine/TabsLine.stories.tsx`
181
- Примеры: `stories/TabsLine/examples/`
153
+ Файлы примеров: `stories/TabsLine/examples/`
154
+
155
+ | Story | Example file | Что демонстрирует |
156
+ |---|---|---|
157
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `paddingX`, `maxVisible`, `size`, `withSeparator` и значка уведомлений |
158
+ | `Default` | `Default.tsx` | Минимальный состав: четыре таба без дропдауна и отступов |
159
+ | `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG |
160
+ | `Paddings` | `Paddings.tsx` | Горизонтальные отступы 0 / 8 / 16 / 24 |
161
+ | `WithSeparator` | `WithSeparator.tsx` | Нижний разделитель на всю ширину |
162
+ | `WithNotificationIcon` | `WithNotificationIcon.tsx` | Значок новых уведомлений на табах |
163
+ | `WithDropdown` | `WithDropdown.tsx` | `maxVisible={3}`: часть табов уезжает в выпадающий список |
164
+ | `VisualTests` | `VisualTests.tsx` | Раскрытый выпадающий список и `:focus-visible` на его кнопке (через `play`) |
182
165
 
183
166
  ---
184
167
 
@@ -186,4 +169,195 @@ Roving tabindex по `focusableTabIndexRef` (ref, не state):
186
169
 
187
170
  | Дата | Изменение |
188
171
  |---|---|
189
- | 2026-04-18 | Создан документ |
172
+ | 2026-09-16 | Создан документ AI-ready для `TabsLine` (TRI-92). |
173
+ | 2026-09-16 | AI-рефакторинг: `TabsLine`, `TabsLineDesktop`, `TabsLineMobile` и `TabsLineDropdown` переведены с `React.FC` на `forwardRef` (у `TabsLineDropdown` ref потребителя объединён с внутренним ref позиционирования), у всех появился `displayName`. В `TabsLineDesktop` распределение табов между строкой и дропдауном переведено с `useEffect` + `setState` на производное значение (`splitTabsByMaxVisible` в `utils.ts`), roving `tabIndex` переехал с ref на состояние — оба `eslint-disable react-hooks/*` сняты. `TabsLineItem` перестал терять `className`, `onMouseEnter` и `onMouseLeave` потребителя; `TabsLineDesktop` и `TabsLineMobile` начали прокидывать `className` и `...rest` на корневой элемент. Удалены ссылки на несуществующие классы LESS-модуля (`tabInner`, `dropdownItem`, `dropdownItemInner` — в самом модуле их не было, поэтому обёртки рендерились без класса) вместе с самими обёртками-`<span>`. Публичный API (имена props, типы, значения enum, barrel-экспорты) не изменён. Добавлены unit-тесты на все части семейства и stories по modern pattern. |
174
+ | 2026-09-16 | Правки по ревью PR: DOM `id` пункта выпадающего списка выводится из внутреннего идентификатора экземпляра (`<instanceId>-item-<index>`) вместо `id` таба — два `TabsLine` с одинаковыми `id` табов больше не дают дублирующихся DOM `id`; в `onChangeTab` по-прежнему приходит исходный `id` таба. В `handleTargetKeyDown` пользовательский `onKeyDown` вызывается после внутренней логики — как у остальных обработчиков кнопки дропдауна. |
175
+
176
+ ---
177
+
178
+ ## Примеры использования
179
+
180
+ ### Default
181
+
182
+ `stories/TabsLine/examples/Default.tsx`
183
+
184
+ ```tsx
185
+ import React, { useState } from "react";
186
+ import { TabsLine } from "@sberbusiness/triplex-next";
187
+
188
+ const TABS = [
189
+ { id: "tabs-line-all", label: "Tab Name 1" },
190
+ { id: "tabs-line-draft", label: "Tab Name 2" },
191
+ { id: "tabs-line-sign", label: "Tab Name 3" },
192
+ { id: "tabs-line-executed", label: "Tab Name 4" },
193
+ ];
194
+
195
+ export const Default = () => {
196
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
197
+
198
+ return <TabsLine tabs={TABS} selectedId={selectedId} onChangeTab={setSelectedId} />;
199
+ };
200
+ ```
201
+
202
+ ### Sizes
203
+
204
+ `stories/TabsLine/examples/Sizes.tsx`
205
+
206
+ ```tsx
207
+ import React, { useState } from "react";
208
+ import { EComponentSize, TabsLine } from "@sberbusiness/triplex-next";
209
+
210
+ const TABS = [
211
+ { id: "tabs-line-all", label: "Tab Name 1" },
212
+ { id: "tabs-line-draft", label: "Tab Name 2" },
213
+ { id: "tabs-line-sign", label: "Tab Name 3" },
214
+ { id: "tabs-line-executed", label: "Tab Name 4" },
215
+ { id: "tabs-line-rejected", label: "Tab Name 5" },
216
+ ];
217
+
218
+ /** Пятый таб уходит в дропдаун — так кнопка дропдауна попадает в кадр во всех трёх размерах. */
219
+ const MAX_VISIBLE = 4;
220
+
221
+ interface ISizeItemProps {
222
+ size: EComponentSize;
223
+ }
224
+
225
+ const SizeItem = ({ size }: ISizeItemProps) => {
226
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
227
+
228
+ return (
229
+ <div>
230
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
231
+ <TabsLine
232
+ tabs={TABS}
233
+ selectedId={selectedId}
234
+ onChangeTab={setSelectedId}
235
+ size={size}
236
+ maxVisible={MAX_VISIBLE}
237
+ />
238
+ </div>
239
+ );
240
+ };
241
+
242
+ const SIZES = Object.values(EComponentSize);
243
+
244
+ export const Sizes = () => (
245
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
246
+ {SIZES.map((size) => (
247
+ <SizeItem key={size} size={size} />
248
+ ))}
249
+ </div>
250
+ );
251
+ ```
252
+
253
+ ### Paddings
254
+
255
+ `stories/TabsLine/examples/Paddings.tsx`
256
+
257
+ ```tsx
258
+ import React, { useState } from "react";
259
+ import { TabsLine } from "@sberbusiness/triplex-next";
260
+
261
+ const TABS = [
262
+ { id: "tabs-line-all", label: "Tab Name 1" },
263
+ { id: "tabs-line-draft", label: "Tab Name 2" },
264
+ { id: "tabs-line-sign", label: "Tab Name 3" },
265
+ { id: "tabs-line-executed", label: "Tab Name 4" },
266
+ ];
267
+
268
+ const PADDINGS: Array<0 | 8 | 16 | 24> = [0, 8, 16, 24];
269
+
270
+ interface IPaddingItemProps {
271
+ paddingX: 0 | 8 | 16 | 24;
272
+ }
273
+
274
+ const PaddingItem = ({ paddingX }: IPaddingItemProps) => {
275
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
276
+
277
+ return (
278
+ <div>
279
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{paddingX}</div>
280
+ <TabsLine tabs={TABS} selectedId={selectedId} onChangeTab={setSelectedId} paddingX={paddingX} />
281
+ </div>
282
+ );
283
+ };
284
+
285
+ export const Paddings = () => (
286
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
287
+ {PADDINGS.map((paddingX) => (
288
+ <PaddingItem key={paddingX} paddingX={paddingX} />
289
+ ))}
290
+ </div>
291
+ );
292
+ ```
293
+
294
+ ### WithSeparator
295
+
296
+ `stories/TabsLine/examples/WithSeparator.tsx`
297
+
298
+ ```tsx
299
+ import React, { useState } from "react";
300
+ import { TabsLine } from "@sberbusiness/triplex-next";
301
+
302
+ const TABS = [
303
+ { id: "tabs-line-all", label: "Tab Name 1" },
304
+ { id: "tabs-line-draft", label: "Tab Name 2" },
305
+ { id: "tabs-line-sign", label: "Tab Name 3" },
306
+ { id: "tabs-line-executed", label: "Tab Name 4" },
307
+ ];
308
+
309
+ export const WithSeparator = () => {
310
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
311
+
312
+ return <TabsLine tabs={TABS} selectedId={selectedId} onChangeTab={setSelectedId} withSeparator />;
313
+ };
314
+ ```
315
+
316
+ ### WithNotificationIcon
317
+
318
+ `stories/TabsLine/examples/WithNotificationIcon.tsx`
319
+
320
+ ```tsx
321
+ import React, { useState } from "react";
322
+ import { TabsLine } from "@sberbusiness/triplex-next";
323
+
324
+ const TABS = [
325
+ { id: "tabs-line-all", label: "Tab Name 1" },
326
+ { id: "tabs-line-draft", label: "Tab Name 2", showNotificationIcon: true },
327
+ { id: "tabs-line-sign", label: "Tab Name 3", showNotificationIcon: true },
328
+ { id: "tabs-line-executed", label: "Tab Name 4" },
329
+ ];
330
+
331
+ export const WithNotificationIcon = () => {
332
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
333
+
334
+ return <TabsLine tabs={TABS} selectedId={selectedId} onChangeTab={setSelectedId} />;
335
+ };
336
+ ```
337
+
338
+ ### WithDropdown
339
+
340
+ `stories/TabsLine/examples/WithDropdown.tsx`
341
+
342
+ ```tsx
343
+ import React, { useState } from "react";
344
+ import { TabsLine } from "@sberbusiness/triplex-next";
345
+
346
+ const TABS = [
347
+ { id: "tabs-line-all", label: "Tab Name 1" },
348
+ { id: "tabs-line-draft", label: "Tab Name 2" },
349
+ { id: "tabs-line-sign", label: "Tab Name 3" },
350
+ { id: "tabs-line-executed", label: "Tab Name 4" },
351
+ { id: "tabs-line-rejected", label: "Tab Name 5" },
352
+ ];
353
+
354
+ export const WithDropdown = () => {
355
+ const [selectedId, setSelectedId] = useState("tabs-line-all");
356
+
357
+ return (
358
+ // maxVisible считает и кнопку дропдауна: в строке остаются два таба, остальные уезжают в список.
359
+ <TabsLine tabs={TABS} selectedId={selectedId} onChangeTab={setSelectedId} maxVisible={3} />
360
+ );
361
+ };
362
+ ```
363
+