@sberbusiness/triplex-next-mcp-server 0.24.0-beta2 → 0.36.0-beta0
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 +10 -0
- package/bundle/components/BodyPage.md +186 -0
- package/bundle/components/Button.md +90 -85
- package/bundle/components/ButtonDropdown.md +28 -70
- package/bundle/components/ButtonIcon.md +35 -48
- package/bundle/components/Checkbox.md +43 -122
- package/bundle/components/CheckboxXGroup.md +109 -46
- package/bundle/components/CheckboxYGroup.md +77 -49
- package/bundle/components/CollapsibleTree.md +581 -0
- package/bundle/components/CollapsibleTreeExtended.md +418 -0
- package/bundle/components/EmptyView.md +14 -15
- package/bundle/components/FooterPage.md +203 -0
- package/bundle/components/Gap.md +115 -57
- package/bundle/components/HeaderPage.md +226 -0
- package/bundle/components/HelpBox.md +45 -84
- package/bundle/components/ImageGallery.md +423 -0
- package/bundle/components/ImageGalleryExtended.md +606 -0
- package/bundle/components/List.md +194 -186
- package/bundle/components/ListItem.md +251 -0
- package/bundle/components/ListItemControlsButton.md +103 -0
- package/bundle/components/ListItemControlsButtonDropdown.md +107 -0
- package/bundle/components/ListItemSelectable.md +99 -0
- package/bundle/components/ListItemTable.md +390 -0
- package/bundle/components/ListMaster.md +877 -156
- package/bundle/components/ListMasterFooter.md +80 -0
- package/bundle/components/ListMasterHeader.md +86 -0
- package/bundle/components/ListSortable.md +153 -0
- package/bundle/components/ListSortableItem.md +175 -0
- package/bundle/components/ModalWindow.md +427 -237
- package/bundle/components/MonthYearField.md +145 -109
- package/bundle/components/Notification.md +625 -155
- package/bundle/components/Overlay.md +271 -170
- package/bundle/components/Page.md +283 -186
- package/bundle/components/Pagination.md +206 -229
- package/bundle/guides/codestyle.md +38 -11
- package/bundle/guides/commits.md +1 -2
- package/bundle/guides/context.md +34 -30
- package/bundle/guides/stories.md +110 -40
- package/bundle/guides/template.md +3 -7
- package/bundle/guides/tests.md +6 -2
- package/bundle/manifest.json +194 -24
- package/bundle/release-notes/1.26.0.mdx +33 -2
- package/bundle/release-notes/1.27.0.mdx +43 -0
- package/bundle/release-notes/1.28.0.mdx +25 -0
- package/bundle/release-notes/1.29.0.mdx +47 -0
- package/bundle/release-notes/1.30.0.mdx +56 -0
- package/bundle/release-notes/1.31.0.mdx +52 -0
- package/bundle/release-notes/1.32.0.mdx +35 -0
- package/bundle/release-notes/1.33.0.mdx +61 -0
- package/bundle/release-notes/1.34.0.mdx +35 -0
- package/bundle/release-notes/1.35.0.mdx +21 -0
- package/bundle/release-notes/1.35.1.mdx +11 -0
- package/bundle/release-notes/1.36.0.mdx +21 -0
- package/bundle/release-notes/1.37.0.mdx +30 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,6 +80,9 @@ npm run inspect
|
|
|
80
80
|
# Обновить bundle/ из последнего релиза triplex-next
|
|
81
81
|
npm run fetch-bundle
|
|
82
82
|
|
|
83
|
+
# Обновить только AI-ready компоненты из bundle, остальные данные сохранить
|
|
84
|
+
npm run fetch-bundle -- --merge --file path/to/mcp-data.json
|
|
85
|
+
|
|
83
86
|
# Обновить bundle/icons.json из @sberbusiness/icons-next
|
|
84
87
|
npm run fetch-icons
|
|
85
88
|
|
|
@@ -111,6 +114,13 @@ bundle/
|
|
|
111
114
|
содержимое в поддиректории выше. Транзиентный `mcp-data.json` удаляется
|
|
112
115
|
после раскладки.
|
|
113
116
|
|
|
117
|
+
По умолчанию дерево перезаписывается целиком. Флаг `--merge` включает
|
|
118
|
+
инкрементальное обновление: компоненты, у которых в bundle есть данные
|
|
119
|
+
(`raw !== null`), перезаписываются, остальные сохраняют текущую документацию
|
|
120
|
+
и записи в `manifest.json`. Гайды и release notes из bundle перезаписываются,
|
|
121
|
+
отсутствующие в bundle — сохраняются. Режим полезен, пока AI-документация
|
|
122
|
+
в triplex-next выверяется покомпонентно.
|
|
123
|
+
|
|
114
124
|
В triplex-next bundle собирается скриптом `scripts/generateMcpData.ts`
|
|
115
125
|
(npm-команда `generateMcpData`) и автоматически прикладывается к релиз-ассетам
|
|
116
126
|
через `.github/workflows/release.yml`.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: BodyPage
|
|
3
|
+
category: Page
|
|
4
|
+
related: [Page, HeaderPage, FooterPage, Body, Island, LightBox]
|
|
5
|
+
tokens: []
|
|
6
|
+
stories: stories/Page/BodyPage/BodyPage.stories.tsx
|
|
7
|
+
version: "1.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# BodyPage
|
|
11
|
+
|
|
12
|
+
## Назначение
|
|
13
|
+
|
|
14
|
+
Тело страницы `Page`. Доступен как статическое свойство `Page.Body`. Контейнер для основного
|
|
15
|
+
контента страницы между `Page.Header` и `Page.Footer`.
|
|
16
|
+
|
|
17
|
+
Используй когда: нужно разместить основной контент внутри `Page` — как в карточке (Island), так и без неё.
|
|
18
|
+
Не используй когда: нужен заголовок или футер страницы — для этого есть `Page.Header` / `Page.Footer`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Варианты и props
|
|
23
|
+
|
|
24
|
+
Публичный API — discriminated union по `type`: `IBodyPageTypeFirstProps | IBodyPageTypeSecondProps`.
|
|
25
|
+
Оба интерфейса расширяют `IBodyProps` (≈ `React.HTMLAttributes<HTMLDivElement>`).
|
|
26
|
+
|
|
27
|
+
### Обязательные props
|
|
28
|
+
|
|
29
|
+
| Prop | Тип | Описание |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `type` | `EBodyPageType` | `FIRST` — контент оборачивается в `Island` (карточка с белым фоном и тенью). `SECOND` — контент без карточки. |
|
|
32
|
+
| `children` | `React.ReactNode` | Контент тела страницы. |
|
|
33
|
+
|
|
34
|
+
### Опциональные props
|
|
35
|
+
|
|
36
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `verticalMargin` | `EBodyPageVerticalMargin` | `LARGE` | Вертикальные отступы: `LARGE` = 24px (16px на узких экранах), `SMALL` = 16px (8px на узких). В LightBox используется `SMALL`. |
|
|
39
|
+
| `size` | `EComponentSize` | — | Размер острова (Island). **Доступен только для `type=FIRST`**; для `type=SECOND` типизирован как `never`. |
|
|
40
|
+
|
|
41
|
+
### Ограничения по типам
|
|
42
|
+
|
|
43
|
+
- `size` доступен только при `type=FIRST` (в `SECOND` контент не оборачивается в Island, поэтому `size?: never`).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Дизайн-токены
|
|
48
|
+
|
|
49
|
+
Собственных CSS-переменных нет. Внешний вид карточки наследуется от `Island` (`EIslandType.TYPE_1`).
|
|
50
|
+
Вертикальные отступы заданы фиксированными значениями в `styles/BodyPage.module.less`
|
|
51
|
+
(24/16px и адаптив 16/8px на `@screen-sm-max`).
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Инварианты
|
|
56
|
+
|
|
57
|
+
- `forwardRef<HTMLDivElement>` — не убирать. Ref всегда указывает на корневой элемент
|
|
58
|
+
(Island для FIRST, Body для SECOND).
|
|
59
|
+
- `displayName = "BodyPage"` — не менять.
|
|
60
|
+
- Discriminated union по `type` и значения `EBodyPageType` / `EBodyPageVerticalMargin` — часть
|
|
61
|
+
публичного API, менять нельзя.
|
|
62
|
+
- Экспорты `BodyPage`, `IBodyPageTypeFirstProps`, `IBodyPageTypeSecondProps` идут в barrel
|
|
63
|
+
`src/components/Page/index.ts` — сохранять.
|
|
64
|
+
- `verticalMargin` по умолчанию `LARGE` — менять дефолт нельзя без обсуждения.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Accessibility
|
|
69
|
+
|
|
70
|
+
Особого a11y-поведения нет — это структурный контейнер. Семантику и aria-атрибуты задаёт
|
|
71
|
+
потребитель через проброшенные HTML-атрибуты (`...rest` уходит в `Body`).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Связанные компоненты
|
|
76
|
+
|
|
77
|
+
- `Page` — родитель; `BodyPage` доступен как `Page.Body`.
|
|
78
|
+
- `HeaderPage` (`Page.Header`), `FooterPage` (`Page.Footer`) — соседи внутри `Page`.
|
|
79
|
+
- `Body` — внутренний контейнер контента (рендерится в обоих типах).
|
|
80
|
+
- `Island` — карточка-обёртка для `type=FIRST`.
|
|
81
|
+
- `LightBox` — типичное место использования с `verticalMargin=SMALL`.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Stories
|
|
86
|
+
|
|
87
|
+
Основные истории: `stories/Page/BodyPage/BodyPage.stories.tsx`
|
|
88
|
+
Файлы примеров: `stories/Page/BodyPage/examples/`
|
|
89
|
+
|
|
90
|
+
| Story | Example file | Что демонстрирует |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `Playground` | `Playground.tsx` | Интерактивный контроль `type` и `verticalMargin` |
|
|
93
|
+
| `Default` | `Default.tsx` | Минимальный пример (FIRST, контент в карточке) |
|
|
94
|
+
| `Types` | `Types.tsx` | Сравнение типов FIRST и SECOND |
|
|
95
|
+
| `VerticalMargins` | `VerticalMargins.tsx` | Отступы LARGE (24px) и SMALL (16px) |
|
|
96
|
+
| `VisualTests` | `VisualTests.tsx` | Матрица type × verticalMargin для скриншот-регрессии |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## История изменений
|
|
101
|
+
|
|
102
|
+
| Дата | Изменение |
|
|
103
|
+
|---|---|
|
|
104
|
+
| 2026-06-18 | Создан документ. AI-рефакторинг (clsx order, JSDoc), unit-тесты, миграция stories на modern pattern. |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Примеры использования
|
|
109
|
+
|
|
110
|
+
### Default
|
|
111
|
+
|
|
112
|
+
`stories/Page/BodyPage/examples/Default.tsx`
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import React from "react";
|
|
116
|
+
import { Page, EBodyPageType, Text, ETextSize, EFontType } from "@sberbusiness/triplex-next";
|
|
117
|
+
|
|
118
|
+
export const Default = () => (
|
|
119
|
+
<Page.Body type={EBodyPageType.FIRST}>
|
|
120
|
+
<Text tag="div" size={ETextSize.B2} type={EFontType.PRIMARY}>
|
|
121
|
+
BodyPage типа FIRST оборачивает контент в Island (карточку) с белым фоном и тенью.
|
|
122
|
+
</Text>
|
|
123
|
+
</Page.Body>
|
|
124
|
+
);
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Types
|
|
128
|
+
|
|
129
|
+
`stories/Page/BodyPage/examples/Types.tsx`
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import React from "react";
|
|
133
|
+
import { Page, EBodyPageType, Text, ETextSize, EFontType } from "@sberbusiness/triplex-next";
|
|
134
|
+
|
|
135
|
+
export const Types = () => (
|
|
136
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
137
|
+
<div>
|
|
138
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: 700 }}>FIRST</div>
|
|
139
|
+
<Page.Body type={EBodyPageType.FIRST}>
|
|
140
|
+
<Text tag="div" size={ETextSize.B2} type={EFontType.PRIMARY}>
|
|
141
|
+
Тип FIRST оборачивает контент в Island (карточку) с белым фоном и тенью.
|
|
142
|
+
</Text>
|
|
143
|
+
</Page.Body>
|
|
144
|
+
</div>
|
|
145
|
+
<div>
|
|
146
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: 700 }}>SECOND</div>
|
|
147
|
+
<Page.Body type={EBodyPageType.SECOND}>
|
|
148
|
+
<Text tag="div" size={ETextSize.B2} type={EFontType.PRIMARY}>
|
|
149
|
+
Тип SECOND отображает контент без карточки.
|
|
150
|
+
</Text>
|
|
151
|
+
</Page.Body>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### VerticalMargins
|
|
158
|
+
|
|
159
|
+
`stories/Page/BodyPage/examples/VerticalMargins.tsx`
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
import React from "react";
|
|
163
|
+
import { Page, EBodyPageType, EBodyPageVerticalMargin, Text, ETextSize, EFontType } from "@sberbusiness/triplex-next";
|
|
164
|
+
|
|
165
|
+
export const VerticalMargins = () => (
|
|
166
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
|
|
167
|
+
<div>
|
|
168
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: 700 }}>LARGE (24px)</div>
|
|
169
|
+
<Page.Body type={EBodyPageType.FIRST} verticalMargin={EBodyPageVerticalMargin.LARGE}>
|
|
170
|
+
<Text tag="div" size={ETextSize.B2} type={EFontType.PRIMARY}>
|
|
171
|
+
Отступы сверху и снизу 24px (значение по умолчанию).
|
|
172
|
+
</Text>
|
|
173
|
+
</Page.Body>
|
|
174
|
+
</div>
|
|
175
|
+
<div>
|
|
176
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: 700 }}>SMALL (16px)</div>
|
|
177
|
+
<Page.Body type={EBodyPageType.FIRST} verticalMargin={EBodyPageVerticalMargin.SMALL}>
|
|
178
|
+
<Text tag="div" size={ETextSize.B2} type={EFontType.PRIMARY}>
|
|
179
|
+
Уменьшенные отступы сверху и снизу 16px. Используется в LightBox.
|
|
180
|
+
</Text>
|
|
181
|
+
</Page.Body>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
```
|
|
186
|
+
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: Button
|
|
3
3
|
category: Buttons
|
|
4
|
-
figma-node: "1-328"
|
|
5
|
-
figma-file: "https://www.figma.com/design/MeAqaaLV9acAgoBOkZC9C2/-W--LIB-DSN-01-Components-DCB-Next-v.1.3.0"
|
|
6
4
|
related: [ButtonIcon, ButtonDropdown, ButtonDropdownExtended]
|
|
7
5
|
tokens:
|
|
8
6
|
- --triplex-next-Button-General_Background_Default
|
|
@@ -64,42 +62,17 @@ version: "1.0"
|
|
|
64
62
|
|
|
65
63
|
---
|
|
66
64
|
|
|
67
|
-
## Файловая структура
|
|
68
|
-
|
|
69
|
-
```text
|
|
70
|
-
src/components/Button/
|
|
71
|
-
├── Button.tsx # Основной компонент, union type TButtonProps
|
|
72
|
-
├── ButtonBase.tsx # Базовый <button>, extends HTML attrs
|
|
73
|
-
├── ButtonIcon.tsx # Иконочная кнопка (squircle / circle)
|
|
74
|
-
├── ButtonDropdown.tsx # Кнопка с выпадающим меню
|
|
75
|
-
├── ButtonDropdownExtended.tsx # Контейнер с кастомным dropdown
|
|
76
|
-
├── enums.ts # EButtonTheme, EButtonDotsTheme, EButtonIconShape
|
|
77
|
-
├── index.ts # Barrel exports
|
|
78
|
-
└── styles/
|
|
79
|
-
├── Button.module.less # Размеры, block, loading, base layout
|
|
80
|
-
├── ButtonGeneral.module.less # Тема GENERAL
|
|
81
|
-
├── ButtonSecondary.module.less
|
|
82
|
-
├── ButtonSecondaryLight.module.less
|
|
83
|
-
├── ButtonDanger.module.less
|
|
84
|
-
├── ButtonLink.module.less # Тема LINK — нет min-width, нет bg
|
|
85
|
-
├── ButtonIcon.module.less # Стили ButtonIcon
|
|
86
|
-
├── ButtonDropdown.module.less
|
|
87
|
-
└── ButtonDropdownExtended.module.less
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
65
|
## Варианты и props
|
|
93
66
|
|
|
94
67
|
### Темы (`EButtonTheme`)
|
|
95
68
|
|
|
96
|
-
| Значение | Описание |
|
|
97
|
-
|
|
98
|
-
| `GENERAL` | Основной стиль (primary) |
|
|
99
|
-
| `SECONDARY` | Вторичный |
|
|
100
|
-
| `SECONDARY_LIGHT` | Вторичный светлый (для тёмных фонов) |
|
|
101
|
-
| `DANGER` | Деструктивное действие |
|
|
102
|
-
| `LINK` | Текстовая ссылка |
|
|
69
|
+
| Значение | Описание |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `GENERAL` | Основной стиль (primary) |
|
|
72
|
+
| `SECONDARY` | Вторичный |
|
|
73
|
+
| `SECONDARY_LIGHT` | Вторичный светлый (для тёмных фонов) |
|
|
74
|
+
| `DANGER` | Деструктивное действие |
|
|
75
|
+
| `LINK` | Текстовая ссылка |
|
|
103
76
|
|
|
104
77
|
### Размеры (`EComponentSize`)
|
|
105
78
|
|
|
@@ -109,17 +82,22 @@ src/components/Button/
|
|
|
109
82
|
| `MD` | 40px | 0 20px | 8px | 14px |
|
|
110
83
|
| `LG` | 56px | 0 24px | 10px | 16px |
|
|
111
84
|
|
|
112
|
-
###
|
|
85
|
+
### Обязательные props (`Button`)
|
|
86
|
+
|
|
87
|
+
| Prop | Тип | Описание |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `theme` | `EButtonTheme` | Визуальный стиль |
|
|
90
|
+
| `size` | `EComponentSize` | Размер |
|
|
91
|
+
|
|
92
|
+
### Опциональные props (`Button`)
|
|
113
93
|
|
|
114
|
-
| Prop | Тип |
|
|
94
|
+
| Prop | Тип | По умолчанию | Описание |
|
|
115
95
|
|---|---|---|---|
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `children` | `React.ReactNode` | Нет | Текст кнопки |
|
|
122
|
-
| `...HTMLButtonAttributes` | — | — | Все стандартные атрибуты `<button>` |
|
|
96
|
+
| `block` | `boolean` | `false` | Полноширинный режим |
|
|
97
|
+
| `loading` | `boolean` | `false` | Состояние загрузки: скрывает контент через `visibility: hidden`, показывает спиннер, ставит `tabIndex={-1}` |
|
|
98
|
+
| `icon` | `React.ReactElement` | — | Иконка слева от текста; если без `children` — кнопка становится квадратной (icon-only) |
|
|
99
|
+
| `children` | `React.ReactNode` | — | Текст кнопки |
|
|
100
|
+
| `...HTMLButtonAttributes` | — | — | Все стандартные атрибуты `<button>`, включая `aria-expanded` (см. Accessibility) |
|
|
123
101
|
|
|
124
102
|
### Props компонента `ButtonIcon`
|
|
125
103
|
|
|
@@ -129,45 +107,11 @@ src/components/Button/
|
|
|
129
107
|
| `active` | `boolean` | `false` | Визуальное активное состояние |
|
|
130
108
|
| `...HTMLButtonAttributes` | — | — | Включая `disabled` |
|
|
131
109
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## Ключевые особенности реализации
|
|
135
|
-
|
|
136
|
-
### Иконка без текста
|
|
137
|
-
Если передан `icon` без `children` — добавляется класс `.icon`, который делает кнопку квадратной:
|
|
138
|
-
```tsx
|
|
139
|
-
// Button.tsx — логика определения icon-only режима
|
|
140
|
-
const iconOnly = Boolean(icon) && !children;
|
|
141
|
-
<button className={clsx(styles.button, { [styles.icon]: iconOnly })}>
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Состояние загрузки
|
|
145
|
-
- `loading` скрывает контент через `visibility: hidden` (не `display: none`) — сохраняет размеры,
|
|
146
|
-
- устанавливает `tabIndex={-1}` — кнопка недоступна с клавиатуры
|
|
147
|
-
- Тема лоадера: `SECONDARY` и `SECONDARY_LIGHT` → `ELoaderSmallTheme.BRAND`, остальные → `ELoaderSmallTheme.NEUTRAL`
|
|
110
|
+
### Ограничения по темам
|
|
148
111
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<Button aria-expanded={true} ...> → добавляет CSS-классы .expanded и .active
|
|
153
|
-
```
|
|
154
|
-
Отдельного prop `expanded` нет — используй стандартный HTML-атрибут.
|
|
155
|
-
|
|
156
|
-
### Тема LINK
|
|
157
|
-
- Нет `min-width`
|
|
158
|
-
- `background: transparent`
|
|
159
|
-
- `padding` по вертикали отличается от других тем (меньше)
|
|
160
|
-
- `block`, `loading`, `icon` — тип `never` в интерфейсе, TypeScript запрещает передачу
|
|
161
|
-
|
|
162
|
-
---
|
|
163
|
-
|
|
164
|
-
## Accessibility
|
|
165
|
-
|
|
166
|
-
- Нативный `<button>` — полная поддержка клавиатуры (Tab, Enter, Space) без дополнительных атрибутов.
|
|
167
|
-
- **`ButtonIcon`**: иконочная кнопка без видимого текста — потребитель **обязан** передать `aria-label` через `...rest`. Библиотека мультиязычная, текст не хардкодится.
|
|
168
|
-
- **`loading`**: устанавливает `tabIndex={-1}` — кнопка исключается из tab-порядка. Если нужна доступность состояния загрузки для скринридеров, потребитель добавляет `aria-busy="true"` через props.
|
|
169
|
-
- **`aria-expanded`**: управляет визуальным состоянием "раскрыто" (добавляет CSS-классы `.expanded`, `.active`). Передаётся потребителем при использовании кнопки как триггера dropdown.
|
|
170
|
-
- Фокус-стиль через `:focus-visible` — виден только при клавиатурной навигации.
|
|
112
|
+
- `block`, `loading`, `icon` недоступны для темы `LINK` — тип `never` в интерфейсе, TypeScript запрещает передачу.
|
|
113
|
+
- Тема `LINK` не имеет `min-width`, фона (`background: transparent`) и использует уменьшенный вертикальный `padding`.
|
|
114
|
+
- Тема лоадера (`loading`) для `SECONDARY` и `SECONDARY_LIGHT` — `ELoaderSmallTheme.BRAND`, для остальных — `ELoaderSmallTheme.NEUTRAL`.
|
|
171
115
|
|
|
172
116
|
---
|
|
173
117
|
|
|
@@ -175,12 +119,12 @@ const iconOnly = Boolean(icon) && !children;
|
|
|
175
119
|
|
|
176
120
|
Паттерн: `--triplex-next-Button-{Theme}_{Property}_{State}`
|
|
177
121
|
|
|
178
|
-
Каждая тема (кроме LINK) имеет полный набор:
|
|
122
|
+
Каждая тема (кроме `LINK`) имеет полный набор:
|
|
179
123
|
- `_Background_Default/Hover/Active/Disabled`
|
|
180
124
|
- `_Color_Default/Hover/Active/Disabled`
|
|
181
125
|
- `_Shadow_Focus`
|
|
182
126
|
|
|
183
|
-
Тема LINK — только `_Color_*` и `_Shadow_Focus` (нет фона).
|
|
127
|
+
Тема `LINK` — только `_Color_*` и `_Shadow_Focus` (нет фона).
|
|
184
128
|
|
|
185
129
|
При добавлении нового визуального состояния нужен новый токен в `src/generated/themesCssVariables.css`.
|
|
186
130
|
|
|
@@ -192,7 +136,17 @@ const iconOnly = Boolean(icon) && !children;
|
|
|
192
136
|
- **`TButtonProps` union type** — должен включать все тематические интерфейсы. При добавлении новой темы — добавь интерфейс в union.
|
|
193
137
|
- **`EButtonTheme` значения** — не переименовывать. Это публичное API.
|
|
194
138
|
- **`EComponentSize`** — общий enum из `src/enums/EComponentSize`, не делай локальный аналог.
|
|
195
|
-
- **Размеры (28px/40px/56px)** — не менять без согласования с дизайнером, другие компоненты ориентируются на эти размеры.
|
|
139
|
+
- **Размеры (28px / 40px / 56px)** — не менять без согласования с дизайнером, другие компоненты ориентируются на эти размеры.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Accessibility
|
|
144
|
+
|
|
145
|
+
- Нативный `<button>` — полная поддержка клавиатуры (Tab, Enter, Space) без дополнительных атрибутов.
|
|
146
|
+
- **`ButtonIcon`**: иконочная кнопка без видимого текста — потребитель **обязан** передать `aria-label` через `...rest`. Библиотека мультиязычная, текст не хардкодится.
|
|
147
|
+
- **`loading`**: устанавливает `tabIndex={-1}` — кнопка исключается из tab-порядка. Если нужна доступность состояния загрузки для скринридеров, потребитель добавляет `aria-busy="true"` через props.
|
|
148
|
+
- **`aria-expanded`**: управляет визуальным состоянием «раскрыто» (добавляет CSS-классы `.expanded`, `.active`). Передаётся потребителем при использовании кнопки как триггера dropdown. Отдельного prop `expanded` нет — используй стандартный HTML-атрибут.
|
|
149
|
+
- Фокус-стиль через `:focus-visible` — виден только при клавиатурной навигации.
|
|
196
150
|
|
|
197
151
|
---
|
|
198
152
|
|
|
@@ -208,7 +162,7 @@ const iconOnly = Boolean(icon) && !children;
|
|
|
208
162
|
|
|
209
163
|
## Stories
|
|
210
164
|
|
|
211
|
-
`stories/Buttons/Button.stories.tsx`
|
|
165
|
+
Основные истории: `stories/Buttons/Button.stories.tsx`
|
|
212
166
|
Файлы примеров: `stories/Buttons/examples/Button/`
|
|
213
167
|
|
|
214
168
|
| Story | Example file | Что демонстрирует |
|
|
@@ -219,7 +173,9 @@ const iconOnly = Boolean(icon) && !children;
|
|
|
219
173
|
| `Sizes` | `SizesExample.tsx` | SM / MD / LG для всех тем |
|
|
220
174
|
| `Themes` | `ThemesExample.tsx` | Все пять тем |
|
|
221
175
|
| `WithIcon` | `WithIconExample.tsx` | Icon-only и text+icon |
|
|
176
|
+
| `WithNotificationIcon` | `WithNotificationIconExample.tsx` | Иконка с индикатором уведомления |
|
|
222
177
|
| `BlockMode` | `BlockModeExample.tsx` | Полноширинный режим |
|
|
178
|
+
| `TextWithIcon` | `TextWithIconExample.tsx` | Текст вместе с иконкой |
|
|
223
179
|
|
|
224
180
|
---
|
|
225
181
|
|
|
@@ -228,6 +184,7 @@ const iconOnly = Boolean(icon) && !children;
|
|
|
228
184
|
| Дата | Изменение |
|
|
229
185
|
|---|---|
|
|
230
186
|
| 2026-03-31 | Создан документ (пилот AI-Ready) |
|
|
187
|
+
| 2026-04-27 | Приведён в соответствие с `docs/ai/template-ai.md`: убраны секции «Файловая структура» и «Ключевые особенности реализации», переструктурированы props, добавлена колонка `Example file` в таблице Stories |
|
|
231
188
|
|
|
232
189
|
---
|
|
233
190
|
|
|
@@ -475,6 +432,27 @@ export const WithIconExample = () => (
|
|
|
475
432
|
);
|
|
476
433
|
```
|
|
477
434
|
|
|
435
|
+
### WithNotificationIcon
|
|
436
|
+
|
|
437
|
+
`stories/Buttons/examples/Button/WithNotificationIconExample.tsx`
|
|
438
|
+
|
|
439
|
+
```tsx
|
|
440
|
+
import React from "react";
|
|
441
|
+
import { Badge, Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
442
|
+
import { BellStrokeNavIcon20 } from "@sberbusiness/icons-next";
|
|
443
|
+
|
|
444
|
+
export const WithNotificationIconExample = () => (
|
|
445
|
+
<div style={{ position: "relative", display: "inline-flex" }}>
|
|
446
|
+
<Button
|
|
447
|
+
icon={<BellStrokeNavIcon20 paletteIndex={0} />}
|
|
448
|
+
size={EComponentSize.MD}
|
|
449
|
+
theme={EButtonTheme.SECONDARY}
|
|
450
|
+
/>
|
|
451
|
+
<Badge.Dot size={EComponentSize.MD} style={{ position: "absolute", top: 4, right: 4 }} />
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
```
|
|
455
|
+
|
|
478
456
|
### BlockMode
|
|
479
457
|
|
|
480
458
|
`stories/Buttons/examples/Button/BlockModeExample.tsx`
|
|
@@ -508,3 +486,30 @@ export const BlockModeExample = () => (
|
|
|
508
486
|
);
|
|
509
487
|
```
|
|
510
488
|
|
|
489
|
+
### TextWithIcon
|
|
490
|
+
|
|
491
|
+
`stories/Buttons/examples/Button/TextWithIconExample.tsx`
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
import React from "react";
|
|
495
|
+
import { DefaulticonStrokePrdIcon16, DefaulticonStrokePrdIcon20 } from "@sberbusiness/icons-next";
|
|
496
|
+
import { Button, EButtonTheme, EComponentSize } from "@sberbusiness/triplex-next";
|
|
497
|
+
|
|
498
|
+
export const TextWithIconExample = () => (
|
|
499
|
+
<div style={{ display: "flex", alignItems: "flex-start", gap: 16 }}>
|
|
500
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.SM}>
|
|
501
|
+
<DefaulticonStrokePrdIcon16 paletteIndex={5} />
|
|
502
|
+
<span style={{ marginLeft: 8 }}>Button text</span>
|
|
503
|
+
</Button>
|
|
504
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.MD}>
|
|
505
|
+
<DefaulticonStrokePrdIcon20 paletteIndex={5} />
|
|
506
|
+
<span style={{ marginLeft: 8 }}>Button text</span>
|
|
507
|
+
</Button>
|
|
508
|
+
<Button theme={EButtonTheme.LINK} size={EComponentSize.LG}>
|
|
509
|
+
<DefaulticonStrokePrdIcon20 paletteIndex={5} />
|
|
510
|
+
<span style={{ marginLeft: 8 }}>Button text</span>
|
|
511
|
+
</Button>
|
|
512
|
+
</div>
|
|
513
|
+
);
|
|
514
|
+
```
|
|
515
|
+
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: ButtonDropdown
|
|
3
3
|
category: Buttons
|
|
4
|
-
figma-node: ""
|
|
5
|
-
figma-file: ""
|
|
6
4
|
related: [Button, ButtonDropdownExtended, Dropdown]
|
|
7
5
|
tokens: []
|
|
8
6
|
stories: stories/Buttons/ButtonDropdown.stories.tsx
|
|
@@ -13,25 +11,10 @@ version: "1.0"
|
|
|
13
11
|
|
|
14
12
|
## Назначение
|
|
15
13
|
|
|
16
|
-
Кнопка-триггер с выпадающим списком действий.
|
|
17
|
-
Поддерживает два визуальных режима: обычная кнопка (`EButtonTheme`) и dots-вариант (`EButtonDotsTheme`).
|
|
14
|
+
Кнопка-триггер с выпадающим списком действий. Поддерживает два визуальных режима: обычная кнопка (`EButtonTheme`) и dots-вариант (`EButtonDotsTheme`).
|
|
18
15
|
|
|
19
16
|
Используй когда: нужно сгруппировать несколько действий под один триггер и дать выбор опции из списка.
|
|
20
|
-
Не используй когда: нужна одиночная кнопка без списка
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Файловая структура
|
|
25
|
-
|
|
26
|
-
```text
|
|
27
|
-
src/components/Button/
|
|
28
|
-
├── ButtonDropdown.tsx # Основной компонент (forwardRef, рендер button + dropdown)
|
|
29
|
-
├── ButtonDropdownExtended.tsx # Базовый контейнер с контролем opened/close и outside click
|
|
30
|
-
├── styles/
|
|
31
|
-
│ ├── ButtonDropdown.module.less # Стили caret, active-состояния, block-режима
|
|
32
|
-
│ └── ButtonDropdownExtended.module.less
|
|
33
|
-
└── index.ts # Публичный экспорт ButtonDropdown
|
|
34
|
-
```
|
|
17
|
+
Не используй когда: нужна одиночная кнопка без списка — используй `Button`.
|
|
35
18
|
|
|
36
19
|
---
|
|
37
20
|
|
|
@@ -49,18 +32,13 @@ src/components/Button/
|
|
|
49
32
|
|
|
50
33
|
| Prop | Тип | По умолчанию | Описание |
|
|
51
34
|
|---|---|---|---|
|
|
52
|
-
| `children` | `React.ReactNode` |
|
|
53
|
-
| `selected` | `IButtonDropdownOption` |
|
|
35
|
+
| `children` | `React.ReactNode` | — | Текст/контент кнопки (используется также в mobile header) |
|
|
36
|
+
| `selected` | `IButtonDropdownOption` | — | Предвыбранная опция для подсветки в списке |
|
|
54
37
|
| `disabled` | `boolean` | `false` | Блокирует кнопку и открытие списка |
|
|
55
|
-
| `buttonAttributes` | `React.ButtonHTMLAttributes<HTMLButtonElement>` |
|
|
56
|
-
| `block` | `boolean` | `false` |
|
|
38
|
+
| `buttonAttributes` | `React.ButtonHTMLAttributes<HTMLButtonElement>` | — | Дополнительные HTML-атрибуты нативной кнопки (включая `aria-label`) |
|
|
39
|
+
| `block` | `boolean` | `false` | Полноширинный режим (только для non-dots тем) |
|
|
57
40
|
| `className` | `string` | — | Дополнительный CSS-класс контейнера |
|
|
58
|
-
| `...rest` | `React.HTMLAttributes<HTMLDivElement>` | — | Атрибуты корневого
|
|
59
|
-
|
|
60
|
-
### Ограничения по темам
|
|
61
|
-
|
|
62
|
-
- Для `EButtonDotsTheme` prop `block` запрещен типизацией (`never`).
|
|
63
|
-
- Dots-режим использует иконку `Dotshorizontal*` вместо текстового контента кнопки.
|
|
41
|
+
| `...rest` | `React.HTMLAttributes<HTMLDivElement>` | — | Атрибуты корневого `<div>` компонента |
|
|
64
42
|
|
|
65
43
|
### Структура опции (`IButtonDropdownOption`)
|
|
66
44
|
|
|
@@ -69,50 +47,19 @@ src/components/Button/
|
|
|
69
47
|
| `id` | `string` | Уникальный идентификатор опции |
|
|
70
48
|
| `label` | `React.ReactNode` | Видимый контент пункта меню |
|
|
71
49
|
| `onSelect` | `() => void` | Callback выбора опции |
|
|
72
|
-
| `...rest` | `IDropdownListItemProps` (без ряда
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Ключевые особенности реализации
|
|
77
|
-
|
|
78
|
-
### Композиция через `ButtonDropdownExtended`
|
|
79
|
-
|
|
80
|
-
- `ButtonDropdown` делегирует управление opened-state и close-on-outside в `ButtonDropdownExtended`.
|
|
81
|
-
- Передает две render-функции: `renderButton` и `renderDropdown`.
|
|
82
|
-
|
|
83
|
-
### Desktop + mobile dropdown
|
|
84
|
-
|
|
85
|
-
- Desktop: рендерится `DropdownList` с `DropdownList.Item`.
|
|
86
|
-
- Mobile: через `mobileViewProps` рендерится `DropdownMobileHeader` + `DropdownMobileList`.
|
|
87
|
-
- После выбора любого пункта вызывается `option.onSelect?.()` и dropdown закрывается.
|
|
50
|
+
| `...rest` | `IDropdownListItemProps` (без ряда запрещённых полей) | Остальные настройки `DropdownList.Item` |
|
|
88
51
|
|
|
89
|
-
###
|
|
90
|
-
|
|
91
|
-
- На кнопке перехватываются `Space`, `ArrowUp`, `ArrowDown` с `preventDefault`.
|
|
92
|
-
- `ArrowUp/ArrowDown` открывают dropdown, если он закрыт.
|
|
93
|
-
- В `ButtonDropdownExtended` dropdown закрывается по `Escape` и (из-за `closeOnTab`) по `Tab`.
|
|
94
|
-
|
|
95
|
-
### ARIA-связка trigger/list
|
|
96
|
-
|
|
97
|
-
- Кнопка получает `aria-haspopup="menu"`, `aria-expanded`, `aria-controls`.
|
|
98
|
-
- `aria-controls` и `DropdownList` связываются через `instanceId` (`uniqueId()`).
|
|
99
|
-
- `aria-activedescendant` синхронизируется через `DropdownListContext`.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## Accessibility
|
|
52
|
+
### Ограничения по темам
|
|
104
53
|
|
|
105
|
-
-
|
|
106
|
-
-
|
|
107
|
-
-
|
|
108
|
-
- Для корректной доступности меню потребитель должен передавать понятные `label` для `options`.
|
|
54
|
+
- Для `EButtonDotsTheme` prop `block` запрещён типизацией (`never`).
|
|
55
|
+
- Dots-режим использует иконку `Dotshorizontal*` вместо текстового контента кнопки.
|
|
56
|
+
- Desktop рендерит `DropdownList` с `DropdownList.Item`; mobile через `mobileViewProps` рендерит `DropdownMobileHeader` + `DropdownMobileList`.
|
|
109
57
|
|
|
110
58
|
---
|
|
111
59
|
|
|
112
60
|
## Дизайн-токены
|
|
113
61
|
|
|
114
|
-
`ButtonDropdown` не использует собственных CSS color
|
|
115
|
-
Визуальные токены берутся из `Button` и `Dropdown`, которые рендерятся внутри.
|
|
62
|
+
`ButtonDropdown` не использует собственных CSS color-токенов. Визуальные токены берутся из `Button` и `Dropdown`, которые рендерятся внутри.
|
|
116
63
|
|
|
117
64
|
---
|
|
118
65
|
|
|
@@ -126,17 +73,27 @@ src/components/Button/
|
|
|
126
73
|
|
|
127
74
|
---
|
|
128
75
|
|
|
76
|
+
## Accessibility
|
|
77
|
+
|
|
78
|
+
- Триггер строится на `Button` (нативный `<button>`), базовая keyboard-навигация сохраняется.
|
|
79
|
+
- Кнопка получает `aria-haspopup="menu"`, `aria-expanded`, `aria-controls`. Связка trigger/list осуществляется через `instanceId` (`uniqueId()`); `aria-activedescendant` синхронизируется через `DropdownListContext`.
|
|
80
|
+
- На кнопке перехватываются `Space`, `ArrowUp`, `ArrowDown` с `preventDefault`. `ArrowUp` / `ArrowDown` открывают dropdown, если он закрыт. Dropdown закрывается по `Escape` и (из-за `closeOnTab`) по `Tab`.
|
|
81
|
+
- Компонент **не хардкодит** aria-тексты: при необходимости `aria-label` нужно передавать через `buttonAttributes`. Для корректной доступности меню потребитель должен передавать понятные `label` для `options`.
|
|
82
|
+
- На mobile-заголовке dropdown отображается `children` триггера; если в продукте нужен локализованный отдельный заголовок, его нужно задавать на уровне композиции (через `ButtonDropdownExtended`).
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
129
86
|
## Связанные компоненты
|
|
130
87
|
|
|
131
|
-
- `Button` (`src/components/Button/Button.tsx`)
|
|
132
|
-
- `ButtonDropdownExtended` (`src/components/Button/ButtonDropdownExtended.tsx`)
|
|
133
|
-
- `Dropdown` (`src/components/Dropdown`)
|
|
88
|
+
- `Button` (`src/components/Button/Button.tsx`) — базовый триггер, на котором построен `ButtonDropdown`.
|
|
89
|
+
- `ButtonDropdownExtended` (`src/components/Button/ButtonDropdownExtended.tsx`) — управляет открытием/закрытием dropdown через render-функции `renderButton` и `renderDropdown`.
|
|
90
|
+
- `Dropdown` (`src/components/Dropdown`) — контейнер и пункты списка для desktop/mobile представления.
|
|
134
91
|
|
|
135
92
|
---
|
|
136
93
|
|
|
137
94
|
## Stories
|
|
138
95
|
|
|
139
|
-
`stories/Buttons/ButtonDropdown.stories.tsx`
|
|
96
|
+
Основные истории: `stories/Buttons/ButtonDropdown.stories.tsx`
|
|
140
97
|
Файлы примеров: `stories/Buttons/examples/ButtonDropdown/`
|
|
141
98
|
|
|
142
99
|
| Story | Example file | Что демонстрирует |
|
|
@@ -157,6 +114,7 @@ src/components/Button/
|
|
|
157
114
|
| Дата | Изменение |
|
|
158
115
|
|---|---|
|
|
159
116
|
| 2026-04-15 | Создан документ AI-ready для `ButtonDropdown`. |
|
|
117
|
+
| 2026-04-27 | Приведён в соответствие с `docs/ai/template-ai.md`: убраны секции «Файловая структура» и «Ключевые особенности реализации», их содержимое перенесено в `Ограничения по темам` и `Accessibility`, добавлена колонка `Example file`. |
|
|
160
118
|
|
|
161
119
|
---
|
|
162
120
|
|