@sberbusiness/triplex-next-mcp-server 0.24.0-beta1 → 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/Alert.md +272 -0
- package/bundle/components/Amount.md +138 -0
- package/bundle/components/AmountField.md +174 -0
- package/bundle/components/Avatar.md +72 -0
- package/bundle/components/Badge.md +203 -0
- package/bundle/components/BodyPage.md +186 -0
- package/bundle/components/Button.md +370 -85
- package/bundle/components/ButtonDropdown.md +328 -70
- package/bundle/components/ButtonIcon.md +98 -42
- package/bundle/components/Calendar.md +58 -0
- package/bundle/components/Checkbox.md +77 -44
- package/bundle/components/CheckboxTree.md +61 -0
- package/bundle/components/CheckboxTreeExtended.md +114 -0
- package/bundle/components/CheckboxXGroup.md +120 -16
- package/bundle/components/CheckboxYGroup.md +88 -15
- package/bundle/components/CollapsibleTree.md +581 -0
- package/bundle/components/CollapsibleTreeExtended.md +418 -0
- package/bundle/components/EmptyView.md +133 -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 +231 -72
- 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/components/SliderExtended.md +285 -0
- 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/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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`
|
|
50
|
+
| `...rest` | `IDropdownListItemProps` (без ряда запрещённых полей) | Остальные настройки `DropdownList.Item` |
|
|
79
51
|
|
|
80
|
-
|
|
81
|
-
- Передает две render-функции: `renderButton` и `renderDropdown`.
|
|
82
|
-
|
|
83
|
-
### Desktop + mobile dropdown
|
|
84
|
-
|
|
85
|
-
- Desktop: рендерится `DropdownList` с `DropdownList.Item`.
|
|
86
|
-
- Mobile: через `mobileViewProps` рендерится `DropdownMobileHeader` + `DropdownMobileList`.
|
|
87
|
-
- После выбора любого пункта вызывается `option.onSelect?.()` и dropdown закрывается.
|
|
88
|
-
|
|
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,3 +114,304 @@ 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`. |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Примеры использования
|
|
122
|
+
|
|
123
|
+
### Default
|
|
124
|
+
|
|
125
|
+
`stories/Buttons/examples/ButtonDropdown/DefaultExample.tsx`
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import React from "react";
|
|
129
|
+
import { ButtonDropdown, EButtonTheme, EComponentSize, IButtonDropdownOption } from "@sberbusiness/triplex-next";
|
|
130
|
+
|
|
131
|
+
const OPTIONS: IButtonDropdownOption[] = [
|
|
132
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => undefined },
|
|
133
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => undefined },
|
|
134
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => undefined },
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
export const DefaultExample = () => (
|
|
138
|
+
<div style={{ width: 280 }}>
|
|
139
|
+
<ButtonDropdown theme={EButtonTheme.GENERAL} size={EComponentSize.MD} options={OPTIONS}>
|
|
140
|
+
Button text
|
|
141
|
+
</ButtonDropdown>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Sizes
|
|
147
|
+
|
|
148
|
+
`stories/Buttons/examples/ButtonDropdown/SizesExample.tsx`
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import React from "react";
|
|
152
|
+
import {
|
|
153
|
+
ButtonDropdown,
|
|
154
|
+
EButtonDotsTheme,
|
|
155
|
+
EButtonTheme,
|
|
156
|
+
EComponentSize,
|
|
157
|
+
IButtonDropdownOption,
|
|
158
|
+
} from "@sberbusiness/triplex-next";
|
|
159
|
+
|
|
160
|
+
const OPTIONS: IButtonDropdownOption[] = [
|
|
161
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => undefined },
|
|
162
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => undefined },
|
|
163
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => undefined },
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
export const SizesExample = () => (
|
|
167
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
168
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
169
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>SM</div>
|
|
170
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 16 }}>
|
|
171
|
+
<ButtonDropdown size={EComponentSize.SM} theme={EButtonTheme.GENERAL} options={OPTIONS}>
|
|
172
|
+
Button text
|
|
173
|
+
</ButtonDropdown>
|
|
174
|
+
<ButtonDropdown size={EComponentSize.SM} theme={EButtonTheme.SECONDARY} options={OPTIONS}>
|
|
175
|
+
Button text
|
|
176
|
+
</ButtonDropdown>
|
|
177
|
+
<ButtonDropdown size={EComponentSize.SM} theme={EButtonTheme.SECONDARY_LIGHT} options={OPTIONS}>
|
|
178
|
+
Button text
|
|
179
|
+
</ButtonDropdown>
|
|
180
|
+
<ButtonDropdown size={EComponentSize.SM} theme={EButtonTheme.DANGER} options={OPTIONS}>
|
|
181
|
+
Button text
|
|
182
|
+
</ButtonDropdown>
|
|
183
|
+
<ButtonDropdown size={EComponentSize.SM} theme={EButtonDotsTheme.DOTS_SECONDARY} options={OPTIONS}>
|
|
184
|
+
Button text
|
|
185
|
+
</ButtonDropdown>
|
|
186
|
+
<ButtonDropdown
|
|
187
|
+
size={EComponentSize.SM}
|
|
188
|
+
theme={EButtonDotsTheme.DOTS_SECONDARY_LIGHT}
|
|
189
|
+
options={OPTIONS}
|
|
190
|
+
>
|
|
191
|
+
Button text
|
|
192
|
+
</ButtonDropdown>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
196
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>MD</div>
|
|
197
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 16 }}>
|
|
198
|
+
<ButtonDropdown size={EComponentSize.MD} theme={EButtonTheme.GENERAL} options={OPTIONS}>
|
|
199
|
+
Button text
|
|
200
|
+
</ButtonDropdown>
|
|
201
|
+
<ButtonDropdown size={EComponentSize.MD} theme={EButtonTheme.SECONDARY} options={OPTIONS}>
|
|
202
|
+
Button text
|
|
203
|
+
</ButtonDropdown>
|
|
204
|
+
<ButtonDropdown size={EComponentSize.MD} theme={EButtonTheme.SECONDARY_LIGHT} options={OPTIONS}>
|
|
205
|
+
Button text
|
|
206
|
+
</ButtonDropdown>
|
|
207
|
+
<ButtonDropdown size={EComponentSize.MD} theme={EButtonTheme.DANGER} options={OPTIONS}>
|
|
208
|
+
Button text
|
|
209
|
+
</ButtonDropdown>
|
|
210
|
+
<ButtonDropdown size={EComponentSize.MD} theme={EButtonDotsTheme.DOTS_SECONDARY} options={OPTIONS}>
|
|
211
|
+
Button text
|
|
212
|
+
</ButtonDropdown>
|
|
213
|
+
<ButtonDropdown
|
|
214
|
+
size={EComponentSize.MD}
|
|
215
|
+
theme={EButtonDotsTheme.DOTS_SECONDARY_LIGHT}
|
|
216
|
+
options={OPTIONS}
|
|
217
|
+
>
|
|
218
|
+
Button text
|
|
219
|
+
</ButtonDropdown>
|
|
220
|
+
</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
223
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>LG</div>
|
|
224
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 16 }}>
|
|
225
|
+
<ButtonDropdown size={EComponentSize.LG} theme={EButtonTheme.GENERAL} options={OPTIONS}>
|
|
226
|
+
Button text
|
|
227
|
+
</ButtonDropdown>
|
|
228
|
+
<ButtonDropdown size={EComponentSize.LG} theme={EButtonTheme.SECONDARY} options={OPTIONS}>
|
|
229
|
+
Button text
|
|
230
|
+
</ButtonDropdown>
|
|
231
|
+
<ButtonDropdown size={EComponentSize.LG} theme={EButtonTheme.SECONDARY_LIGHT} options={OPTIONS}>
|
|
232
|
+
Button text
|
|
233
|
+
</ButtonDropdown>
|
|
234
|
+
<ButtonDropdown size={EComponentSize.LG} theme={EButtonTheme.DANGER} options={OPTIONS}>
|
|
235
|
+
Button text
|
|
236
|
+
</ButtonDropdown>
|
|
237
|
+
<ButtonDropdown size={EComponentSize.LG} theme={EButtonDotsTheme.DOTS_SECONDARY} options={OPTIONS}>
|
|
238
|
+
Button text
|
|
239
|
+
</ButtonDropdown>
|
|
240
|
+
<ButtonDropdown
|
|
241
|
+
size={EComponentSize.LG}
|
|
242
|
+
theme={EButtonDotsTheme.DOTS_SECONDARY_LIGHT}
|
|
243
|
+
options={OPTIONS}
|
|
244
|
+
>
|
|
245
|
+
Button text
|
|
246
|
+
</ButtonDropdown>
|
|
247
|
+
</div>
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Themes
|
|
254
|
+
|
|
255
|
+
`stories/Buttons/examples/ButtonDropdown/ThemesExample.tsx`
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
import React from "react";
|
|
259
|
+
import {
|
|
260
|
+
ButtonDropdown,
|
|
261
|
+
EButtonDotsTheme,
|
|
262
|
+
EButtonTheme,
|
|
263
|
+
EComponentSize,
|
|
264
|
+
IButtonDropdownOption,
|
|
265
|
+
} from "@sberbusiness/triplex-next";
|
|
266
|
+
|
|
267
|
+
const OPTIONS: IButtonDropdownOption[] = [
|
|
268
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => undefined },
|
|
269
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => undefined },
|
|
270
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => undefined },
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
export const ThemesExample = () => (
|
|
274
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
275
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 16 }}>
|
|
276
|
+
<ButtonDropdown theme={EButtonTheme.GENERAL} size={EComponentSize.MD} options={OPTIONS}>
|
|
277
|
+
General
|
|
278
|
+
</ButtonDropdown>
|
|
279
|
+
<ButtonDropdown theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} options={OPTIONS}>
|
|
280
|
+
Secondary
|
|
281
|
+
</ButtonDropdown>
|
|
282
|
+
<ButtonDropdown theme={EButtonTheme.SECONDARY_LIGHT} size={EComponentSize.MD} options={OPTIONS}>
|
|
283
|
+
Secondary Light
|
|
284
|
+
</ButtonDropdown>
|
|
285
|
+
<ButtonDropdown theme={EButtonTheme.DANGER} size={EComponentSize.MD} options={OPTIONS}>
|
|
286
|
+
Danger
|
|
287
|
+
</ButtonDropdown>
|
|
288
|
+
</div>
|
|
289
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
290
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>DOTS_SECONDARY</div>
|
|
291
|
+
<ButtonDropdown theme={EButtonDotsTheme.DOTS_SECONDARY} size={EComponentSize.MD} options={OPTIONS}>
|
|
292
|
+
Dots Secondary
|
|
293
|
+
</ButtonDropdown>
|
|
294
|
+
</div>
|
|
295
|
+
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
|
296
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>DOTS_SECONDARY_LIGHT</div>
|
|
297
|
+
<ButtonDropdown theme={EButtonDotsTheme.DOTS_SECONDARY_LIGHT} size={EComponentSize.MD} options={OPTIONS}>
|
|
298
|
+
Dots Secondary Light
|
|
299
|
+
</ButtonDropdown>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### BlockMode
|
|
306
|
+
|
|
307
|
+
`stories/Buttons/examples/ButtonDropdown/BlockModeExample.tsx`
|
|
308
|
+
|
|
309
|
+
```tsx
|
|
310
|
+
import React from "react";
|
|
311
|
+
import { ButtonDropdown, EButtonTheme, EComponentSize, Gap, IButtonDropdownOption } from "@sberbusiness/triplex-next";
|
|
312
|
+
|
|
313
|
+
const OPTIONS: IButtonDropdownOption[] = [
|
|
314
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => undefined },
|
|
315
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => undefined },
|
|
316
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => undefined },
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
export const BlockModeExample = () => (
|
|
320
|
+
<div style={{ maxWidth: 280 }}>
|
|
321
|
+
<ButtonDropdown block theme={EButtonTheme.GENERAL} size={EComponentSize.MD} options={OPTIONS}>
|
|
322
|
+
Button text
|
|
323
|
+
</ButtonDropdown>
|
|
324
|
+
<Gap size={16} />
|
|
325
|
+
<ButtonDropdown block theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} options={OPTIONS}>
|
|
326
|
+
Button text
|
|
327
|
+
</ButtonDropdown>
|
|
328
|
+
<Gap size={16} />
|
|
329
|
+
<ButtonDropdown block theme={EButtonTheme.SECONDARY_LIGHT} size={EComponentSize.MD} options={OPTIONS}>
|
|
330
|
+
Button text
|
|
331
|
+
</ButtonDropdown>
|
|
332
|
+
<Gap size={16} />
|
|
333
|
+
<ButtonDropdown block theme={EButtonTheme.DANGER} size={EComponentSize.MD} options={OPTIONS}>
|
|
334
|
+
Button text
|
|
335
|
+
</ButtonDropdown>
|
|
336
|
+
</div>
|
|
337
|
+
);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Disabled
|
|
341
|
+
|
|
342
|
+
`stories/Buttons/examples/ButtonDropdown/DisabledExample.tsx`
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
import React from "react";
|
|
346
|
+
import {
|
|
347
|
+
ButtonDropdown,
|
|
348
|
+
EButtonDotsTheme,
|
|
349
|
+
EButtonTheme,
|
|
350
|
+
EComponentSize,
|
|
351
|
+
IButtonDropdownOption,
|
|
352
|
+
} from "@sberbusiness/triplex-next";
|
|
353
|
+
|
|
354
|
+
const OPTIONS: IButtonDropdownOption[] = [
|
|
355
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => undefined },
|
|
356
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => undefined },
|
|
357
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => undefined },
|
|
358
|
+
];
|
|
359
|
+
|
|
360
|
+
export const DisabledExample = () => (
|
|
361
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 16 }}>
|
|
362
|
+
<ButtonDropdown disabled theme={EButtonTheme.GENERAL} size={EComponentSize.MD} options={OPTIONS}>
|
|
363
|
+
Button text
|
|
364
|
+
</ButtonDropdown>
|
|
365
|
+
<ButtonDropdown disabled theme={EButtonTheme.SECONDARY} size={EComponentSize.MD} options={OPTIONS}>
|
|
366
|
+
Button text
|
|
367
|
+
</ButtonDropdown>
|
|
368
|
+
<ButtonDropdown disabled theme={EButtonTheme.SECONDARY_LIGHT} size={EComponentSize.MD} options={OPTIONS}>
|
|
369
|
+
Button text
|
|
370
|
+
</ButtonDropdown>
|
|
371
|
+
<ButtonDropdown disabled theme={EButtonTheme.DANGER} size={EComponentSize.MD} options={OPTIONS}>
|
|
372
|
+
Button text
|
|
373
|
+
</ButtonDropdown>
|
|
374
|
+
<ButtonDropdown disabled theme={EButtonDotsTheme.DOTS_SECONDARY} size={EComponentSize.MD} options={OPTIONS}>
|
|
375
|
+
Button text
|
|
376
|
+
</ButtonDropdown>
|
|
377
|
+
<ButtonDropdown
|
|
378
|
+
disabled
|
|
379
|
+
theme={EButtonDotsTheme.DOTS_SECONDARY_LIGHT}
|
|
380
|
+
size={EComponentSize.MD}
|
|
381
|
+
options={OPTIONS}
|
|
382
|
+
>
|
|
383
|
+
Button text
|
|
384
|
+
</ButtonDropdown>
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### WithSelectedOption
|
|
390
|
+
|
|
391
|
+
`stories/Buttons/examples/ButtonDropdown/WithSelectedOptionExample.tsx`
|
|
392
|
+
|
|
393
|
+
```tsx
|
|
394
|
+
import React, { useMemo, useState } from "react";
|
|
395
|
+
import { ButtonDropdown, EButtonTheme, EComponentSize, IButtonDropdownOption } from "@sberbusiness/triplex-next";
|
|
396
|
+
|
|
397
|
+
const createOptions = (onItemSelect?: (id: string) => void): IButtonDropdownOption[] => [
|
|
398
|
+
{ id: "opt-1", label: "Действие 1", onSelect: () => onItemSelect?.("opt-1") },
|
|
399
|
+
{ id: "opt-2", label: "Действие 2", onSelect: () => onItemSelect?.("opt-2") },
|
|
400
|
+
{ id: "opt-3", label: "Действие 3", onSelect: () => onItemSelect?.("opt-3") },
|
|
401
|
+
];
|
|
402
|
+
|
|
403
|
+
export const WithSelectedOptionExample = () => {
|
|
404
|
+
const [selectedId, setSelectedId] = useState<string | undefined>("opt-2");
|
|
405
|
+
const options = useMemo(() => createOptions(setSelectedId), []);
|
|
406
|
+
const selected = options.find((o) => o.id === selectedId);
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<div style={{ maxWidth: 280 }}>
|
|
410
|
+
<ButtonDropdown theme={EButtonTheme.GENERAL} size={EComponentSize.MD} options={options} selected={selected}>
|
|
411
|
+
Button text
|
|
412
|
+
</ButtonDropdown>
|
|
413
|
+
</div>
|
|
414
|
+
);
|
|
415
|
+
};
|
|
416
|
+
```
|
|
417
|
+
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: ButtonIcon
|
|
3
3
|
category: Buttons
|
|
4
|
-
figma-node: ""
|
|
5
|
-
figma-file: ""
|
|
6
4
|
related: [Button]
|
|
7
5
|
tokens:
|
|
8
6
|
- --triplex-next-Button-Icon_Shadow_Focus
|
|
@@ -17,19 +15,7 @@ version: "1.0"
|
|
|
17
15
|
Кнопка только с иконкой без текстового контента. Используется как компактный trigger для локальных действий (например, открыть меню, удалить, закрыть).
|
|
18
16
|
|
|
19
17
|
Используй когда: действие можно однозначно распознать по иконке и есть корректный `aria-label`.
|
|
20
|
-
Не используй когда: нужен текстовый CTA или несколько визуальных тем с размерами как у `Button`
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Файловая структура
|
|
25
|
-
|
|
26
|
-
```text
|
|
27
|
-
src/components/Button/
|
|
28
|
-
├── ButtonIcon.tsx # Компонент (forwardRef, native button)
|
|
29
|
-
├── enums.ts # EButtonIconShape (SQUIRCLE / CIRCLE)
|
|
30
|
-
└── styles/
|
|
31
|
-
└── ButtonIcon.module.less # Геометрия, focus-visible, базовые состояния
|
|
32
|
-
```
|
|
18
|
+
Не используй когда: нужен текстовый CTA или несколько визуальных тем с размерами как у `Button` — используй `Button`.
|
|
33
19
|
|
|
34
20
|
---
|
|
35
21
|
|
|
@@ -51,29 +37,8 @@ src/components/Button/
|
|
|
51
37
|
### Ограничения использования
|
|
52
38
|
|
|
53
39
|
- Размер `ButtonIcon` задаётся размером переданной иконки; у компонента нет собственного prop `size`.
|
|
54
|
-
- Корневой DOM-элемент всегда
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Ключевые особенности реализации
|
|
59
|
-
|
|
60
|
-
### Маппинг формы через enum
|
|
61
|
-
|
|
62
|
-
- `shape` маппится в CSS-класс через `Record<EButtonIconShape, string>`.
|
|
63
|
-
- При расширении `EButtonIconShape` нужно добавить класс в маппинг и в `ButtonIcon.module.less`.
|
|
64
|
-
|
|
65
|
-
### Визуальные состояния
|
|
66
|
-
|
|
67
|
-
- `active` и `disabled` добавляются как глобальные CSS-классы (`active`, `disabled`) для совместимости с иконками.
|
|
68
|
-
- Дополнительно всегда добавляется класс `hoverable`.
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Accessibility
|
|
73
|
-
|
|
74
|
-
- Компонент рендерит нативный `<button>`, поэтому базовая keyboard-навигация доступна из коробки.
|
|
75
|
-
- Компонент **не хардкодит** `aria-label` (библиотека мультиязычная). Для иконки без текста потребитель обязан передать `aria-label` через `...rest`.
|
|
76
|
-
- Фокусное состояние реализовано через `:focus-visible` и токен `--triplex-next-Button-Icon_Shadow_Focus`.
|
|
40
|
+
- Корневой DOM-элемент всегда `<button>`, `type` по умолчанию принудительно установлен в `button`.
|
|
41
|
+
- При расширении `EButtonIconShape` необходимо добавить класс в маппинг `Record<EButtonIconShape, string>` и в `ButtonIcon.module.less`.
|
|
77
42
|
|
|
78
43
|
---
|
|
79
44
|
|
|
@@ -94,16 +59,24 @@ src/components/Button/
|
|
|
94
59
|
|
|
95
60
|
---
|
|
96
61
|
|
|
62
|
+
## Accessibility
|
|
63
|
+
|
|
64
|
+
- Компонент рендерит нативный `<button>`, поэтому базовая keyboard-навигация доступна из коробки.
|
|
65
|
+
- Компонент **не хардкодит** `aria-label` (библиотека мультиязычная). Для иконки без текста потребитель **обязан** передать `aria-label` через `...rest`.
|
|
66
|
+
- Фокусное состояние реализовано через `:focus-visible` и токен `--triplex-next-Button-Icon_Shadow_Focus`.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
97
70
|
## Связанные компоненты
|
|
98
71
|
|
|
99
|
-
- `Button` (`src/components/Button/Button.tsx`)
|
|
100
|
-
- `ButtonDropdown` (`src/components/Button/ButtonDropdown.tsx`)
|
|
72
|
+
- `Button` (`src/components/Button/Button.tsx`) — текстовая/универсальная кнопка, используемая вместо `ButtonIcon` когда нужен label.
|
|
73
|
+
- `ButtonDropdown` (`src/components/Button/ButtonDropdown.tsx`) — использует иконочный паттерн для dots-варианта trigger.
|
|
101
74
|
|
|
102
75
|
---
|
|
103
76
|
|
|
104
77
|
## Stories
|
|
105
78
|
|
|
106
|
-
`stories/Buttons/ButtonIcon.stories.tsx`
|
|
79
|
+
Основные истории: `stories/Buttons/ButtonIcon.stories.tsx`
|
|
107
80
|
Файлы примеров: `stories/Buttons/examples/ButtonIcon/`
|
|
108
81
|
|
|
109
82
|
| Story | Example file | Что демонстрирует |
|
|
@@ -111,7 +84,7 @@ src/components/Button/
|
|
|
111
84
|
| `Playground` | `PlaygroundExample.tsx` | Интерактивный контроль `shape`, `active`, `disabled` |
|
|
112
85
|
| `Default` | `DefaultExample.tsx` | Базовый сценарий использования |
|
|
113
86
|
| `Sizes` | `SizesExample.tsx` | Разные размеры через размер иконки (16/20/24/32) |
|
|
114
|
-
| `
|
|
87
|
+
| `States` | `StatesExample.tsx` | Состояния `active` и `disabled` |
|
|
115
88
|
|
|
116
89
|
---
|
|
117
90
|
|
|
@@ -120,3 +93,86 @@ src/components/Button/
|
|
|
120
93
|
| Дата | Изменение |
|
|
121
94
|
|---|---|
|
|
122
95
|
| 2026-04-15 | Создан документ AI-ready для `ButtonIcon`. |
|
|
96
|
+
| 2026-04-27 | Приведён в соответствие с `docs/ai/template-ai.md`: убрана секция «Файловая структура», содержимое «Ключевых особенностей реализации» перенесено в `Ограничения использования`, добавлена колонка `Example file`. |
|
|
97
|
+
| 2026-07-08 | Актуализирована таблица Stories: story `Disabled` заменена на `States` (`StatesExample.tsx`). |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Примеры использования
|
|
102
|
+
|
|
103
|
+
### Default
|
|
104
|
+
|
|
105
|
+
`stories/Buttons/examples/ButtonIcon/DefaultExample.tsx`
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import React from "react";
|
|
109
|
+
import { DefaulticonStrokePrdIcon32 } from "@sberbusiness/icons-next";
|
|
110
|
+
import { ButtonIcon, EButtonIconShape } from "@sberbusiness/triplex-next";
|
|
111
|
+
|
|
112
|
+
export const DefaultExample = () => (
|
|
113
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE}>
|
|
114
|
+
<DefaulticonStrokePrdIcon32 paletteIndex={5} />
|
|
115
|
+
</ButtonIcon>
|
|
116
|
+
);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Sizes
|
|
120
|
+
|
|
121
|
+
`stories/Buttons/examples/ButtonIcon/SizesExample.tsx`
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
import React from "react";
|
|
125
|
+
import {
|
|
126
|
+
DefaulticonStrokePrdIcon16,
|
|
127
|
+
DefaulticonStrokePrdIcon20,
|
|
128
|
+
DefaulticonStrokePrdIcon24,
|
|
129
|
+
DefaulticonStrokePrdIcon32,
|
|
130
|
+
} from "@sberbusiness/icons-next";
|
|
131
|
+
import { ButtonIcon, EButtonIconShape } from "@sberbusiness/triplex-next";
|
|
132
|
+
|
|
133
|
+
export const SizesExample = () => (
|
|
134
|
+
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
|
135
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE}>
|
|
136
|
+
<DefaulticonStrokePrdIcon16 paletteIndex={5} />
|
|
137
|
+
</ButtonIcon>
|
|
138
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE}>
|
|
139
|
+
<DefaulticonStrokePrdIcon20 paletteIndex={5} />
|
|
140
|
+
</ButtonIcon>
|
|
141
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE}>
|
|
142
|
+
<DefaulticonStrokePrdIcon24 paletteIndex={5} />
|
|
143
|
+
</ButtonIcon>
|
|
144
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE}>
|
|
145
|
+
<DefaulticonStrokePrdIcon32 paletteIndex={5} />
|
|
146
|
+
</ButtonIcon>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### States
|
|
152
|
+
|
|
153
|
+
`stories/Buttons/examples/ButtonIcon/StatesExample.tsx`
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import React from "react";
|
|
157
|
+
import { DefaulticonStrokePrdIcon32 } from "@sberbusiness/icons-next";
|
|
158
|
+
import { ButtonIcon, EButtonIconShape } from "@sberbusiness/triplex-next";
|
|
159
|
+
|
|
160
|
+
export const StatesExample = () => (
|
|
161
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
|
|
162
|
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "start", gap: "8px" }}>
|
|
163
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Active</div>
|
|
164
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE} active>
|
|
165
|
+
<DefaulticonStrokePrdIcon32 paletteIndex={5} />
|
|
166
|
+
</ButtonIcon>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "start", gap: "8px" }}>
|
|
170
|
+
<div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>Disabled</div>
|
|
171
|
+
<ButtonIcon shape={EButtonIconShape.SQUIRCLE} disabled>
|
|
172
|
+
<DefaulticonStrokePrdIcon32 paletteIndex={5} />
|
|
173
|
+
</ButtonIcon>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|