@sberbusiness/triplex-next-mcp-server 0.36.0-beta0 → 1.41.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/bundle/components/AlertContext.md +268 -0
  2. package/bundle/components/Button.md +5 -2
  3. package/bundle/components/ButtonDropdown.md +9 -1
  4. package/bundle/components/ButtonDropdownExtended.md +422 -0
  5. package/bundle/components/ButtonIcon.md +7 -1
  6. package/bundle/components/Calendar.md +183 -368
  7. package/bundle/components/Caption.md +291 -0
  8. package/bundle/components/Chip.md +310 -314
  9. package/bundle/components/CodeText.md +197 -0
  10. package/bundle/components/Col.md +311 -162
  11. package/bundle/components/CollapsibleTree.md +6 -3
  12. package/bundle/components/CollapsibleTreeExtended.md +6 -2
  13. package/bundle/components/Dropdown.md +748 -346
  14. package/bundle/components/FormField.md +468 -232
  15. package/bundle/components/IconWrapper.md +142 -89
  16. package/bundle/components/Island.md +221 -129
  17. package/bundle/components/KeyDownListener.md +215 -102
  18. package/bundle/components/LightBox.md +2252 -233
  19. package/bundle/components/List.md +26 -1
  20. package/bundle/components/ListActionItem.md +157 -0
  21. package/bundle/components/ListItem.md +34 -9
  22. package/bundle/components/ListItemSelectable.md +2 -2
  23. package/bundle/components/ListMaster.md +4 -5
  24. package/bundle/components/ListTableItem.md +391 -0
  25. package/bundle/components/LoaderMiddle.md +265 -0
  26. package/bundle/components/LoaderScreen.md +178 -81
  27. package/bundle/components/Notification.md +21 -21
  28. package/bundle/components/Page.md +1 -0
  29. package/bundle/components/Portal.md +131 -67
  30. package/bundle/components/Row.md +171 -69
  31. package/bundle/components/Text.md +320 -0
  32. package/bundle/components/TextField.md +364 -97
  33. package/bundle/components/ThemeProvider.md +305 -117
  34. package/bundle/components/Title.md +296 -0
  35. package/bundle/components/Tooltip.md +421 -181
  36. package/bundle/guides/commits.md +126 -24
  37. package/bundle/guides/context.md +1 -1
  38. package/bundle/guides/tests.md +42 -0
  39. package/bundle/manifest.json +109 -23
  40. package/bundle/release-notes/1.37.0.mdx +42 -1
  41. package/bundle/release-notes/1.38.0.mdx +36 -0
  42. package/bundle/release-notes/1.39.0.mdx +66 -0
  43. package/bundle/release-notes/1.40.0.mdx +27 -0
  44. package/bundle/release-notes/1.41.0.mdx +83 -0
  45. package/bundle/release-notes/1.42.0.mdx +7 -0
  46. package/package.json +1 -1
