@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
|
@@ -234,7 +234,7 @@ export const Default = () => (
|
|
|
234
234
|
rightSwipeableArea={
|
|
235
235
|
<ListItemControls>
|
|
236
236
|
<ListItemControlsButton
|
|
237
|
-
icon={<AttachmentStrokeSrvIcon20 paletteIndex={
|
|
237
|
+
icon={<AttachmentStrokeSrvIcon20 paletteIndex={0} />}
|
|
238
238
|
onClick={() => {}}
|
|
239
239
|
>
|
|
240
240
|
Скачать
|
|
@@ -279,7 +279,7 @@ export const WithBothAreas = () => (
|
|
|
279
279
|
leftSwipeableArea={
|
|
280
280
|
<ListItemControls>
|
|
281
281
|
<ListItemControlsButton
|
|
282
|
-
icon={<DefaulticonStrokePrdIcon20 paletteIndex={
|
|
282
|
+
icon={<DefaulticonStrokePrdIcon20 paletteIndex={0} />}
|
|
283
283
|
onClick={() => {}}
|
|
284
284
|
>
|
|
285
285
|
В архив
|
|
@@ -289,7 +289,7 @@ export const WithBothAreas = () => (
|
|
|
289
289
|
rightSwipeableArea={
|
|
290
290
|
<ListItemControls>
|
|
291
291
|
<ListItemControlsButton
|
|
292
|
-
icon={<AttachmentStrokeSrvIcon20 paletteIndex={
|
|
292
|
+
icon={<AttachmentStrokeSrvIcon20 paletteIndex={0} />}
|
|
293
293
|
onClick={() => {}}
|
|
294
294
|
>
|
|
295
295
|
Скачать
|
|
@@ -368,7 +368,7 @@ export const ImperativeApi = () => {
|
|
|
368
368
|
leftSwipeableArea={
|
|
369
369
|
<ListItemControls>
|
|
370
370
|
<ListItemControlsButton
|
|
371
|
-
icon={<DefaulticonStrokePrdIcon20 paletteIndex={
|
|
371
|
+
icon={<DefaulticonStrokePrdIcon20 paletteIndex={0} />}
|
|
372
372
|
onClick={() => {}}
|
|
373
373
|
>
|
|
374
374
|
В архив
|
|
@@ -378,7 +378,7 @@ export const ImperativeApi = () => {
|
|
|
378
378
|
rightSwipeableArea={
|
|
379
379
|
<ListItemControls>
|
|
380
380
|
<ListItemControlsButton
|
|
381
|
-
icon={<AttachmentStrokeSrvIcon20 paletteIndex={
|
|
381
|
+
icon={<AttachmentStrokeSrvIcon20 paletteIndex={0} />}
|
|
382
382
|
onClick={() => {}}
|
|
383
383
|
>
|
|
384
384
|
Скачать
|
|
@@ -451,13 +451,13 @@ export const Example = () => {
|
|
|
451
451
|
rightSwipeableArea={
|
|
452
452
|
<ListItemControls>
|
|
453
453
|
<ListItemControlsButton
|
|
454
|
-
icon={<AttachmentStrokeSrvIcon20 paletteIndex={
|
|
454
|
+
icon={<AttachmentStrokeSrvIcon20 paletteIndex={0} />}
|
|
455
455
|
onClick={() => {}}
|
|
456
456
|
>
|
|
457
457
|
Скачать
|
|
458
458
|
</ListItemControlsButton>
|
|
459
459
|
<ListItemControlsButtonDropdown
|
|
460
|
-
icon={<DotshorizontalStrokeSrvIcon20 paletteIndex={
|
|
460
|
+
icon={<DotshorizontalStrokeSrvIcon20 paletteIndex={0} />}
|
|
461
461
|
options={[
|
|
462
462
|
{ id: `${payment.id}-option-1`, label: "Повторить", onSelect: () => {} },
|
|
463
463
|
{ id: `${payment.id}-option-2`, label: "Удалить", onSelect: () => {} },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: Tabs
|
|
3
|
-
category:
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
category: Tabs
|
|
4
|
+
related: [TabsExtended, TabsLine, ButtonDropdown]
|
|
5
|
+
tokens:
|
|
6
|
+
- Tabs.Type1_Tab_Background_Selected
|
|
7
|
+
- Tabs.Type2_Tab_Background_Selected
|
|
8
8
|
stories: stories/Tabs/Tabs.stories.tsx
|
|
9
9
|
version: "1.0"
|
|
10
10
|
---
|
|
@@ -13,135 +13,121 @@ version: "1.0"
|
|
|
13
13
|
|
|
14
14
|
## Назначение
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
табы, не помещающиеся в строку, автоматически уезжают в выпадающее меню.
|
|
18
|
-
Принимает простой массив `tabs: ITabsItem[]` вместо children-композиции.
|
|
19
|
-
Под капотом — `TabsExtended` с фиксированной разметкой
|
|
20
|
-
`Content > TabsWrapper + DropdownWrapper`.
|
|
16
|
+
Готовые табы-переключатели поверх `TabsExtended`: компонент принимает массив табов, сам собирает разметку, сам рендерит кнопку выпадающего списка для не поместившихся табов и сам обрабатывает навигацию стрелками.
|
|
21
17
|
|
|
22
|
-
Используй когда: нужен
|
|
23
|
-
|
|
24
|
-
`TabsExtended` напрямую. Для простых горизонтальных линейных табов без
|
|
25
|
-
overflow — `TabsLine`.
|
|
18
|
+
Используй когда: нужен типовой набор табов «лейбл + опциональный значок уведомлений» — это первый выбор для обычной задачи.
|
|
19
|
+
Не используй когда:
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
```text
|
|
32
|
-
src/components/Tabs/
|
|
33
|
-
├── Tabs.tsx # FC<ITabsProps>; склейка TabsExtended + ButtonDropdown
|
|
34
|
-
├── styles/
|
|
35
|
-
│ ├── Tabs.module.less
|
|
36
|
-
│ └── Tabs.less
|
|
37
|
-
└── index.ts
|
|
38
|
-
```
|
|
21
|
+
- содержимое таба нестандартное (своя разметка, свой триггер выпадающего списка) — бери `TabsExtended`, `Tabs` собран поверх него;
|
|
22
|
+
- нужны табы-подчёркивания в шапке раздела — бери `TabsLine`;
|
|
23
|
+
- нужен набор переключателей без семантики табов — это не задача компонента.
|
|
39
24
|
|
|
40
25
|
---
|
|
41
26
|
|
|
42
|
-
##
|
|
27
|
+
## Варианты и props
|
|
43
28
|
|
|
44
|
-
|
|
29
|
+
`ITabsProps` расширяет `ITabsExtendedProps` (а тот — `React.HTMLAttributes<HTMLDivElement>`). Неизвестные компоненту атрибуты уходят на корневой `<div>` с `role="tablist"`.
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
extends Omit<ITabsExtendedTabProps, "children" | "onSelect">
|
|
48
|
-
```
|
|
31
|
+
### Обязательные props
|
|
49
32
|
|
|
50
|
-
|
|
|
33
|
+
| Prop | Тип | Описание |
|
|
51
34
|
|---|---|---|
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
|
|
56
|
-
### `ITabsProps`
|
|
35
|
+
| `tabs` | `Array<ITabsItem>` | Состав табов. Порядок массива = порядок в строке и порядок обхода стрелками |
|
|
36
|
+
| `selectedId` | `string` | Id выбранного таба. Компонент полностью управляемый: своего состояния выбора у него нет |
|
|
37
|
+
| `onSelectTab` | `(selectedId: string) => void` | Запрос на смену выбранного таба. Вызывается **только** если выбирают таб, отличный от `selectedId` — и из строки, и из выпадающего списка |
|
|
57
38
|
|
|
58
|
-
|
|
59
|
-
extends ITabsExtendedProps
|
|
60
|
-
```
|
|
39
|
+
### Опциональные props
|
|
61
40
|
|
|
62
|
-
| Prop | Тип |
|
|
41
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
63
42
|
|---|---|---|---|
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `buttonDropdownAttributes?` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | HTML-атрибуты для `ButtonDropdown`. |
|
|
69
|
-
| `type?` | `ETabsExtendedType` | `TYPE_1` | Тип оформления (от `TabsExtended`). |
|
|
70
|
-
| `children` | `never` | — | Запрещено — структура фиксирована. |
|
|
71
|
-
| `...HTMLAttributes<HTMLDivElement>` | — | — | На корневой `TabsExtended` (role=tablist). |
|
|
43
|
+
| `type` | `ETabsExtendedType` | `TYPE_1` | Тип оформления, выбирается по фону **страницы**: `TYPE_1` — серый фон контейнера, для белой страницы; `TYPE_2` — белый фон контейнера, для затенённой страницы |
|
|
44
|
+
| `size` | `EComponentSize` | `MD` | Один prop на весь компонент: задаёт и контейнер, и кнопки табов, и кнопку выпадающего списка (в отличие от `TabsExtended`, где размеры независимы) |
|
|
45
|
+
| `buttonDropdownAttributes` | `React.ButtonHTMLAttributes<HTMLButtonElement>` | — | HTML-атрибуты кнопки выпадающего списка. Сюда передаётся `aria-label`; переданный `className` мерджится с собственными классами кнопки, не затирая их |
|
|
46
|
+
| `children` | `never` | — | Запрещены типом: состав задаётся только через `tabs` |
|
|
72
47
|
|
|
73
|
-
|
|
48
|
+
### Поля элемента `tabs` (`ITabsItem`)
|
|
74
49
|
|
|
75
|
-
|
|
50
|
+
`ITabsItem` расширяет `Omit<ITabsExtendedTabProps, "children" | "onSelect">`, то есть принимает и HTML-атрибуты контейнера таба (`<span>`).
|
|
76
51
|
|
|
77
|
-
|
|
52
|
+
| Поле | Тип | Описание |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `id` | `string` | Id таба. Сопоставляется с `selectedId` и приходит в `onSelectTab` |
|
|
55
|
+
| `label` | `React.ReactNode` | Содержимое кнопки таба |
|
|
56
|
+
| `showNotificationIcon` | `boolean` | Значок новых уведомлений (`Badge.Dot`) на кнопке таба и на пункте выпадающего списка |
|
|
57
|
+
|
|
58
|
+
### Ограничения
|
|
78
59
|
|
|
79
|
-
`
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`
|
|
60
|
+
- `label` и `showNotificationIcon` — поля кнопки таба, на контейнер таба они не уходят; остальные поля элемента считаются HTML-атрибутами `<span>`-контейнера.
|
|
61
|
+
- Компонент не хардкодит текст: без `buttonDropdownAttributes["aria-label"]` кнопка выпадающего списка остаётся без доступного имени.
|
|
62
|
+
- Выпадающий список рендерится всегда, но скрыт (`hidden`), пока все табы помещаются в строку.
|
|
63
|
+
- `id` табов должны быть уникальными: по ним идёт и сопоставление с `selectedId`, и React-ключи, и замеры ширины.
|
|
83
64
|
|
|
84
|
-
|
|
65
|
+
---
|
|
85
66
|
|
|
86
|
-
|
|
67
|
+
## Дизайн-токены
|
|
87
68
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- `ArrowRight` → следующий таб (если не последний в строке — `isLastInlineTab`).
|
|
91
|
-
- При `onFocus` запоминаются id `prevAvailableToFocusTabId` /
|
|
92
|
-
`nextAvailableToFocusTabId` — на их кнопки навешиваются `prevTabRef` /
|
|
93
|
-
`nextTabRef` для программного `.focus()`.
|
|
69
|
+
Переопределяются через `ThemeProvider` (prop `tokens`) — см. `ThemeProvider-ai.md` →
|
|
70
|
+
«Как переопределять токены». Значения по умолчанию — `src/components/DesignTokens/components/Tabs.ts`.
|
|
94
71
|
|
|
95
|
-
|
|
96
|
-
`<TabsExtended.Content.Tab>{({ selected, isFirstInlineTab, isLastInlineTab }) => ...}</TabsExtended.Content.Tab>`.
|
|
72
|
+
Собственные стили компонента — только подсветка кнопки выпадающего списка, когда выбранный таб уехал в список:
|
|
97
73
|
|
|
98
|
-
|
|
74
|
+
```text
|
|
75
|
+
Tabs.Type1_Tab_Background_Selected
|
|
76
|
+
Tabs.Type2_Tab_Background_Selected
|
|
77
|
+
```
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
- `availableToFocusTabId` инициализируется как `selectedId || tabs[0].id` —
|
|
102
|
-
при пустом `tabs` упадёт (`tabs[0].id`); потребитель должен передавать
|
|
103
|
-
непустой массив.
|
|
79
|
+
Оформление контейнера и самих кнопок табов приходит из `TabsExtended` — он использует всё семейство токенов `Tabs`, общее для обоих компонентов.
|
|
104
80
|
|
|
105
81
|
---
|
|
106
82
|
|
|
107
83
|
## Инварианты
|
|
108
84
|
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
- **`FC, не forwardRef`** — ref не пробрасывается. Если нужен ref на `tablist` — использовать `TabsExtended` напрямую.
|
|
85
|
+
- `Tabs` объявлен через `React.forwardRef<HTMLDivElement, ITabsProps>`, `ref` уходит на корневой `<div role="tablist">` компонента `TabsExtended`; `displayName = "Tabs"`. Зафиксировано unit-тестом.
|
|
86
|
+
- Публичные имена `ITabsProps`, `ITabsItem`, `Tabs` и barrel `src/components/Tabs/index.ts` — часть публичного API.
|
|
87
|
+
- Компонент строится на `TabsExtended` и опирается на флаги `isFirstInlineTab` / `isLastInlineTab` из render-prop `Tab`: на них держатся границы навигации стрелками. Изменение их семантики ломает клавиатуру `Tabs`.
|
|
88
|
+
- Класс `.tabButtonDropdown` согласует высоту кнопки выпадающего списка с высотой кнопки таба (SM — 20px, MD — 32px, LG — 40px). Без него `ButtonDropdown` выше таба того же размера и растягивает и контейнер, и сами табы.
|
|
89
|
+
- `label` и `showNotificationIcon` обязаны вырезаться из элемента `tabs` перед спредом на `TabsExtended.Content.Tab`: иначе они уходят на `<span>` как неизвестные DOM-атрибуты, и React пишет предупреждение в консоль. Зафиксировано unit-тестом.
|
|
90
|
+
- Массив `tabs` может быть пустым — компонент не должен падать (поэтому запасной id фокусируемого таба читается через optional chaining).
|
|
91
|
+
- Таб с `tabIndex = 0` вычисляется на каждом рендере, а не хранится готовым в state: в state лежит только таб, последним получивший фокус, вместе с `selectedId` на тот момент. `inlineItemsIds` сверяется с текущим `tabs`: `TabsExtended` пересчитывает раскладку только при смене числа табов или ширины, и после замены `tabs` на массив той же длины там остаются старые id. Распределение табов по строке и выпадающему списку `Tabs` читает из `TabsExtendedContext` (`inlineItemsIds`) через `Consumer` внутри render-prop таба — сам `Tabs` рендерит провайдер и прочитать контекст хуком не может.
|
|
117
92
|
|
|
118
93
|
---
|
|
119
94
|
|
|
120
95
|
## Accessibility
|
|
121
96
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
- `
|
|
129
|
-
должен добавить текстовый намёк в `label` сам.
|
|
97
|
+
- Роли приходят из `TabsExtended`: корневой элемент — `role="tablist"`, кнопка таба — `<button role="tab">` с `aria-selected`, контейнер таба — `role="presentation"`.
|
|
98
|
+
- **Roving tabindex:** в таб-порядок страницы попадает ровно один таб (`tabIndex = 0`), остальные получают `tabIndex = -1`. Кандидаты по приоритету: таб, последним получивший фокус (стрелкой или кликом), — пока выбран тот же таб, что и в момент фокуса (смена выбора, в том числе извне через `selectedId`, его сбрасывает); затем выбранный таб (или первый, если `selectedId` пуст). `tabIndex = 0` получает первый кандидат, отображаемый в строке, а если оба в выпадающем списке или отсутствуют в `tabs` — первый таб строки. Строка табов не выпадает из обхода по `Tab`.
|
|
99
|
+
- **Клавиатура:** `ArrowLeft` / `ArrowRight` переносят фокус на соседний таб **в строке** и делают его единственным табом с `tabIndex = 0`. На краях строки фокус остаётся на месте (переноса на противоположный край нет). Для обеих стрелок вызывается `preventDefault` — чтобы страница не скроллилась; другие клавиши компонент не перехватывает.
|
|
100
|
+
- Выбор таба — нативной активацией `<button>` (`Enter` / `Space`) и кликом. Стрелки только перемещают фокус и **не меняют** выбранный таб.
|
|
101
|
+
- Табы, уехавшие в выпадающий список, скрыты через `display: none` и из обхода клавиатурой выпадают: попасть в них можно только через список.
|
|
102
|
+
- `aria-label` кнопки выпадающего списка задаёт потребитель через `buttonDropdownAttributes` — компонент текста не хардкодит.
|
|
103
|
+
- `aria-controls` и связку с панелью контента компонент не выставляет: если табы переключают панели, их `id` и `aria-controls` проставляет потребитель.
|
|
130
104
|
|
|
131
105
|
---
|
|
132
106
|
|
|
133
107
|
## Связанные компоненты
|
|
134
108
|
|
|
135
|
-
- `TabsExtended` —
|
|
136
|
-
- `TabsLine` —
|
|
137
|
-
- `ButtonDropdown` (`
|
|
109
|
+
- `TabsExtended` — база: `Tabs` рендерит его субкомпоненты и наследует от его интерфейсов (`ITabsProps extends ITabsExtendedProps`, `ITabsItem extends Omit<ITabsExtendedTabProps, ...>`). Туда же уходят за нестандартной разметкой табов.
|
|
110
|
+
- `TabsLine` — альтернативный визуальный паттерн табов (подчёркивание вместо залитой кнопки).
|
|
111
|
+
- `ButtonDropdown` — кнопка выпадающего списка, которую `Tabs` рендерит сам для не поместившихся табов; тема подбирается по `type` (`DOTS_SECONDARY` / `DOTS_SECONDARY_LIGHT`).
|
|
112
|
+
- `Badge` — `Badge.Dot` как значок новых уведомлений на кнопке таба (рендерится внутри `TabsExtended.Content.TabButton`).
|
|
138
113
|
|
|
139
114
|
---
|
|
140
115
|
|
|
141
116
|
## Stories
|
|
142
117
|
|
|
143
118
|
Основные истории: `stories/Tabs/Tabs.stories.tsx`
|
|
144
|
-
|
|
119
|
+
Файлы примеров: `stories/Tabs/examples/`
|
|
120
|
+
|
|
121
|
+
| Story | Example file | Что демонстрирует |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль типа, размера, количества табов, ширины контейнера и значка уведомлений |
|
|
124
|
+
| `Default` | `Default.tsx` | Минимальный набор: три таба, всё помещается в строку |
|
|
125
|
+
| `Types` | `Types.tsx` | Типы оформления `TYPE_1` и `TYPE_2` |
|
|
126
|
+
| `Sizes` | `Sizes.tsx` | Размеры SM / MD / LG |
|
|
127
|
+
| `WithNotificationIcon` | `WithNotificationIcon.tsx` | Значок новых уведомлений на табе |
|
|
128
|
+
| `WithDropdown` | `WithDropdown.tsx` | Узкий контейнер: часть табов уезжает в выпадающий список |
|
|
129
|
+
| `VisualTests` | `VisualTests.tsx` | Типы × размеры, значок уведомлений, переполнение строки, подсветка кнопки списка при выбранном скрытом табе, `:focus-visible` на табе (через `play`) |
|
|
130
|
+
| `VisualTestsOpen` | `VisualTests.tsx` | Скриншот-регрессия раскрытого выпадающего списка (открывается через `play`) |
|
|
145
131
|
|
|
146
132
|
---
|
|
147
133
|
|
|
@@ -149,4 +135,191 @@ Roving tabindex по `availableToFocusTabId`:
|
|
|
149
135
|
|
|
150
136
|
| Дата | Изменение |
|
|
151
137
|
|---|---|
|
|
152
|
-
| 2026-
|
|
138
|
+
| 2026-09-22 | Создан документ AI-ready для `Tabs` (TRI-90). |
|
|
139
|
+
| 2026-09-22 | AI-рефакторинг: `label` и `showNotificationIcon` больше не уходят на контейнер таба как DOM-атрибуты (React-предупреждение в консоли), React-ключ таба переведён с индекса на `id`, пустой массив `tabs` не роняет компонент, JSDoc проставлен на всех публичных props. Публичный API (имена props, значения enum, barrel-экспорты) не изменён. Добавлены unit-тесты на клавиатуру, выбор таба и выпадающий список, stories переписаны по modern pattern. |
|
|
140
|
+
| 2026-09-22 | `Tabs` переведён на `forwardRef` (ref на корневой `<div role="tablist">`) и получил `displayName`; тип компонента — `React.ForwardRefExoticComponent`. Roving tabindex синхронизирован с `selectedId` и с распределением табов по строке и выпадающему списку: строка табов больше не выпадает из обхода по `Tab`. Добавлены unit-тесты на `ref`, `displayName` и перенос `tabIndex = 0` (внешняя смена выбора, клик, уезд таба в выпадающий список, замена `tabs`). |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Примеры использования
|
|
145
|
+
|
|
146
|
+
### Default
|
|
147
|
+
|
|
148
|
+
`stories/Tabs/examples/Default.tsx`
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import React, { useState } from "react";
|
|
152
|
+
import { Tabs } from "@sberbusiness/triplex-next";
|
|
153
|
+
|
|
154
|
+
const TABS = [
|
|
155
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
156
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
157
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
export const Default = () => {
|
|
161
|
+
const [selectedId, setSelectedId] = useState("tab-1");
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<Tabs
|
|
165
|
+
tabs={TABS}
|
|
166
|
+
selectedId={selectedId}
|
|
167
|
+
onSelectTab={setSelectedId}
|
|
168
|
+
buttonDropdownAttributes={{ "aria-label": "Ещё табы" }}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Types
|
|
175
|
+
|
|
176
|
+
`stories/Tabs/examples/Types.tsx`
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import React, { useState } from "react";
|
|
180
|
+
import { ETabsExtendedType, Tabs } from "@sberbusiness/triplex-next";
|
|
181
|
+
|
|
182
|
+
const TABS = [
|
|
183
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
184
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
185
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
interface ITypeItemProps {
|
|
189
|
+
caption: string;
|
|
190
|
+
type: ETabsExtendedType;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const TypeItem = ({ caption, type }: ITypeItemProps) => {
|
|
194
|
+
const [selectedId, setSelectedId] = useState("tab-1");
|
|
195
|
+
|
|
196
|
+
return (
|
|
197
|
+
<div>
|
|
198
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{caption}</div>
|
|
199
|
+
<Tabs
|
|
200
|
+
tabs={TABS}
|
|
201
|
+
type={type}
|
|
202
|
+
selectedId={selectedId}
|
|
203
|
+
onSelectTab={setSelectedId}
|
|
204
|
+
buttonDropdownAttributes={{ "aria-label": "Ещё табы" }}
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export const Types = () => (
|
|
211
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
212
|
+
<TypeItem caption="TYPE_1 — для белой страницы" type={ETabsExtendedType.TYPE_1} />
|
|
213
|
+
<TypeItem caption="TYPE_2 — для затенённой страницы" type={ETabsExtendedType.TYPE_2} />
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Sizes
|
|
219
|
+
|
|
220
|
+
`stories/Tabs/examples/Sizes.tsx`
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import React, { useState } from "react";
|
|
224
|
+
import { EComponentSize, Tabs } from "@sberbusiness/triplex-next";
|
|
225
|
+
|
|
226
|
+
const TABS = [
|
|
227
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
228
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
229
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
interface ISizeItemProps {
|
|
233
|
+
size: EComponentSize;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const SizeItem = ({ size }: ISizeItemProps) => {
|
|
237
|
+
const [selectedId, setSelectedId] = useState("tab-1");
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div>
|
|
241
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{size.toUpperCase()}</div>
|
|
242
|
+
<Tabs
|
|
243
|
+
tabs={TABS}
|
|
244
|
+
size={size}
|
|
245
|
+
selectedId={selectedId}
|
|
246
|
+
onSelectTab={setSelectedId}
|
|
247
|
+
buttonDropdownAttributes={{ "aria-label": "Ещё табы" }}
|
|
248
|
+
/>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const SIZES = Object.values(EComponentSize);
|
|
254
|
+
|
|
255
|
+
export const Sizes = () => (
|
|
256
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
257
|
+
{SIZES.map((size) => (
|
|
258
|
+
<SizeItem key={size} size={size} />
|
|
259
|
+
))}
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### WithNotificationIcon
|
|
265
|
+
|
|
266
|
+
`stories/Tabs/examples/WithNotificationIcon.tsx`
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import React, { useState } from "react";
|
|
270
|
+
import { Tabs } from "@sberbusiness/triplex-next";
|
|
271
|
+
|
|
272
|
+
const TABS = [
|
|
273
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
274
|
+
{ id: "tab-2", label: "Tab Name 2", showNotificationIcon: true },
|
|
275
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
export const WithNotificationIcon = () => {
|
|
279
|
+
const [selectedId, setSelectedId] = useState("tab-1");
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<Tabs
|
|
283
|
+
tabs={TABS}
|
|
284
|
+
selectedId={selectedId}
|
|
285
|
+
onSelectTab={setSelectedId}
|
|
286
|
+
buttonDropdownAttributes={{ "aria-label": "Ещё табы" }}
|
|
287
|
+
/>
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### WithDropdown
|
|
293
|
+
|
|
294
|
+
`stories/Tabs/examples/WithDropdown.tsx`
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import React, { useState } from "react";
|
|
298
|
+
import { Tabs } from "@sberbusiness/triplex-next";
|
|
299
|
+
|
|
300
|
+
const TABS = [
|
|
301
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
302
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
303
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
304
|
+
{ id: "tab-4", label: "Tab Name 4" },
|
|
305
|
+
{ id: "tab-5", label: "Tab Name 5" },
|
|
306
|
+
{ id: "tab-6", label: "Tab Name 6" },
|
|
307
|
+
];
|
|
308
|
+
|
|
309
|
+
export const WithDropdown = () => {
|
|
310
|
+
const [selectedId, setSelectedId] = useState("tab-1");
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
// Ширина контейнера ограничена, чтобы часть табов не поместилась в строку и уехала в выпадающий список.
|
|
314
|
+
<div style={{ maxWidth: "420px" }}>
|
|
315
|
+
<Tabs
|
|
316
|
+
tabs={TABS}
|
|
317
|
+
selectedId={selectedId}
|
|
318
|
+
onSelectTab={setSelectedId}
|
|
319
|
+
buttonDropdownAttributes={{ "aria-label": "Ещё табы" }}
|
|
320
|
+
/>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
```
|
|
325
|
+
|
|
@@ -203,9 +203,9 @@ import React, { useState } from "react";
|
|
|
203
203
|
import { TabsExtended } from "@sberbusiness/triplex-next";
|
|
204
204
|
|
|
205
205
|
const TABS = [
|
|
206
|
-
{ id: "tab-1", label: "
|
|
207
|
-
{ id: "tab-2", label: "
|
|
208
|
-
{ id: "tab-3", label: "
|
|
206
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
207
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
208
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
209
209
|
];
|
|
210
210
|
|
|
211
211
|
export const Default = () => {
|
|
@@ -240,9 +240,9 @@ import React, { useState } from "react";
|
|
|
240
240
|
import { ETabsExtendedType, TabsExtended } from "@sberbusiness/triplex-next";
|
|
241
241
|
|
|
242
242
|
const TABS = [
|
|
243
|
-
{ id: "tab-1", label: "
|
|
244
|
-
{ id: "tab-2", label: "
|
|
245
|
-
{ id: "tab-3", label: "
|
|
243
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
244
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
245
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
246
246
|
];
|
|
247
247
|
|
|
248
248
|
interface ITypeItemProps {
|
|
@@ -294,9 +294,9 @@ import React, { useState } from "react";
|
|
|
294
294
|
import { EComponentSize, TabsExtended } from "@sberbusiness/triplex-next";
|
|
295
295
|
|
|
296
296
|
const TABS = [
|
|
297
|
-
{ id: "tab-1", label: "
|
|
298
|
-
{ id: "tab-2", label: "
|
|
299
|
-
{ id: "tab-3", label: "
|
|
297
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
298
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
299
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
300
300
|
];
|
|
301
301
|
|
|
302
302
|
interface ISizeItemProps {
|
|
@@ -349,9 +349,9 @@ import React, { useState } from "react";
|
|
|
349
349
|
import { TabsExtended } from "@sberbusiness/triplex-next";
|
|
350
350
|
|
|
351
351
|
const TABS = [
|
|
352
|
-
{ id: "tab-1", label: "
|
|
353
|
-
{ id: "tab-2", label: "
|
|
354
|
-
{ id: "tab-3", label: "
|
|
352
|
+
{ id: "tab-1", label: "Tab Name 1", showNotificationIcon: false },
|
|
353
|
+
{ id: "tab-2", label: "Tab Name 2", showNotificationIcon: true },
|
|
354
|
+
{ id: "tab-3", label: "Tab Name 3", showNotificationIcon: true },
|
|
355
355
|
];
|
|
356
356
|
|
|
357
357
|
export const WithNotificationIcon = () => {
|
|
@@ -395,12 +395,12 @@ import {
|
|
|
395
395
|
} from "@sberbusiness/triplex-next";
|
|
396
396
|
|
|
397
397
|
const TABS = [
|
|
398
|
-
{ id: "tab-1", label: "
|
|
399
|
-
{ id: "tab-2", label: "
|
|
400
|
-
{ id: "tab-3", label: "
|
|
401
|
-
{ id: "tab-4", label: "
|
|
402
|
-
{ id: "tab-5", label: "
|
|
403
|
-
{ id: "tab-6", label: "
|
|
398
|
+
{ id: "tab-1", label: "Tab Name 1" },
|
|
399
|
+
{ id: "tab-2", label: "Tab Name 2" },
|
|
400
|
+
{ id: "tab-3", label: "Tab Name 3" },
|
|
401
|
+
{ id: "tab-4", label: "Tab Name 4" },
|
|
402
|
+
{ id: "tab-5", label: "Tab Name 5" },
|
|
403
|
+
{ id: "tab-6", label: "Tab Name 6" },
|
|
404
404
|
];
|
|
405
405
|
|
|
406
406
|
/**
|