@sberbusiness/triplex-next-mcp-server 0.24.0-beta1
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 +124 -0
- package/bundle/.gitkeep +0 -0
- package/bundle/components/AbstractTree.md +198 -0
- package/bundle/components/AccordionBase.md +227 -0
- package/bundle/components/Alert.md +317 -0
- package/bundle/components/Amount.md +225 -0
- package/bundle/components/AmountField.md +289 -0
- package/bundle/components/Avatar.md +209 -0
- package/bundle/components/Badge.md +277 -0
- package/bundle/components/Body.md +167 -0
- package/bundle/components/Button.md +230 -0
- package/bundle/components/ButtonDropdown.md +159 -0
- package/bundle/components/ButtonIcon.md +122 -0
- package/bundle/components/Calendar.md +445 -0
- package/bundle/components/Card.md +374 -0
- package/bundle/components/CarouselExtended.md +250 -0
- package/bundle/components/Checkbox.md +126 -0
- package/bundle/components/CheckboxTree.md +280 -0
- package/bundle/components/CheckboxTreeExtended.md +321 -0
- package/bundle/components/CheckboxXGroup.md +68 -0
- package/bundle/components/CheckboxYGroup.md +67 -0
- package/bundle/components/Chip.md +404 -0
- package/bundle/components/ChipGroup.md +179 -0
- package/bundle/components/Col.md +221 -0
- package/bundle/components/CollapsableTree.md +255 -0
- package/bundle/components/Confirm.md +343 -0
- package/bundle/components/DateField.md +371 -0
- package/bundle/components/DatePickerExtended.md +342 -0
- package/bundle/components/DateRange.md +275 -0
- package/bundle/components/DesignTokens.md +385 -0
- package/bundle/components/Divider.md +196 -0
- package/bundle/components/DocumentNumberEdit.md +280 -0
- package/bundle/components/Dropdown.md +433 -0
- package/bundle/components/Ellipsis.md +208 -0
- package/bundle/components/EmptyView.md +99 -0
- package/bundle/components/ExpandAnimation.md +281 -0
- package/bundle/components/Footer.md +339 -0
- package/bundle/components/FormField.md +326 -0
- package/bundle/components/FormGroup.md +99 -0
- package/bundle/components/Gap.md +113 -0
- package/bundle/components/Header.md +280 -0
- package/bundle/components/HelpBox.md +154 -0
- package/bundle/components/IconWrapper.md +145 -0
- package/bundle/components/Island.md +197 -0
- package/bundle/components/IslandAccordion.md +230 -0
- package/bundle/components/IslandWidget.md +233 -0
- package/bundle/components/KeyDownListener.md +157 -0
- package/bundle/components/LightBox.md +322 -0
- package/bundle/components/Link.md +161 -0
- package/bundle/components/List.md +247 -0
- package/bundle/components/ListMaster.md +204 -0
- package/bundle/components/Loader.md +161 -0
- package/bundle/components/LoaderScreen.md +134 -0
- package/bundle/components/Marker.md +127 -0
- package/bundle/components/MarkerStatus.md +148 -0
- package/bundle/components/MediaWidth.md +169 -0
- package/bundle/components/MobileView.md +120 -0
- package/bundle/components/ModalWindow.md +308 -0
- package/bundle/components/MonthYearField.md +168 -0
- package/bundle/components/MultiselectField.md +213 -0
- package/bundle/components/Notification.md +233 -0
- package/bundle/components/NumberField.md +178 -0
- package/bundle/components/OrderedList.md +169 -0
- package/bundle/components/Overlay.md +237 -0
- package/bundle/components/Page.md +246 -0
- package/bundle/components/Pagination.md +298 -0
- package/bundle/components/Portal.md +119 -0
- package/bundle/components/Radio.md +174 -0
- package/bundle/components/Row.md +125 -0
- package/bundle/components/SMSField.md +160 -0
- package/bundle/components/SegmentedControl.md +235 -0
- package/bundle/components/SelectExtendedField.md +322 -0
- package/bundle/components/SelectField.md +213 -0
- package/bundle/components/Skeleton.md +129 -0
- package/bundle/components/Slider.md +157 -0
- package/bundle/components/SliderExtended.md +210 -0
- package/bundle/components/SliderRange.md +160 -0
- package/bundle/components/SmallInput.md +101 -0
- package/bundle/components/Spoiler.md +121 -0
- package/bundle/components/StatusTracker.md +151 -0
- package/bundle/components/Step.md +121 -0
- package/bundle/components/Stepper.md +163 -0
- package/bundle/components/Suggest.md +160 -0
- package/bundle/components/SuggestField.md +157 -0
- package/bundle/components/SwipeableArea.md +144 -0
- package/bundle/components/Table.md +199 -0
- package/bundle/components/Tabs.md +152 -0
- package/bundle/components/TabsExtended.md +273 -0
- package/bundle/components/TabsLine.md +189 -0
- package/bundle/components/Tag.md +135 -0
- package/bundle/components/TagColor.md +121 -0
- package/bundle/components/TagGroup.md +100 -0
- package/bundle/components/TextField.md +152 -0
- package/bundle/components/TextareaField.md +124 -0
- package/bundle/components/ThemeProvider.md +181 -0
- package/bundle/components/Tooltip.md +254 -0
- package/bundle/components/TopOverlay.md +145 -0
- package/bundle/components/TreeView.md +224 -0
- package/bundle/components/Triggers.md +121 -0
- package/bundle/guides/codestyle.md +205 -0
- package/bundle/guides/commits.md +166 -0
- package/bundle/guides/context.md +277 -0
- package/bundle/guides/stories.md +618 -0
- package/bundle/guides/template.md +154 -0
- package/bundle/guides/tests.md +301 -0
- package/bundle/icons.json +4156 -0
- package/bundle/manifest.json +796 -0
- package/bundle/release-notes/1.1.0.mdx +26 -0
- package/bundle/release-notes/1.1.1.mdx +14 -0
- package/bundle/release-notes/1.10.0.mdx +43 -0
- package/bundle/release-notes/1.11.0.mdx +19 -0
- package/bundle/release-notes/1.12.0.mdx +21 -0
- package/bundle/release-notes/1.13.0.mdx +43 -0
- package/bundle/release-notes/1.14.0.mdx +29 -0
- package/bundle/release-notes/1.15.0.mdx +56 -0
- package/bundle/release-notes/1.16.0.mdx +55 -0
- package/bundle/release-notes/1.17.0.mdx +15 -0
- package/bundle/release-notes/1.18.0.mdx +24 -0
- package/bundle/release-notes/1.19.0.mdx +48 -0
- package/bundle/release-notes/1.19.1.mdx +9 -0
- package/bundle/release-notes/1.2.0.mdx +80 -0
- package/bundle/release-notes/1.20.0.mdx +48 -0
- package/bundle/release-notes/1.21.0.mdx +66 -0
- package/bundle/release-notes/1.22.0.mdx +75 -0
- package/bundle/release-notes/1.23.0.mdx +50 -0
- package/bundle/release-notes/1.24.0.mdx +59 -0
- package/bundle/release-notes/1.25.0.mdx +52 -0
- package/bundle/release-notes/1.26.0.mdx +11 -0
- package/bundle/release-notes/1.3.0.mdx +21 -0
- package/bundle/release-notes/1.4.0.mdx +37 -0
- package/bundle/release-notes/1.5.0.mdx +24 -0
- package/bundle/release-notes/1.6.0.mdx +37 -0
- package/bundle/release-notes/1.6.1.mdx +11 -0
- package/bundle/release-notes/1.7.0.mdx +29 -0
- package/bundle/release-notes/1.8.0.mdx +38 -0
- package/bundle/release-notes/1.9.0.mdx +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +324 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: Tabs
|
|
3
|
+
category: Navigation
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [TabsExtended, TabsLine]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/Tabs/Tabs.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Tabs
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Готовый компонент табов с overflow-меню (`ButtonDropdown` с `DOTS_SECONDARY`):
|
|
17
|
+
табы, не помещающиеся в строку, автоматически уезжают в выпадающее меню.
|
|
18
|
+
Принимает простой массив `tabs: ITabsItem[]` вместо children-композиции.
|
|
19
|
+
Под капотом — `TabsExtended` с фиксированной разметкой
|
|
20
|
+
`Content > TabsWrapper + DropdownWrapper`.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужен стандартный таб-бар с поддержкой переполнения.
|
|
23
|
+
Для кастомной разметки (свой dropdown, иконки, другие заголовки) —
|
|
24
|
+
`TabsExtended` напрямую. Для простых горизонтальных линейных табов без
|
|
25
|
+
overflow — `TabsLine`.
|
|
26
|
+
|
|
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
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Публичный API
|
|
43
|
+
|
|
44
|
+
### `ITabsItem`
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
extends Omit<ITabsExtendedTabProps, "children" | "onSelect">
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Поле | Тип | Описание |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `id` | `string` | Уникальный id таба (используется в `selectedId`/`onSelectTab`). |
|
|
53
|
+
| `label` | `ReactNode` | Содержимое кнопки таба. |
|
|
54
|
+
| `showNotificationIcon?` | `boolean` | Точка-индикатор уведомления на табе. |
|
|
55
|
+
|
|
56
|
+
### `ITabsProps`
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
extends ITabsExtendedProps
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Prop | Тип | Default | Описание |
|
|
63
|
+
|---|---|---|---|
|
|
64
|
+
| `tabs` | `ITabsItem[]` | — | Массив табов. **Обязателен.** |
|
|
65
|
+
| `selectedId` | `string` | — | Id выбранного таба (от `ITabsExtendedProps`). |
|
|
66
|
+
| `onSelectTab` | `(selectedId: string) => void` | — | Колбэк выбора таба. |
|
|
67
|
+
| `size?` | `EComponentSize` | `MD` | Размер табов и кнопки overflow. |
|
|
68
|
+
| `buttonDropdownAttributes?` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | HTML-атрибуты для `ButtonDropdown`. |
|
|
69
|
+
| `type?` | `ETabsExtendedType` | `TYPE_1` | Тип оформления (от `TabsExtended`). |
|
|
70
|
+
| `children` | `never` | — | Запрещено — структура фиксирована. |
|
|
71
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | — | На корневой `TabsExtended` (role=tablist). |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Поведение
|
|
76
|
+
|
|
77
|
+
### Overflow → Dropdown
|
|
78
|
+
|
|
79
|
+
`TabsExtended.Content.DropdownWrapper` получает массив `dropdownItemsIds`
|
|
80
|
+
табов, не поместившихся в строку. `Tabs` фильтрует `tabs` по этим id и
|
|
81
|
+
передаёт в `ButtonDropdown.options`. Выбранный таб подсвечивается через
|
|
82
|
+
`selected={tabs.find(t => t.id === selectedId)}`.
|
|
83
|
+
|
|
84
|
+
### Keyboard navigation
|
|
85
|
+
|
|
86
|
+
Roving tabindex по `availableToFocusTabId`:
|
|
87
|
+
|
|
88
|
+
- `ArrowLeft` → переключает фокус на предыдущий таб (если не первый в строке —
|
|
89
|
+
`isFirstInlineTab`), `preventDefault` для подавления скролла.
|
|
90
|
+
- `ArrowRight` → следующий таб (если не последний в строке — `isLastInlineTab`).
|
|
91
|
+
- При `onFocus` запоминаются id `prevAvailableToFocusTabId` /
|
|
92
|
+
`nextAvailableToFocusTabId` — на их кнопки навешиваются `prevTabRef` /
|
|
93
|
+
`nextTabRef` для программного `.focus()`.
|
|
94
|
+
|
|
95
|
+
`isFirstInlineTab`/`isLastInlineTab` приходят через render-prop
|
|
96
|
+
`<TabsExtended.Content.Tab>{({ selected, isFirstInlineTab, isLastInlineTab }) => ...}</TabsExtended.Content.Tab>`.
|
|
97
|
+
|
|
98
|
+
### Defaults
|
|
99
|
+
|
|
100
|
+
- `size = EComponentSize.MD`
|
|
101
|
+
- `availableToFocusTabId` инициализируется как `selectedId || tabs[0].id` —
|
|
102
|
+
при пустом `tabs` упадёт (`tabs[0].id`); потребитель должен передавать
|
|
103
|
+
непустой массив.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Инварианты
|
|
108
|
+
|
|
109
|
+
- **`children: never`** — структура зашита (`TabsWrapper` + `DropdownWrapper`).
|
|
110
|
+
Для своей разметки — `TabsExtended`.
|
|
111
|
+
- **`tabs` непустой** — `tabs[0].id` читается без guard'а.
|
|
112
|
+
- **`onSelectTab` обязателен** — таб не сам мутирует `selectedId`, контроллер на потребителе.
|
|
113
|
+
- **`selectedId` управляемый** — `TabsExtended` гарантирует, что повторный выбор того же id не триггерит `onSelectTab` (`if (selectedId !== id)`).
|
|
114
|
+
- **Overflow-кнопка — `ButtonDropdown` с `EButtonDotsTheme.DOTS_SECONDARY`** — менять только осознанно (UX-канон).
|
|
115
|
+
- **Размер кнопки overflow привязан к `size`** через `clsx(styles.tabButtonDropdown, styles[size])`.
|
|
116
|
+
- **`FC, не forwardRef`** — ref не пробрасывается. Если нужен ref на `tablist` — использовать `TabsExtended` напрямую.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Accessibility
|
|
121
|
+
|
|
122
|
+
- Корневой `<div role="tablist">` приходит из `TabsExtended`.
|
|
123
|
+
- На каждой кнопке таба (`TabsExtended.Content.TabButton`) — `role="tab"` + `aria-selected`.
|
|
124
|
+
- Roving tabindex (`tabIndex = availableToFocusTabId === item.id ? 0 : -1`) —
|
|
125
|
+
только один таб в tab-стопе клавиатуры.
|
|
126
|
+
- ArrowLeft/ArrowRight перемещают фокус **только в пределах inline-табов**
|
|
127
|
+
(не в overflow-меню) — для overflow используется родной dropdown.
|
|
128
|
+
- `showNotificationIcon` — визуальный индикатор; для screen-reader потребитель
|
|
129
|
+
должен добавить текстовый намёк в `label` сам.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Связанные компоненты
|
|
134
|
+
|
|
135
|
+
- `TabsExtended` — низкоуровневый базовый компонент со слотами.
|
|
136
|
+
- `TabsLine` — горизонтальные табы без overflow-меню.
|
|
137
|
+
- `ButtonDropdown` (`EButtonDotsTheme.DOTS_SECONDARY`) — кнопка overflow.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Stories
|
|
142
|
+
|
|
143
|
+
Основные истории: `stories/Tabs/Tabs.stories.tsx`
|
|
144
|
+
Примеры: `stories/Tabs/examples/`
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## История изменений
|
|
149
|
+
|
|
150
|
+
| Дата | Изменение |
|
|
151
|
+
|---|---|
|
|
152
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: TabsExtended
|
|
3
|
+
category: Navigation (base)
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [Tabs, TabsLine]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: ""
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# TabsExtended
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Низкоуровневая основа табов: рендерит `<div role="tablist">` с контекстом
|
|
17
|
+
для слотов (`Content`, `TabsWrapper`, `Tab`, `TabButton`, `DropdownWrapper`).
|
|
18
|
+
Сам не задаёт компоновку — потребитель выбирает структуру вручную.
|
|
19
|
+
`TabsWrapper` автоматически вычисляет, какие табы не помещаются в строку,
|
|
20
|
+
и передаёт их id в `DropdownWrapper` для overflow-меню.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужны табы с кастомной разметкой или своим dropdown'ом.
|
|
23
|
+
Для готового решения с `ButtonDropdown` overflow — `Tabs`. Для линейных
|
|
24
|
+
табов без overflow — `TabsLine`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
src/components/TabsExtended/
|
|
32
|
+
├── TabsExtended.tsx # FC + Composition; Provider
|
|
33
|
+
├── TabsExtendedContext.ts # state overflow-распределения
|
|
34
|
+
├── enums.ts # ETabsExtendedType
|
|
35
|
+
├── types.ts # TTabsExtendedOnSelectTab
|
|
36
|
+
├── utils.ts # SIZE→TEXT, TYPE→className maps
|
|
37
|
+
├── components/
|
|
38
|
+
│ ├── TabsExtendedContent.tsx # FC + Composition
|
|
39
|
+
│ ├── TabsExtendedTabsWrapper.tsx # measurement + overflow
|
|
40
|
+
│ ├── TabsExtendedTab.tsx # forwardRef<HTMLSpanElement>
|
|
41
|
+
│ ├── TabsExtendedTabButton.tsx # forwardRef<HTMLButtonElement>
|
|
42
|
+
│ ├── TabsExtendedDropdownWrapper.tsx # render-prop slot
|
|
43
|
+
│ └── TabsExtendedTabContext.ts # { isFakeTab }
|
|
44
|
+
├── styles/
|
|
45
|
+
└── index.ts
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Композиция
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
<TabsExtended selectedId={id} onSelectTab={setId} type={ETabsExtendedType.TYPE_1}>
|
|
54
|
+
<TabsExtended.Content size={EComponentSize.MD}>
|
|
55
|
+
<TabsExtended.Content.TabsWrapper>
|
|
56
|
+
{tabs.map(t => (
|
|
57
|
+
<TabsExtended.Content.Tab key={t.id} id={t.id}>
|
|
58
|
+
{({ selected, isFirstInlineTab, isLastInlineTab }) => (
|
|
59
|
+
<TabsExtended.Content.TabButton selected={selected}>
|
|
60
|
+
{t.label}
|
|
61
|
+
</TabsExtended.Content.TabButton>
|
|
62
|
+
)}
|
|
63
|
+
</TabsExtended.Content.Tab>
|
|
64
|
+
))}
|
|
65
|
+
</TabsExtended.Content.TabsWrapper>
|
|
66
|
+
|
|
67
|
+
<TabsExtended.Content.DropdownWrapper>
|
|
68
|
+
{({ dropdownItemsIds, onSelectTab }) => (/* свой dropdown */)}
|
|
69
|
+
</TabsExtended.Content.DropdownWrapper>
|
|
70
|
+
</TabsExtended.Content>
|
|
71
|
+
</TabsExtended>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Все компоненты экспортируются также напрямую: `TabsExtendedContent`,
|
|
75
|
+
`TabsExtendedTabsWrapper`, `TabsExtendedTab`, `TabsExtendedTabButton`,
|
|
76
|
+
`TabsExtendedDropdownWrapper`.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Публичный API
|
|
81
|
+
|
|
82
|
+
### `ITabsExtendedProps`
|
|
83
|
+
|
|
84
|
+
| Prop | Тип | Default | Описание |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| `selectedId` | `string` | — | Id выбранного таба. |
|
|
87
|
+
| `onSelectTab` | `(id: string) => void` | — | Колбэк выбора. Не вызывается, если `id === selectedId`. |
|
|
88
|
+
| `type?` | `ETabsExtendedType` | `TYPE_1` | Стиль таба. |
|
|
89
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | — | На корневой div (role=tablist). |
|
|
90
|
+
|
|
91
|
+
### `ITabsExtendedContentProps`
|
|
92
|
+
|
|
93
|
+
| Prop | Тип | Default | Описание |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `size?` | `EComponentSize` | `MD` | Размер; маппится на CSS-класс через `createSizeToClassNameMap`. |
|
|
96
|
+
| `...HTMLAttributes<HTMLDivElement>` | — | — | На контейнер. |
|
|
97
|
+
|
|
98
|
+
### `ITabsExtendedTabProps`
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
extends Omit<HTMLAttributes<HTMLSpanElement>, "children">
|
|
102
|
+
{
|
|
103
|
+
id: string;
|
|
104
|
+
children: (props: ITabsExtendedItemProvideProps) => ReactNode;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`ITabsExtendedItemProvideProps`:
|
|
109
|
+
|
|
110
|
+
| Поле | Тип | Описание |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `selected` | `boolean` | `selectedId === id`. |
|
|
113
|
+
| `isFirstInlineTab` | `boolean` | Первый в `inlineItemsIds`. |
|
|
114
|
+
| `isLastInlineTab` | `boolean` | Последний в `inlineItemsIds`. |
|
|
115
|
+
|
|
116
|
+
### `ITabsExtendedButtonProps`
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
extends ButtonHTMLAttributes<HTMLButtonElement>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Prop | Тип | Default | Описание |
|
|
123
|
+
|---|---|---|---|
|
|
124
|
+
| `selected?` | `boolean` | — | Подсветка + `aria-selected`. |
|
|
125
|
+
| `size?` | `EComponentSize` | `MD` | Размер. |
|
|
126
|
+
| `showNotificationIcon?` | `boolean` | — | Рендерит `Badge.Dot`. |
|
|
127
|
+
|
|
128
|
+
Контент рендерится дважды (`Text` default + hover-вариант) для CSS-эффектов.
|
|
129
|
+
|
|
130
|
+
### `ITabsExtendedTabsWrapperProps`
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
extends HTMLAttributes<HTMLDivElement>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Без публичных полей — только htmlDivAttributes.
|
|
137
|
+
|
|
138
|
+
### `ITabsExtendedDropdownWrapperProps`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
142
|
+
{
|
|
143
|
+
children: (props: ITabsExtendedDropdownWrapperProvideProps) => ReactNode;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`ITabsExtendedDropdownWrapperProvideProps`:
|
|
148
|
+
|
|
149
|
+
| Поле | Тип | Описание |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `dropdownItemsIds` | `string[]` | Id табов, не поместившихся inline. |
|
|
152
|
+
| `onSelectTab` | `(id: string) => void` | Колбэк выбора (тот же, что в Context). |
|
|
153
|
+
|
|
154
|
+
### Enums
|
|
155
|
+
|
|
156
|
+
- `ETabsExtendedType`: `TYPE_1` | `TYPE_2`
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Контекст
|
|
161
|
+
|
|
162
|
+
### `TabsExtendedContext`
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
{
|
|
166
|
+
dropdownItemsIds: string[]; // id табов в overflow
|
|
167
|
+
inlineItemsIds: string[]; // id видимых inline
|
|
168
|
+
setDropdownItemsIds(ids): void;
|
|
169
|
+
setInlineItemsIds(ids): void;
|
|
170
|
+
selectedId: string;
|
|
171
|
+
onSelectTab(id): void; // wrapper, не вызывает onSelect повторно
|
|
172
|
+
type: ETabsExtendedType;
|
|
173
|
+
dropdownRef: RefObject<HTMLDivElement>; // на DropdownWrapper, для замера ширины
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### `TabsExtendedTabContext`
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
{ isFakeTab: boolean }
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Включается только внутри fake-копии табов (см. ниже).
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Overflow-механика (`TabsExtendedTabsWrapper`)
|
|
188
|
+
|
|
189
|
+
Рендерит **две копии** табов:
|
|
190
|
+
|
|
191
|
+
1. **`tabsFake`** (скрытый) — внутри `TabsExtendedTabContext.Provider value={isFakeTab: true}`. Табы рендерятся без `id` и без `onClick` (см. `TabsExtendedTab`), `data-` атрибуты у детей удаляются (`stripDataAttributes`). Используется для замера фактической ширины каждого таба.
|
|
192
|
+
2. **`tabsReal`** (видимый) — настоящие табы.
|
|
193
|
+
|
|
194
|
+
`checkVisibleItems` (вызывается в `useLayoutEffect` + по `useResizeDetector` throttle 150ms):
|
|
195
|
+
- Идёт по детям `tabsFake`, читает `getBoundingClientRect`.
|
|
196
|
+
- Если `right > containerRight - 4 - dropdownWidth` → таб уходит в `dropdownIds`.
|
|
197
|
+
- `containerRight - 4` — учёт padding (2px) + gap (2px).
|
|
198
|
+
- Если **хотя бы один** таб реально вылез за `containerRight` (`overflow = true`),
|
|
199
|
+
выставляется `setDropdownItemsIds(dropdownIds)`. Иначе — `[]` (всё помещается).
|
|
200
|
+
- Inline-табы — те, у которых `data-tab-item-id` есть и right укладывается.
|
|
201
|
+
|
|
202
|
+
Размер dropdown отслеживается через `dropdownRef.getBoundingClientRect()` в
|
|
203
|
+
`useLayoutEffect` каждый рендер; при изменении — повторный замер.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## `TabsExtendedTab` поведение
|
|
208
|
+
|
|
209
|
+
- `<span role="presentation" data-tab-item-id={id}>` — оборачивает render-prop children.
|
|
210
|
+
- `id` атрибут НЕ ставится, если `isFakeTab` или таб в `dropdownItemsIds` (избегаем дублирования id в DOM).
|
|
211
|
+
- `onClick` блокируется в fake-режиме.
|
|
212
|
+
- Видимость: при попадании в overflow — добавляется `styles.hidden` (display:none), но таб остаётся в DOM (для пересчёта inline-кадра).
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## `TabsExtendedTabButton` поведение
|
|
217
|
+
|
|
218
|
+
- `<button type="button" role="tab" aria-selected={selected}>`.
|
|
219
|
+
- Внутри — два `<Text>` (default + hover) для CSS-перехода стилей.
|
|
220
|
+
- `Badge.Dot` — при `showNotificationIcon`.
|
|
221
|
+
- Размер текста: SIZE→`ETextSize` через `TABS_EXTENDED_SIZE_TO_TEXT_SIZE_MAP` (LG=B2, MD=B3, SM=B4).
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Инварианты
|
|
226
|
+
|
|
227
|
+
- **`TabsExtended` — `FC` + composition** (`TabsExtended.Content = ...`).
|
|
228
|
+
- **`TabsExtended.Content` — `FC` + composition** (`TabsWrapper`/`Tab`/`TabButton`/`DropdownWrapper`).
|
|
229
|
+
- **`TabsExtendedTab` и `TabsExtendedTabButton` — `forwardRef`** (Span/Button). Не убирать.
|
|
230
|
+
- **`Tab.children` — render-prop**: `(state) => ReactNode`. Не accept'ить плоские дети.
|
|
231
|
+
- **`DropdownWrapper.children` — render-prop**: получает `dropdownItemsIds` + `onSelectTab`.
|
|
232
|
+
- **`TabsWrapper` рендерит две копии children** (fake + real). Любой таб должен корректно работать как fake (без `id`, без onClick).
|
|
233
|
+
- **`stripDataAttributes` чистит только `data-*` пропсы у прямых children** (на 1 уровень). Глубже — потребитель ответственен.
|
|
234
|
+
- **`onSelectTab` в Context — wrapper**, который НЕ вызывает props.onSelectTab при `id === selectedId`. Дедупликация выбора.
|
|
235
|
+
- **`dropdownRef` — на `DropdownWrapper`** (не на самой кнопке dropdown'а потребителя). Используется для замера зарезервированной ширины.
|
|
236
|
+
- **`role="tablist"` / `role="tab"` / `aria-selected`** — обязательная семантика; не переопределять без причины.
|
|
237
|
+
- **`ETabsExtendedType` экспортируется** — часть публичного API.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Accessibility
|
|
242
|
+
|
|
243
|
+
- `<div role="tablist">` на корне.
|
|
244
|
+
- `<button role="tab" aria-selected>` на каждой кнопке.
|
|
245
|
+
- Управление фокусом (roving tabindex, ArrowLeft/Right) — НЕ встроено;
|
|
246
|
+
потребитель реализует сам (см. `Tabs`).
|
|
247
|
+
- `<TabsExtendedTab>` — `role="presentation"` (это обёртка, не tab сам по себе).
|
|
248
|
+
- Таб в overflow не имеет id и убран из таб-ордера через `display:none` —
|
|
249
|
+
попадает в кастомный dropdown потребителя (там ARIA — на потребителе).
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Связанные компоненты
|
|
254
|
+
|
|
255
|
+
- `Tabs` — готовый таб-бар поверх TabsExtended с `ButtonDropdown` overflow.
|
|
256
|
+
- `TabsLine` — горизонтальные линейные табы (без overflow, своя реализация).
|
|
257
|
+
- `Badge.Dot` — индикатор в `TabButton`.
|
|
258
|
+
- `Typography.Text` — текст лейбла.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Stories
|
|
263
|
+
|
|
264
|
+
Отдельных stories нет — демонстрируется через `Tabs` и собственные примеры
|
|
265
|
+
в `src/components/TabsExtended/components/index.ts` (`Type1Example`, `Type2Example`, `PlaygroundExample`).
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## История изменений
|
|
270
|
+
|
|
271
|
+
| Дата | Изменение |
|
|
272
|
+
|---|---|
|
|
273
|
+
| 2026-04-18 | Создан документ |
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: TabsLine
|
|
3
|
+
category: Navigation
|
|
4
|
+
figma-node: ""
|
|
5
|
+
figma-file: ""
|
|
6
|
+
related: [Tabs, TabsExtended, MobileView, Dropdown]
|
|
7
|
+
tokens: []
|
|
8
|
+
stories: stories/TabsLine/TabsLine.stories.tsx
|
|
9
|
+
version: "1.0"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# TabsLine
|
|
13
|
+
|
|
14
|
+
## Назначение
|
|
15
|
+
|
|
16
|
+
Линейные табы — кнопки, разделённые тонкой линией снизу, без текстового
|
|
17
|
+
содержимого внутри. Отдельная реализация (НЕ поверх `TabsExtended`).
|
|
18
|
+
Адаптивная: desktop рендерит `TabsLineDesktop` с overflow в `Dropdown`,
|
|
19
|
+
mobile — горизонтальный скролл (`TabsLineMobile`). Переключение через
|
|
20
|
+
`MobileView` по media query.
|
|
21
|
+
|
|
22
|
+
Используй когда: нужны "линейные" табы Sber-стиля с поддержкой свыше
|
|
23
|
+
нескольких табов и автосворачиванием в dropdown. Для табов с кастомным
|
|
24
|
+
overflow — `Tabs` (общий стиль) или `TabsExtended` (низкоуровневый).
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Файловая структура
|
|
29
|
+
|
|
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
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
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` контейнера. |
|
|
80
|
+
|
|
81
|
+
### `ITabsLineBaseProps`
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "children">
|
|
85
|
+
{ tabs; onChangeTab; selectedId; size? }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Общая база Desktop/Mobile вариантов.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Поведение Desktop (`TabsLineDesktop`)
|
|
93
|
+
|
|
94
|
+
### Распределение inline / dropdown
|
|
95
|
+
|
|
96
|
+
`useEffect` на `[tabs, maxVisible]`:
|
|
97
|
+
- Если `maxVisible` задан И `i + 1 >= maxVisible` И `tabs.length > maxVisible` → `dropdownTabs`. Иначе → `inlineTabs`.
|
|
98
|
+
- Сравнение через `isEqual` (lodash) перед `setState` — избегаем лишних рендеров.
|
|
99
|
+
|
|
100
|
+
`maxVisible` без overflow (когда `tabs.length <= maxVisible`) — все остаются inline.
|
|
101
|
+
|
|
102
|
+
### Overflow-кнопка (`TabsLineDropdown`)
|
|
103
|
+
|
|
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`.
|
|
110
|
+
|
|
111
|
+
### Keyboard navigation
|
|
112
|
+
|
|
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`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Поведение Mobile (`TabsLineMobile`)
|
|
121
|
+
|
|
122
|
+
- Простая горизонтальная лента (`overflow-x: auto` через CSS).
|
|
123
|
+
- НЕТ overflow-меню — табы прокручиваются.
|
|
124
|
+
- НЕ принимает `maxVisible`, `paddingX`, `withSeparator`, `size` — игнорируются на mobile.
|
|
125
|
+
- Возвращает `null` при пустом `tabs`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## `TabsLineItem` поведение
|
|
130
|
+
|
|
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` таба!).
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Инварианты
|
|
140
|
+
|
|
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`) — это разные подсистемы.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Accessibility
|
|
156
|
+
|
|
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).
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Связанные компоненты
|
|
168
|
+
|
|
169
|
+
- `Tabs` — табы с overflow через `ButtonDropdown`.
|
|
170
|
+
- `TabsExtended` — низкоуровневая альтернатива (composition slots).
|
|
171
|
+
- `MobileView` — переключение desktop/mobile.
|
|
172
|
+
- `Dropdown` / `DropdownList` — overflow-меню в desktop-варианте.
|
|
173
|
+
- `Badge.Dot` — индикатор уведомлений.
|
|
174
|
+
- `Typography.Text` — текст лейбла.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Stories
|
|
179
|
+
|
|
180
|
+
Основные истории: `stories/TabsLine/TabsLine.stories.tsx`
|
|
181
|
+
Примеры: `stories/TabsLine/examples/`
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## История изменений
|
|
186
|
+
|
|
187
|
+
| Дата | Изменение |
|
|
188
|
+
|---|---|
|
|
189
|
+
| 2026-04-18 | Создан документ |
|