@@ -0,0 +1,422 @@
1
+ ---
2
+ component: ButtonDropdownExtended
3
+ category: Buttons
4
+ related: [ButtonDropdown, Button, Dropdown, DropdownList]
5
+ tokens: []
6
+ stories: stories/Buttons/ButtonDropdownExtended.stories.tsx
7
+ version: "1.0"
8
+ ---
9
+
10
+ # ButtonDropdownExtended
11
+
12
+ ## Назначение
13
+
14
+ Низкоуровневый контейнер «кнопка + выпадающий блок». Сам ничего не рисует: кнопку и содержимое выпадающего блока задаёт потребитель через render-функции `renderButton` и `renderDropdown`. Компонент берёт на себя только состояние открытости и закрытие блока по Escape, по Tab (при `closeOnTab`) и по клику/тапу вне кнопки и вне блока.
15
+
16
+ Используй когда: нужен собственный триггер и/или произвольное содержимое выпадающего блока (форма, фильтр, кастомный список), а логику открытия/закрытия переиспользовать.
17
+ Не используй когда: нужен готовый список действий — используй `ButtonDropdown`, он построен поверх этого компонента.
18
+
19
+ ---
20
+
21
+ ## Варианты и props
22
+
23
+ ### Обязательные props
24
+
25
+ | Prop | Тип | Описание |
26
+ |---|---|---|
27
+ | `renderButton` | `(props: IButtonDropdownExtendedButtonProvideProps) => React.ReactNode` | Отрисовка кнопки-триггера. Получает `opened` и `setOpened` |
28
+ | `renderDropdown` | `(props: IButtonDropdownExtendedDropdownProvideProps) => React.ReactNode` | Отрисовка выпадающего блока. Дополнительно получает `className` внутреннего позиционирующего класса |
29
+ | `dropdownRef` | `React.RefObject<HTMLElement>` | Ссылка на DOM-элемент выпадающего блока. Нужна для click-outside: клик внутри этого элемента не закрывает блок |
30
+
31
+ ### Опциональные props
32
+
33
+ | Prop | Тип | По умолчанию | Описание |
34
+ |---|---|---|---|
35
+ | `opened` | `boolean` | — | Внешнее состояние открытости. Включает контролируемый режим |
36
+ | `setOpened` | `(opened: boolean) => void` | — | Запрос на смену состояния. Вызывается **только** в контролируемом режиме |
37
+ | `closeOnTab` | `boolean` | `false` | Закрывать блок по Tab (в дополнение к Escape и клику снаружи) |
38
+ | `className` | `string` | — | Дополнительный CSS-класс корневого `<div>` |
39
+ | `...rest` | `React.HTMLAttributes<HTMLDivElement>` | — | Атрибуты корневого `<div>` |
40
+
41
+ ### Контролируемый и неконтролируемый режим
42
+
43
+ - Режим **фиксируется на монтировании**: если `opened !== undefined` на первом рендере — компонент контролируемый на всё время жизни, иначе неконтролируемый. Появление или исчезновение `opened` после маунта режим не меняет.
44
+ - В контролируемом режиме внутреннее состояние не используется: `setOpened` вызывается, а фактическое значение приходит из prop `opened`. Без `setOpened` блок в этом режиме не сможет закрыться.
45
+ - В неконтролируемом режиме `setOpened` не вызывается — состояние живёт внутри компонента.
46
+ - `renderButton` и `renderDropdown` в обоих режимах получают одно и то же актуальное значение `opened` и один и тот же обработчик `setOpened`.
47
+
48
+ ### Статические субкомпоненты
49
+
50
+ `ButtonDropdownExtended.Dropdown` и `ButtonDropdownExtended.DropdownList` — реэкспорты `Dropdown` и `DropdownList`, чтобы собирать выпадающий блок без дополнительных импортов. Это те же самые компоненты, а не обёртки.
51
+
52
+ ---
53
+
54
+ ## Дизайн-токены
55
+
56
+ Собственных CSS-переменных нет. `ButtonDropdownExtended.module.less` задаёт только раскладку корневого контейнера: `display: inline-block`, `position: relative`, `vertical-align: middle` (общая опорная точка с `Button`), `line-height: 0`. Цвета и размеры приходят из вложенных `Button` и `Dropdown`.
57
+
58
+ Правила класса выпадающего блока (`padding: 4px`, `margin-top: 4px`, `right: 0`) объявлены **вложенным** селектором внутри `.buttonDropdownExtended`, то есть компилируются в потомковый. При штатном использовании они не применяются: `ButtonDropdownExtended.Dropdown` рендерится через `<Portal container={document.body}>`, и узел с этим классом не является потомком корневого `<div>`. Позиционирование блока в этом случае обеспечивает сам `Dropdown` по `targetRef`. Учитывай это, если правишь отступы: менять их нужно не здесь.
59
+
60
+ ---
61
+
62
+ ## Инварианты
63
+
64
+ - Компонент объявлен как `IButtonDropdownExtendedComponent extends React.FC<IButtonDropdownExtendedProps>`, `forwardRef` **не используется**: ref на корневой `<div>` не пробрасывается. Добавление `forwardRef` меняет базовый тип экспортируемого интерфейса — это breaking change, требует отдельного решения.
65
+ - Публичные имена `ButtonDropdownExtended`, `IButtonDropdownExtendedProps`, `IButtonDropdownExtendedButtonProvideProps`, `IButtonDropdownExtendedDropdownProvideProps`, `IButtonDropdownExtendedComponent` экспортируются из `src/components/Button/index.ts` — сохранять.
66
+ - Статические свойства `Dropdown` и `DropdownList` — часть публичного API.
67
+ - Фиксация режима управления на монтировании — наблюдаемое поведение, на него опираются потребители. Менять только осознанно.
68
+ - `renderDropdown` обязан применить переданный `className` к выпадающему блоку. Но не считай, что на нём держится раскладка: стили этого класса объявлены вложенным селектором внутри `.buttonDropdownExtended` и при рендере блока в портале (штатный путь через `ButtonDropdownExtended.Dropdown`) не применяются — см. раздел «Дизайн-токены».
69
+ - Корневой DOM-элемент — `<div>` с классом `buttonDropdownExtended`; на нём же живёт `containerRef`, по которому определяется «клик внутри триггера».
70
+ - Слушатели `keydown` / `mousedown` / `touchstart` вешаются на `document` только пока блок открыт.
71
+
72
+ ---
73
+
74
+ ## Accessibility
75
+
76
+ - Компонент не рендерит собственной разметки кроме контейнера `<div>` и **не выставляет ARIA-атрибутов**. Роли и связки — зона ответственности `renderButton` / `renderDropdown`.
77
+ - Потребитель обязан сам проставить на триггере `aria-haspopup`, `aria-expanded={opened}` и при необходимости `aria-controls` (см. `ButtonDropdown` как эталонную реализацию).
78
+ - Клавиатура, обрабатываемая самим компонентом: `Escape` закрывает блок всегда, `Tab` — только при `closeOnTab`. Обработчик висит на `document`, поэтому срабатывает независимо от того, где находится фокус.
79
+ - Фокус компонент не перемещает: при открытии фокус не уходит в блок, при закрытии не возвращается на триггер. Если продукту нужен focus trap или возврат фокуса — реализуй это в render-функциях.
80
+ - Текстовых строк компонент не содержит, хардкода языка нет.
81
+
82
+ ---
83
+
84
+ ## Связанные компоненты
85
+
86
+ - `ButtonDropdown` (`src/components/Button/ButtonDropdown.tsx`) — готовая кнопка со списком действий, построена поверх `ButtonDropdownExtended`.
87
+ - `Button` (`src/components/Button/Button.tsx`) — обычный триггер для `renderButton`.
88
+ - `Dropdown` (`src/components/Dropdown`) — выпадающий блок, доступен как `ButtonDropdownExtended.Dropdown`; рендерится в портале, позиционируется по `targetRef`.
89
+ - `DropdownList` (`src/components/Dropdown/desktop/DropdownList.tsx`) — список с клавиатурной навигацией, доступен как `ButtonDropdownExtended.DropdownList`.
90
+ - Внутренние потребители в библиотеке: `ListItemControlsButtonDropdown`, `TableBasicSettings` — при изменении поведения проверяй их.
91
+
92
+ ---
93
+
94
+ ## Stories
95
+
96
+ Основные истории: `stories/Buttons/ButtonDropdownExtended.stories.tsx`
97
+ Файлы примеров: `stories/Buttons/examples/ButtonDropdownExtended/`
98
+
99
+ | Story | Example file | Что демонстрирует |
100
+ |---|---|---|
101
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `closeOnTab` |
102
+ | `Default` | `Default.tsx` | Неконтролируемый режим: кнопка `Button` + `Dropdown` со списком действий |
103
+ | `Controlled` | `Controlled.tsx` | Контролируемый режим через `opened` / `setOpened` |
104
+ | `CloseOnTab` | `CloseOnTab.tsx` | Закрытие блока по Tab |
105
+ | `WithCustomContent` | `WithCustomContent.tsx` | Произвольное содержимое блока вместо списка действий |
106
+ | `VisualTests` | `VisualTests.tsx` | Раскрытые блоки: список действий (`MIN_TARGET` + `RIGHT`, раскрывается `play`-функцией) и произвольный контент (`CONTENT` + `LEFT`, форсированно раскрыт в контролируемом режиме) |
107
+
108
+ Документационные стори намеренно рендерятся в закрытом состоянии: раскрытый `Dropdown` блокирует скролл страницы (`wheel`-обработчик в `DropdownDesktop`), что ломает навигацию по autodocs. Раскрытые состояния покрыты `VisualTests`.
109
+
110
+ Скриншот-тесты: `Playground` пропускается всегда; `Controlled` и `CloseOnTab` пропускаются как визуально идентичные `Default`.
111
+
112
+ ---
113
+
114
+ ## История изменений
115
+
116
+ | Дата | Изменение |
117
+ |---|---|
118
+ | 2026-07-29 | Создан документ AI-ready для `ButtonDropdownExtended`. AI-рефакторинг без изменения публичного API: режим управления переведён с ref на `useState` (устранены ошибки `react-hooks/refs`), убраны вырожденный тернарник в `useState` и мёртвые проверки `opened` внутри обработчиков, `setOpened && setOpened(...)` заменён на опциональный вызов, добавлены JSDoc. Добавлены unit-тесты и stories по modern pattern. |
119
+
120
+ ---
121
+
122
+ ## Примеры использования
123
+
124
+ ### Default
125
+
126
+ `stories/Buttons/examples/ButtonDropdownExtended/Default.tsx`
127
+
128
+ ```tsx
129
+ import React, { useRef } from "react";
130
+ import {
131
+ Button,
132
+ ButtonDropdownExtended,
133
+ EButtonTheme,
134
+ EComponentSize,
135
+ EDropdownAlignment,
136
+ EDropdownWidth,
137
+ IButtonDropdownExtendedButtonProvideProps,
138
+ IButtonDropdownExtendedDropdownProvideProps,
139
+ } from "@sberbusiness/triplex-next";
140
+
141
+ const OPTIONS = [
142
+ { id: "opt-1", label: "Действие 1" },
143
+ { id: "opt-2", label: "Действие 2" },
144
+ { id: "opt-3", label: "Действие 3" },
145
+ ];
146
+
147
+ export const Default = () => {
148
+ const buttonRef = useRef<HTMLButtonElement>(null);
149
+ const dropdownRef = useRef<HTMLDivElement>(null);
150
+
151
+ const renderButton = ({ opened, setOpened }: IButtonDropdownExtendedButtonProvideProps) => (
152
+ <Button
153
+ theme={EButtonTheme.GENERAL}
154
+ size={EComponentSize.MD}
155
+ aria-haspopup="menu"
156
+ aria-expanded={opened}
157
+ onClick={() => setOpened(!opened)}
158
+ ref={buttonRef}
159
+ >
160
+ Действия
161
+ </Button>
162
+ );
163
+
164
+ const renderDropdown = ({ opened, setOpened, className }: IButtonDropdownExtendedDropdownProvideProps) => (
165
+ <ButtonDropdownExtended.Dropdown
166
+ className={className}
167
+ size={EComponentSize.MD}
168
+ width={EDropdownWidth.MIN_TARGET}
169
+ alignment={EDropdownAlignment.LEFT}
170
+ opened={opened}
171
+ setOpened={setOpened}
172
+ targetRef={buttonRef}
173
+ ref={dropdownRef}
174
+ >
175
+ <ButtonDropdownExtended.DropdownList dropdownOpened={opened} size={EComponentSize.MD}>
176
+ {OPTIONS.map((option) => (
177
+ <ButtonDropdownExtended.DropdownList.Item
178
+ key={option.id}
179
+ id={option.id}
180
+ onSelect={() => setOpened(false)}
181
+ >
182
+ {option.label}
183
+ </ButtonDropdownExtended.DropdownList.Item>
184
+ ))}
185
+ </ButtonDropdownExtended.DropdownList>
186
+ </ButtonDropdownExtended.Dropdown>
187
+ );
188
+
189
+ return (
190
+ <ButtonDropdownExtended renderButton={renderButton} renderDropdown={renderDropdown} dropdownRef={dropdownRef} />
191
+ );
192
+ };
193
+ ```
194
+
195
+ ### Controlled
196
+
197
+ `stories/Buttons/examples/ButtonDropdownExtended/Controlled.tsx`
198
+
199
+ ```tsx
200
+ import React, { useRef, useState } from "react";
201
+ import {
202
+ Button,
203
+ ButtonDropdownExtended,
204
+ EButtonTheme,
205
+ EComponentSize,
206
+ EDropdownAlignment,
207
+ EDropdownWidth,
208
+ IButtonDropdownExtendedButtonProvideProps,
209
+ IButtonDropdownExtendedDropdownProvideProps,
210
+ } from "@sberbusiness/triplex-next";
211
+
212
+ const OPTIONS = [
213
+ { id: "opt-1", label: "Действие 1" },
214
+ { id: "opt-2", label: "Действие 2" },
215
+ { id: "opt-3", label: "Действие 3" },
216
+ ];
217
+
218
+ export const Controlled = () => {
219
+ const buttonRef = useRef<HTMLButtonElement>(null);
220
+ const dropdownRef = useRef<HTMLDivElement>(null);
221
+ // opened должен быть определён уже на первом рендере — режим управления фиксируется на монтировании.
222
+ const [opened, setOpened] = useState(false);
223
+
224
+ // В контролируемом режиме render-функции получают то же значение opened и тот же обработчик,
225
+ // что и в неконтролируемом, — контракт render-props одинаков в обоих режимах.
226
+ const renderButton = ({ opened, setOpened }: IButtonDropdownExtendedButtonProvideProps) => (
227
+ <Button
228
+ theme={EButtonTheme.GENERAL}
229
+ size={EComponentSize.MD}
230
+ aria-haspopup="menu"
231
+ aria-expanded={opened}
232
+ onClick={() => setOpened(!opened)}
233
+ ref={buttonRef}
234
+ >
235
+ Действия
236
+ </Button>
237
+ );
238
+
239
+ const renderDropdown = ({ className, opened, setOpened }: IButtonDropdownExtendedDropdownProvideProps) => (
240
+ <ButtonDropdownExtended.Dropdown
241
+ className={className}
242
+ size={EComponentSize.MD}
243
+ width={EDropdownWidth.MIN_TARGET}
244
+ alignment={EDropdownAlignment.LEFT}
245
+ opened={opened}
246
+ setOpened={setOpened}
247
+ targetRef={buttonRef}
248
+ ref={dropdownRef}
249
+ >
250
+ <ButtonDropdownExtended.DropdownList dropdownOpened={opened} size={EComponentSize.MD}>
251
+ {OPTIONS.map((option) => (
252
+ <ButtonDropdownExtended.DropdownList.Item
253
+ key={option.id}
254
+ id={option.id}
255
+ onSelect={() => setOpened(false)}
256
+ >
257
+ {option.label}
258
+ </ButtonDropdownExtended.DropdownList.Item>
259
+ ))}
260
+ </ButtonDropdownExtended.DropdownList>
261
+ </ButtonDropdownExtended.Dropdown>
262
+ );
263
+
264
+ return (
265
+ <ButtonDropdownExtended
266
+ opened={opened}
267
+ setOpened={setOpened}
268
+ renderButton={renderButton}
269
+ renderDropdown={renderDropdown}
270
+ dropdownRef={dropdownRef}
271
+ />
272
+ );
273
+ };
274
+ ```
275
+
276
+ ### CloseOnTab
277
+
278
+ `stories/Buttons/examples/ButtonDropdownExtended/CloseOnTab.tsx`
279
+
280
+ ```tsx
281
+ import React, { useRef } from "react";
282
+ import {
283
+ Button,
284
+ ButtonDropdownExtended,
285
+ EButtonTheme,
286
+ EComponentSize,
287
+ EDropdownAlignment,
288
+ EDropdownWidth,
289
+ IButtonDropdownExtendedButtonProvideProps,
290
+ IButtonDropdownExtendedDropdownProvideProps,
291
+ } from "@sberbusiness/triplex-next";
292
+
293
+ const OPTIONS = [
294
+ { id: "opt-1", label: "Действие 1" },
295
+ { id: "opt-2", label: "Действие 2" },
296
+ { id: "opt-3", label: "Действие 3" },
297
+ ];
298
+
299
+ export const CloseOnTab = () => {
300
+ const buttonRef = useRef<HTMLButtonElement>(null);
301
+ const dropdownRef = useRef<HTMLDivElement>(null);
302
+
303
+ const renderButton = ({ opened, setOpened }: IButtonDropdownExtendedButtonProvideProps) => (
304
+ <Button
305
+ theme={EButtonTheme.GENERAL}
306
+ size={EComponentSize.MD}
307
+ aria-haspopup="menu"
308
+ aria-expanded={opened}
309
+ onClick={() => setOpened(!opened)}
310
+ ref={buttonRef}
311
+ >
312
+ Действия
313
+ </Button>
314
+ );
315
+
316
+ const renderDropdown = ({ opened, setOpened, className }: IButtonDropdownExtendedDropdownProvideProps) => (
317
+ <ButtonDropdownExtended.Dropdown
318
+ className={className}
319
+ size={EComponentSize.MD}
320
+ width={EDropdownWidth.MIN_TARGET}
321
+ alignment={EDropdownAlignment.LEFT}
322
+ opened={opened}
323
+ setOpened={setOpened}
324
+ targetRef={buttonRef}
325
+ ref={dropdownRef}
326
+ >
327
+ <ButtonDropdownExtended.DropdownList dropdownOpened={opened} size={EComponentSize.MD}>
328
+ {OPTIONS.map((option) => (
329
+ <ButtonDropdownExtended.DropdownList.Item
330
+ key={option.id}
331
+ id={option.id}
332
+ onSelect={() => setOpened(false)}
333
+ >
334
+ {option.label}
335
+ </ButtonDropdownExtended.DropdownList.Item>
336
+ ))}
337
+ </ButtonDropdownExtended.DropdownList>
338
+ </ButtonDropdownExtended.Dropdown>
339
+ );
340
+
341
+ return (
342
+ <ButtonDropdownExtended
343
+ closeOnTab
344
+ renderButton={renderButton}
345
+ renderDropdown={renderDropdown}
346
+ dropdownRef={dropdownRef}
347
+ />
348
+ );
349
+ };
350
+ ```
351
+
352
+ ### WithCustomContent
353
+
354
+ `stories/Buttons/examples/ButtonDropdownExtended/WithCustomContent.tsx`
355
+
356
+ ```tsx
357
+ import React, { useRef, useState } from "react";
358
+ import {
359
+ Button,
360
+ ButtonDropdownExtended,
361
+ EButtonTheme,
362
+ EComponentSize,
363
+ EDropdownAlignment,
364
+ ETextSize,
365
+ EDropdownWidth,
366
+ IButtonDropdownExtendedButtonProvideProps,
367
+ IButtonDropdownExtendedDropdownProvideProps,
368
+ Text,
369
+ } from "@sberbusiness/triplex-next";
370
+
371
+ export const WithCustomContent = () => {
372
+ const buttonRef = useRef<HTMLButtonElement>(null);
373
+ const dropdownRef = useRef<HTMLDivElement>(null);
374
+ const [opened, setOpened] = useState(false);
375
+
376
+ const renderButton = ({ opened, setOpened }: IButtonDropdownExtendedButtonProvideProps) => (
377
+ <Button
378
+ theme={EButtonTheme.SECONDARY}
379
+ size={EComponentSize.MD}
380
+ aria-haspopup="dialog"
381
+ aria-expanded={opened}
382
+ onClick={() => setOpened(!opened)}
383
+ ref={buttonRef}
384
+ >
385
+ Фильтр
386
+ </Button>
387
+ );
388
+
389
+ const renderDropdown = ({ className, opened, setOpened }: IButtonDropdownExtendedDropdownProvideProps) => (
390
+ <ButtonDropdownExtended.Dropdown
391
+ className={className}
392
+ size={EComponentSize.MD}
393
+ width={EDropdownWidth.CONTENT}
394
+ alignment={EDropdownAlignment.LEFT}
395
+ opened={opened}
396
+ setOpened={setOpened}
397
+ targetRef={buttonRef}
398
+ ref={dropdownRef}
399
+ >
400
+ <div style={{ display: "flex", flexDirection: "column", gap: 12, padding: 8, width: 220 }}>
401
+ <Text tag="div" size={ETextSize.B3}>
402
+ Здесь может быть любой контент: форма, фильтр или подсказка.
403
+ </Text>
404
+ <Button theme={EButtonTheme.GENERAL} size={EComponentSize.SM} onClick={() => setOpened(false)}>
405
+ Применить
406
+ </Button>
407
+ </div>
408
+ </ButtonDropdownExtended.Dropdown>
409
+ );
410
+
411
+ return (
412
+ <ButtonDropdownExtended
413
+ opened={opened}
414
+ setOpened={setOpened}
415
+ renderButton={renderButton}
416
+ renderDropdown={renderDropdown}
417
+ dropdownRef={dropdownRef}
418
+ />
419
+ );
420
+ };
421
+ ```
422
+
@@ -31,13 +31,15 @@ version: "1.0"
31
31
  |---|---|---|---|
