@sberbusiness/triplex-next-mcp-server 1.46.0-beta → 1.48.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/bundle/components/AmountField.md +222 -323
- package/bundle/components/Avatar.md +138 -146
- package/bundle/components/CardStatic.md +402 -0
- package/bundle/components/CheckboxTree.md +157 -215
- package/bundle/components/CheckboxTreeExtended.md +7 -7
- package/bundle/components/ChipGroup.md +1 -1
- package/bundle/components/ChipMonthYearPicker.md +4 -4
- package/bundle/components/ChipSuggest.md +7 -6
- package/bundle/components/CollapsibleTree.md +8 -4
- package/bundle/components/CollapsibleTreeExtended.md +8 -6
- package/bundle/components/Confirm.md +346 -281
- package/bundle/components/DateRange.md +351 -210
- package/bundle/components/FooterPage.md +1 -0
- package/bundle/components/FormField.md +11 -4
- package/bundle/components/FormGroup.md +3 -2
- package/bundle/components/LightBox.md +8 -0
- package/bundle/components/ListMaster.md +2 -2
- package/bundle/components/ListTableItem.md +6 -6
- package/bundle/components/MonthYearField.md +15 -4
- package/bundle/components/Slider.md +389 -97
- package/bundle/components/SliderRange.md +486 -105
- package/bundle/components/StatusTracker.md +386 -94
- package/bundle/components/Stepper.md +355 -102
- package/bundle/components/StepperExtended.md +633 -0
- package/bundle/components/SuggestField.md +1049 -96
- package/bundle/components/SwipeableArea.md +7 -7
- package/bundle/components/Tabs.md +265 -92
- package/bundle/components/TabsExtended.md +18 -18
- package/bundle/components/TabsLine.md +301 -127
- package/bundle/components/TagColor.md +187 -66
- package/bundle/components/TagGroup.md +184 -48
- package/bundle/components/TextareaField.md +278 -70
- package/bundle/components/TopOverlay.md +17 -1
- package/bundle/components/TreeView.md +16 -7
- package/bundle/components/TriggerClickOnKeyDownEvent.md +277 -0
- package/bundle/components/UploadZone.md +318 -0
- package/bundle/guides/codestyle.md +16 -1
- package/bundle/guides/commits.md +47 -1
- package/bundle/guides/tests.md +23 -2
- package/bundle/manifest.json +48 -21
- package/bundle/release-notes/1.47.0.mdx +115 -0
- package/bundle/release-notes/1.48.0.mdx +68 -0
- package/bundle/release-notes/1.49.0.mdx +7 -0
- package/bundle/tokens.json +3 -3
- package/dist/index.js +220 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: TabsLine
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
Используй когда: нужны
|
|
23
|
-
нескольких табов и автосворачиванием в dropdown. Для табов с кастомным
|
|
24
|
-
overflow — `Tabs` (общий стиль) или `TabsExtended` (низкоуровневый).
|
|
20
|
+
Используй когда: нужны табы-подчёркивания в шапке раздела или над таблицей, а состав табов — это плоский список «текст + опциональный значок уведомлений».
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Файловая структура
|
|
22
|
+
Не используй когда:
|
|
29
23
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
32
|
+
`TabsLine` — единая точка входа. Остальные компоненты папки экспортируются через barrel, но рассчитаны на внутреннее использование: собственного AI.md у них нет, их контракт описан здесь.
|
|
82
33
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
##
|
|
44
|
+
## Варианты и props
|
|
45
|
+
|
|
46
|
+
`ITabsLineProps` наследует `ITabsLineDesktopProps` → `ITabsLineBaseProps` → `Omit<React.HTMLAttributes<HTMLDivElement>, "children">`. Неизвестные компоненту атрибуты уходят на корневой `<div>`.
|
|
93
47
|
|
|
94
|
-
###
|
|
48
|
+
### Обязательные props
|
|
95
49
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
50
|
+
| Prop | Тип | Описание |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `tabs` | `ITabsLineItemProps[]` | Коллекция табов. Порядок в массиве — порядок в строке |
|
|
53
|
+
| `selectedId` | `string` | Id выбранного таба. Компонент полностью управляемый: своего состояния выбора у него нет |
|
|
54
|
+
| `onChangeTab` | `(tabId: string) => void` | Запрос на смену таба. Вызывается на **каждый** клик по табу, включая клик по уже выбранному |
|
|
99
55
|
|
|
100
|
-
|
|
56
|
+
### Опциональные props
|
|
101
57
|
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
+
Интерфейс наследует `React.HTMLAttributes<HTMLButtonElement>`, поэтому в элемент массива можно положить любые атрибуты кнопки.
|
|
112
69
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
98
|
+
```text
|
|
99
|
+
TabsLine.BorderColor_Active
|
|
100
|
+
TabsLine.BorderColor_Hover
|
|
101
|
+
TabsLine.Separator_Color
|
|
102
|
+
TabsLine.Shadow_Focus
|
|
103
|
+
```
|
|
130
104
|
|
|
131
|
-
|
|
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
|
-
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
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
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
- `
|
|
174
|
-
- `
|
|
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
|
-
|
|
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-
|
|
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
|
+
|