32
32
  | `shape` | `EButtonIconShape` | `SQUIRCLE` | Форма кнопки: `SQUIRCLE` (радиус 4px) или `CIRCLE` (радиус 50%) |
33
33
  | `active` | `boolean` | `false` | Визуально активное состояние |
34
+ | `children` | `React.ReactNode` | — | Содержимое кнопки, обычно иконка из `@sberbusiness/icons-next` |
34
35
  | `className` | `string` | — | Дополнительный CSS-класс |
35
36
  | `...rest` | `React.ButtonHTMLAttributes<HTMLButtonElement> & DataAttributes` | — | Нативные атрибуты `<button>`, включая `disabled`, `aria-*`, `data-*` |
36
37
 
37
38
  ### Ограничения использования
38
39
 
39
40
  - Размер `ButtonIcon` задаётся размером переданной иконки; у компонента нет собственного prop `size`.
40
- - Корневой DOM-элемент всегда `<button>`, `type` по умолчанию принудительно установлен в `button`.
41
+ - Внешний DOM-узел — не `<button>`: компонент рендерит `IconWrapper` (`<span>` с классами `hoverable` и `displayContents`, то есть `display: contents`), а внутри него — `<button>`. Ref-target и получатель `className` / `...rest` — всегда `<button>`; классы состояний `active` и `disabled` выставляются на span-обёртке. Это важно для `>`-селекторов и логики на `parentElement`.
42
+ - `type="button"` выставляется компонентом (чтобы кнопка не триггерила submit формы), но может быть переопределён через `...rest`.
41
43
  - При расширении `EButtonIconShape` необходимо добавить класс в маппинг `Record<EButtonIconShape, string>` и в `ButtonIcon.module.less`.
42
44
 
43
45
  ---
@@ -85,6 +87,8 @@ version: "1.0"
85
87
  | `Default` | `DefaultExample.tsx` | Базовый сценарий использования |
86
88
  | `Sizes` | `SizesExample.tsx` | Разные размеры через размер иконки (16/20/24/32) |
87
89
  | `States` | `StatesExample.tsx` | Состояния `active` и `disabled` |
90
+ | `VisualTestsSquircle` | `VisualTestsExample.tsx` | Скриншот-регрессия: `:focus-visible` на squircle-кнопке |
91
+ | `VisualTestsCircle` | `VisualTestsExample.tsx` | Скриншот-регрессия: `:focus-visible` на circle-кнопке |
88
92
 
89
93
  ---
90
94
 
@@ -95,6 +99,8 @@ version: "1.0"
95
99
  | 2026-04-15 | Создан документ AI-ready для `ButtonIcon`. |
96
100
  | 2026-04-27 | Приведён в соответствие с `docs/ai/template-ai.md`: убрана секция «Файловая структура», содержимое «Ключевых особенностей реализации» перенесено в `Ограничения использования`, добавлена колонка `Example file`. |
97
101
  | 2026-07-08 | Актуализирована таблица Stories: story `Disabled` заменена на `States` (`StatesExample.tsx`). |
102
+ | 2026-07-15 | `vertical-align: top` заменён на `middle` (синхронно с `Button`) — общая опорная точка для кнопок разной высоты и центрирование относительно строки инлайн-текста. |
103
+ | 2026-07-30 | AI-рефакторинг: внутренние импорты переведены на относительные, `children` объявлен и задокументирован явно, JSDoc уточнён дефолтами; добавлены unit-тесты `__tests__/ButtonIcon.test.tsx`; в таблицу Stories добавлены отсутствовавшие `VisualTestsSquircle` и `VisualTestsCircle`. Публичный API не изменён. |
98
104
 
99
105
  ---
100
106