@sberbusiness/triplex-next-mcp-server 1.42.0-beta → 1.44.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 (35) hide show
  1. package/bundle/components/AlertProcess.md +3 -2
  2. package/bundle/components/BodyPage.md +72 -4
  3. package/bundle/components/Button.md +1 -0
  4. package/bundle/components/Checkbox.md +7 -1
  5. package/bundle/components/CheckboxTreeExtended.md +149 -248
  6. package/bundle/components/CheckboxXGroup.md +2 -0
  7. package/bundle/components/CheckboxYGroup.md +7 -2
  8. package/bundle/components/ChipGroup.md +195 -126
  9. package/bundle/components/CollapsibleTreeExtended.md +6 -1
  10. package/bundle/components/DatePickerExtended.md +409 -262
  11. package/bundle/components/ExpandAnimation.md +263 -218
  12. package/bundle/components/Footer.md +235 -278
  13. package/bundle/components/Island.md +36 -1
  14. package/bundle/components/LightBox.md +14 -3
  15. package/bundle/components/MediaWidth.md +7 -1
  16. package/bundle/components/Notification.md +21 -3
  17. package/bundle/components/Overlay.md +24 -1
  18. package/bundle/components/Pagination.md +12 -13
  19. package/bundle/components/SliderExtended.md +358 -222
  20. package/bundle/components/TableBasic.md +2294 -0
  21. package/bundle/components/TabsExtended.md +380 -194
  22. package/bundle/components/Tag.md +259 -78
  23. package/bundle/components/TopOverlay.md +366 -90
  24. package/bundle/components/TreeView.md +343 -166
  25. package/bundle/components/TreeViewNode.md +436 -0
  26. package/bundle/components/UnorderedList.md +254 -0
  27. package/bundle/components/UnorderedListExtended.md +255 -0
  28. package/bundle/components/WindowResizeListener.md +249 -0
  29. package/bundle/guides/commits.md +121 -15
  30. package/bundle/guides/stories.md +1 -1
  31. package/bundle/manifest.json +46 -20
  32. package/bundle/release-notes/1.43.0.mdx +87 -0
  33. package/bundle/release-notes/1.44.0.mdx +120 -0
  34. package/bundle/release-notes/1.45.0.mdx +7 -0
  35. package/package.json +1 -1
@@ -1,11 +1,9 @@
1
1
  ---
2
2
  component: ExpandAnimation
3
- category: Animation
4
- figma-node: ""
5
- figma-file: ""
6
- related: [AccordionBase, Spoiler, IslandAccordion, CollapsableTree]
3
+ category: ExpandAnimation
4
+ related: [AccordionBase]
7
5
  tokens: []
8
- stories: ""
6
+ stories: stories/ExpandAnimation/ExpandAnimation.stories.tsx
9
7
  version: "1.0"
10
8
  ---
11
9
 
@@ -13,269 +11,316 @@ version: "1.0"
13
11
 
14
12
  ## Назначение
15
13
 
16
- Инфраструктурный компонент-"анимация": плавное сворачивание/разворачивание
17
- блока по высоте (`height: 0 ↔ scrollHeight`) через `react-transition-group`.
18
- Используется в `AccordionBase`, `IslandAccordion`, `Spoiler`,
19
- `CollapsableTree` и любом compound'е, где нужно "раскрыть / свернуть"
20
- блок.
14
+ Обёртка, анимирующая раскрытие и сворачивание содержимого по высоте. Своего
15
+ состояния не хранит: раскрытость целиком задаётся пропом `expanded` со стороны
16
+ потребителя. Внутри — `Transition` из `react-transition-group`, поверх которого
17
+ надстроены измерение высоты контента и инлайн-стили фаз анимации.
21
18
 
22
- Ключевая задача — **анимация CSS через height** (у которой нет `auto` в
23
- transitions), реализованная через явное измерение `scrollHeight` и
24
- переключение между `0 ↔ измеренная высота` в нужные моменты жизненного
25
- цикла Transition.
19
+ Собственного оформления у компонента нет — только `transition-property: height`
20
+ и `transition-timing-function: ease-in-out`. Это строительный блок, а не
21
+ готовый UI.
26
22
 
27
- Используй когда: строишь собственный компонент с разворачивающимся
28
- блоком. Подписывайся на `onStart`/`onEnd` для syncронизации логики.
29
- Не используй как: универсальная анимация "fade" — только expand/collapse.
23
+ Используй когда: нужно плавно раскрыть/свернуть произвольный блок, а состояние
24
+ раскрытия уже живёт в потребителе (аккордеон, «показать ещё», раскрываемая
25
+ строка таблицы).
30
26
 
31
- ---
32
-
33
- ## Файловая структура
34
-
35
- ```text
36
- src/components/ExpandAnimation/
37
- ├── ExpandAnimation.tsx # forwardRef, Transition wrapper
38
- ├── index.ts # Barrel
39
- └── styles/
40
- ├── ExpandAnimation.module.less # .expandAnimation { transition-property: height; timing: ease-in-out }
41
- └── ExpandedAnimation.module.less # дубликат-остаток (см. ниже)
42
- ```
43
-
44
- Особенности:
45
- - **`forwardRef`** — есть.
46
- - **`ExpandedAnimation.module.less`** — дублирующийся файл с тем же
47
- содержимым (`.expandedAnimation` вместо `.expandAnimation`). Не
48
- импортируется компонентом, legacy-остаток. **Не удалять
49
- отдельным коммитом без проверки потребителей** — возможно, кто-то
50
- импортирует его напрямую.
51
- - **Нет `__tests__`** — покрывается через потребителей
52
- (AccordionBase-tests проверяют анимацию косвенно).
53
- - **Нет stories** — внутренний/инфраструктурный.
54
- - **Без `data-tx`** — не ставит атрибут версии.
27
+ Не используй когда: нужен готовый аккордеон с внутренним состоянием, id-связкой
28
+ заголовка и тела и ARIA-атрибутами — для этого есть `AccordionBase` и
29
+ построенные на нём компоненты.
55
30
 
56
31
  ---
57
32
 
58
33
  ## Варианты и props
59
34
 
60
- ### `IExpandAnimationProps extends React.HTMLAttributes<HTMLDivElement>`
35
+ `IExpandAnimationProps` расширяет `React.HTMLAttributes<HTMLDivElement>` —
36
+ `...rest` уходит на корневой `<div>`.
61
37
 
62
- | Prop | Тип | Default | Описание |
63
- |---|---|---|---|
64
- | `expanded` | `boolean` | — (обязателен) | Целевое состояние: раскрыт/свёрнут. Controlled. |
65
- | `animationTime` | `number` | `300` (мс) | Длительность transition. Идёт в `timeout` Transition'а и в `transitionDuration` inline-стиля. |
66
- | `onStart` | `() => void` | — | Срабатывает в начале анимации (onEnter и onExit). |
67
- | `onEnd` | `() => void` | — | Срабатывает в конце анимации (onEntered и onExited). |
68
- | `transitionProps` | `React.ComponentProps<typeof Transition<HTMLDivElement>>` | — | Дополнительные пропсы для `Transition`. Колбэки мержатся (см. ниже). |
69
- | `className`, `style`, `children`, `...HTMLDivAttributes` | — | — | Стандартные. |
38
+ ### Обязательные props
70
39
 
71
- ---
40
+ | Prop | Тип | Описание |
41
+ |---|---|---|
42
+ | `expanded` | `boolean` | Развёрнут ли блок. Управляемое свойство, внутреннего состояния нет |
72
43
 
73
- ## Ключевые особенности реализации
44
+ ### Опциональные props
74
45
 
75
- ### Transition статусы → inline-styles
46
+ | Prop | Тип | По умолчанию | Описание |
47
+ |---|---|---|---|
48
+ | `animationTime` | `number` | `300` | Длительность анимации в мс. Одновременно уходит в `timeout` у `Transition` и в инлайн-стиль `transition-duration` — задавать их врозь нельзя |
49
+ | `onStart` | `() => void` | — | Начало анимации. Вызывается **и при разворачивании, и при сворачивании** (из `onEnter` и `onExit`) |
50
+ | `onEnd` | `() => void` | — | Конец анимации. Вызывается **и при разворачивании, и при сворачивании** (из `onEntered` и `onExited`) |
51
+ | `transitionProps` | `React.ComponentProps<typeof Transition<HTMLDivElement>>` | — | Сквозные props для внутреннего `Transition` |
52
+ | `className` | `string` | — | Мерджится с собственным классом через `clsx` |
53
+ | `style` | `React.CSSProperties` | — | Разливается **после** стилей фазы анимации, поэтому переопределяет `height` / `overflow` / `visibility`. Передавать `height` здесь — значит сломать анимацию |
76
54
 
77
- ```ts
78
- const transitionStylesRef = useRef<Record<TransitionStatus, React.CSSProperties>>({
79
- entering: { height: undefined, overflow: "hidden" }, // height задаётся в onEnter
80
- entered: {},
81
- exiting: { height: 0, overflow: "hidden" },
82
- exited: { height: 0, overflow: "hidden", visibility: "hidden" },
83
- unmounted: {},
84
- });
85
- ```
55
+ ### Как работает анимация
86
56
 
87
- **Состояния:**
88
- - **`entering`** — начало раскрытия. `height` проставляется **в
89
- момент onEnter**, до этого `undefined` (браузер рендерит от 0 к
90
- `scrollHeight`).
91
- - **`entered`** — раскрыт. Пусто — браузер держит естественную высоту
92
- (`auto`).
93
- - **`exiting`** — начало сворачивания. `height: 0` + `overflow: hidden`.
94
- - **`exited`** — свёрнут. `height: 0 + overflow: hidden + visibility: hidden`.
95
- `visibility: hidden` убирает из tab-order.
96
- - **`unmounted`** — компонент размонтирован, пусто.
97
-
98
- **useRef** — объект не пересоздаётся между рендерами, ссылки на
99
- `CSSProperties` стабильны.
100
-
101
- ### Измерение высоты в onEnter
102
-
103
- ```ts
104
- const handleEnter = (appearing) => {
105
- if (nodeRef.current) {
106
- transitionStylesRef.current["entering"].height = nodeRef.current.scrollHeight;
107
- }
108
- onStart?.();
109
- transitionProps?.onEnter?.(appearing);
110
- };
111
- ```
112
-
113
- **`scrollHeight` читается из DOM** — это реальная высота содержимого.
114
- Браузер проанимирует от `0` до этого значения. Почему в onEnter:
115
- перед ним height был `0`, после onEnter — DOM получит конкретное
116
- значение для transition to.
117
-
118
- **Проблема "height: auto в transitions":** CSS не может transition
119
- между `0` и `auto`. ExpandAnimation решает это, подставляя реальное
120
- число.
121
-
122
- ### onExit: hack с `scrollHeight` и force reflow
123
-
124
- ```ts
125
- const handleExit = () => {
126
- if (nodeRef.current) {
127
- nodeRef.current.style.height = nodeRef.current.scrollHeight + "px";
128
- // trigger reflow
129
- // eslint-disable-next-line @typescript-eslint/no-unused-expressions
130
- nodeRef.current.scrollHeight;
131
- }
132
- onStart?.();
133
- transitionProps?.onExit?.();
134
- };
135
- ```
57
+ | Фаза `TransitionStatus` | Инлайн-стили корневого `<div>` |
58
+ |---|---|
59
+ | `entering` | `height: <scrollHeight>`, `overflow: hidden` |
60
+ | `entered` | — (высота отпускается в `auto`) |
61
+ | `exiting` | `height: 0`, `overflow: hidden` |
62
+ | `exited` | `height: 0`, `overflow: hidden`, `visibility: hidden` |
63
+ | `unmounted` | — |
64
+
65
+ - **Разворачивание.** В `onEnter` измеряется `scrollHeight` контента и
66
+ записывается в карту стилей фазы `entering` — до того, как `Transition`
67
+ отрисует эту фазу. Без этого анимировать не от чего.
68
+ - **Сворачивание.** В `onExit` текущая высота фиксируется прямой записью в
69
+ `node.style.height` (вместо `auto`), затем принудительно читается
70
+ `scrollHeight` для reflow — иначе браузер схлопнет переход в один кадр.
71
+ - **`entered` без высоты** — контент может свободно менять размер, пока блок
72
+ раскрыт.
73
+
74
+ ### `transitionProps` — порядок применения
75
+
76
+ `transitionProps` разливается в `Transition` **после** `in`, `timeout` и
77
+ `nodeRef`, но **до** обработчиков. Практические следствия:
78
+
79
+ - `in`, `timeout`, `nodeRef` из `transitionProps` **перекроют** внутренние
80
+ значения. Передача своего `nodeRef` ломает измерение высоты — не делать.
81
+ - `onEnter` / `onEntered` / `onExit` / `onExited` перекрыть нельзя: компонент
82
+ ставит свои обработчики последними и вызывает пользовательские внутри них,
83
+ после `onStart` / `onEnd`.
84
+ - Остальные обработчики (`onEntering`, `onExiting`) проходят насквозь без
85
+ обёртки.
86
+
87
+ **Известное неудобство типизации:** `transitionProps` типизирован полным
88
+ `ComponentProps<typeof Transition<HTMLDivElement>>`, где `children` и `timeout`
89
+ обязательны. Передать объект с одними обработчиками без каста нельзя, хотя
90
+ `children` / `in` / `timeout` компонент задаёт сам. Сузить тип (например до
91
+ `Partial<>` или `Omit<>`) — breaking change публичного API, отдельное решение
92
+ мейнтейнера.
136
93
 
137
- **Последовательность:**
138
- 1. Ставим `style.height = scrollHeight + "px"` — конкретное значение,
139
- от которого пойдёт transition.
140
- 2. **Force reflow** — читаем `scrollHeight` "впустую" (expression с
141
- lint-ignore). Без этого браузер может "оптимизировать" и не
142
- зафиксировать промежуточный state — transition пойдёт от "auto"
143
- сразу к "0" без плавности.
144
- 3. Вызываем `onStart` и обёрнутый колбэк.
145
- 4. React-Transition-Group затем переключит в `exiting`, где inline
146
- style из `transitionStylesRef` ставит `height: 0` → браузер
147
- анимирует от запомненной высоты до 0.
94
+ ---
148
95
 
149
- **Не удалять `nodeRef.current.scrollHeight;` "пустое" чтение** — это
150
- **is** reflow trigger. Без него анимация не работает.
96
+ ## Дизайн-токены
151
97
 
152
- ### `nodeRef` — обязательный для Transition с `nodeRef` паттерном
98
+ Токенов нет. `ExpandAnimation.module.less` содержит один класс без
99
+ CSS-переменных:
153
100
 
154
- ```tsx
155
- <Transition in={expanded} timeout={animationTime} nodeRef={nodeRef} ...>
101
+ ```text
102
+ .expandAnimation {
103
+ transition-property: height;
104
+ transition-timing-function: ease-in-out;
105
+ }
156
106
  ```
157
107
 
158
- `react-transition-group` v4+ требует `nodeRef` для strict-mode
159
- совместимости (вместо `findDOMNode`). `nodeRef` в ExpandAnimation
160
- ставится через `setRef` (который также форвардит внешний ref).
108
+ Все размеры и цвета — на стороне контента и потребителя.
161
109
 
162
- ### Слияние колбэков Transition
110
+ ---
163
111
 
164
- ```tsx
165
- <Transition
166
- {...transitionProps}
167
- onEnter={handleEnter} // переопределяет transitionProps.onEnter
168
- onEntered={handleEntered}
169
- onExit={handleExit}
170
- onExited={handleExited}
171
- >
172
- ```
112
+ ## Инварианты
173
113
 
174
- **`handle*` вызывают `transitionProps?.onEnter?.(...)` и т. п.** — так
175
- что если пользователь передал свои `onEnter` через transitionProps,
176
- они всё равно выполнятся, но **после** наших handler'ов.
114
+ - **`forwardRef` на компоненте — не убирать.** Ref идёт на корневой `<div>`,
115
+ тот же элемент, что получает `className`, `style` и `...rest`.
116
+ - **Внутренний `nodeRef` и пользовательский `ref` совмещаются в `setRef`.**
117
+ `nodeRef` нужен `Transition` (React 18 StrictMode-safe вместо
118
+ `findDOMNode`) и используется для измерения высоты. Убрать совмещение —
119
+ сломать либо анимацию, либо ref потребителя.
120
+ - **Карта стилей фаз живёт в `useRef`, а не пересоздаётся на рендере.**
121
+ `onEnter` мутирует `entering.height` до рендера фазы `entering`; на свежем
122
+ объекте мутация потерялась бы, и раскрытие анимировалось бы от нуля к нулю.
123
+ - **`animationTime` управляет и `timeout`, и `transition-duration`.**
124
+ Рассинхрон приведёт к тому, что `onEnd` сработает не в конце анимации.
125
+ - **`visibility: hidden` в фазе `exited`** — свёрнутое содержимое не попадает
126
+ в порядок обхода по Tab. Убирать нельзя: на это опираются потребители —
127
+ например, `CollapsibleTree-ai.md` фиксирует это как гарантию доступности
128
+ свёрнутой ветки.
129
+ - **Публичные экспорты barrel** (`src/components/ExpandAnimation/index.ts`):
130
+ `ExpandAnimation`, `IExpandAnimationProps`. Переименование — breaking change.
131
+ - **`displayName = "ExpandAnimation"`.**
132
+ - **Корневой DOM — `<div>`.** Тег не настраивается.
133
+ - **React 17-совместимость.** Компонент уходит в ветку `release-0`; React
134
+ 18-only API (`useId`, `useSyncExternalStore`, `useInsertionEffect`)
135
+ использовать нельзя.
177
136
 
178
- **Порядок:** наш handler → (onStart для start-ветки) → user's onEnter/Exit.
137
+ ---
179
138
 
180
- ### `style` inline — слияние трёх источников
139
+ ## Accessibility
181
140
 
182
- ```tsx
183
- style={{
184
- transitionDuration: animationTime + "ms",
185
- ...transitionStylesRef.current[state],
186
- ...style,
187
- }}
188
- ```
141
+ Компонент **не выставляет ARIA-атрибутов сам** — он не знает, что за контент
142
+ оборачивает и какой элемент им управляет. Связывание с триггером —
143
+ ответственность потребителя:
189
144
 
190
- **Порядок:** дефолт duration → наш state-style → пользовательский
191
- `style`. Пользовательский стиль побеждает — он может подрезать
192
- наше поведение, если потребитель этого хочет.
145
+ - `aria-expanded` ставится на кнопку-триггер, не на блок анимации.
146
+ - `aria-controls` на триггере указывает на `id` блока анимации; сам `id`
147
+ передаётся в `ExpandAnimation` через `...rest`.
148
+ - `role="region"` и `aria-labelledby` при необходимости тоже передаются через
149
+ `...rest` (так делает `IslandAccordion`).
193
150
 
194
- ### `setRef` — double-duty ref
151
+ Что компонент обеспечивает сам: в свёрнутом состоянии (`exited`) корневой блок
152
+ получает `visibility: hidden`, поэтому скрытое содержимое не фокусируется по
153
+ Tab и не читается скринридером. Во время анимации сворачивания
154
+ (`exiting`) `visibility` ещё не выставлен — содержимое кратковременно доступно,
155
+ это осознанный компромисс ради видимого перехода.
195
156
 
196
- ```ts
197
- const setRef = (instance) => {
198
- nodeRef.current = instance;
199
- if (typeof ref === "function") ref(instance);
200
- else if (ref) ref.current = instance;
201
- };
202
- ```
157
+ Клавиатурных обработчиков у компонента нет.
203
158
 
204
- Внутренний `nodeRef` (для Transition) + внешний forwarded ref.
159
+ ---
205
160
 
206
- ### Дубликат файла `ExpandedAnimation.module.less`
161
+ ## Связанные компоненты
207
162
 
208
- Файл существует, но **не используется** компонентом. Вероятно legacy
209
- от переименования. Не удалять без проверки прямого импорта из
210
- потребителей (`grep -r "ExpandedAnimation"`).
163
+ - `AccordionBase` (`src/components/AccordionBase`) — слой выше: хранит
164
+ состояние раскрытия (controlled / uncontrolled), генерирует id и
165
+ ARIA-атрибуты для связки header ↔ body, рендерит `ExpandAnimation` внутри.
166
+ Если нужен аккордеон, а не просто анимация высоты — начинай с него.
167
+ - Потребители внутри библиотеки, чьё поведение зависит от этого компонента:
168
+ `IslandAccordion` (передаёт `transitionProps`, `role="region"`, `id`),
169
+ `IslandWidget` и `IslandWidgetExtraFooter`, `CollapsibleTreeExtended`
170
+ (через `AccordionBase`), `Table/TableBasicSettings`. Правки в фазах анимации
171
+ и в `onStart` / `onEnd` нужно сверять с ними.
211
172
 
212
173
  ---
213
174
 
214
- ## Инварианты
175
+ ## Stories
176
+
177
+ Основные истории: `stories/ExpandAnimation/ExpandAnimation.stories.tsx`
178
+ Файлы примеров: `stories/ExpandAnimation/examples/`
215
179
 
216
- - **`nodeRef.current.scrollHeight;` в handleExit** — force reflow.
217
- Не удалять. Без него transition ломается.
218
- - **`expanded` — controlled only** — нет defaultExpanded.
219
- - **Состояние inline-styles в `transitionStylesRef`** — мутабельные
220
- объекты, храним в ref чтобы не пересоздавать.
221
- - **Transition `nodeRef`** — обязателен (v4+ API).
222
- - **`visibility: hidden` в exited** — убирает из tab-order. Не
223
- менять на `display: none` (сломает transition).
224
- - **`transitionProps` колбэки вызываются после наших** — порядок
225
- важен, не менять.
226
- - **`animationTime` синхронизирован между `timeout` Transition'а и
227
- `transitionDuration`** — не менять одно без другого.
228
- - **`data-tx`** — не проставляется (нестандартно для библиотеки,
229
- возможен debt).
230
- - **`forwardRef`** — есть.
180
+ | Story | Example file | Что демонстрирует |
181
+ |---|---|---|
182
+ | `Playground` | `Playground.tsx` | Интерактивный контроль `expanded` и `animationTime` |
183
+ | `Default` | `Default.tsx` | Управляемое раскрытие кнопкой со связкой `aria-expanded` / `aria-controls` |
184
+ | `Animation time` | `AnimationTime.tsx` | Длительности 100 / 300 / 1000 мс |
185
+ | `Callbacks` | `Callbacks.tsx` | `onStart` / `onEnd` как индикатор идущей анимации |
231
186
 
232
187
  ---
233
188
 
234
- ## Accessibility
189
+ ## История изменений
235
190
 
236
- - **`visibility: hidden` в свёрнутом состоянии** — убирает элементы
237
- из tab-order и screen reader'а. Важный инвариант для "раскрытые
238
- элементы не доступны клавиатурой".
239
- - **`overflow: hidden`** в анимации — скрывает частично видимый
240
- контент.
241
- - **`aria-expanded`** — **не** проставляется ExpandAnimation. Это
242
- задача потребителя (AccordionBase делает это).
243
- - **`onStart`/`onEnd`** — позволяют потребителю обновить ARIA-state
244
- в правильный момент (например, объявить "свернуто" для screen
245
- reader'а после завершения).
191
+ | Дата | Изменение |
192
+ |---|---|
193
+ | 2026-08-21 | Создан документ. Зафиксированы: карта стилей по фазам анимации, требование хранить её в ref, порядок применения `transitionProps`, контракт `visibility: hidden` в `exited`, отсутствие собственных ARIA-атрибутов и токенов |
246
194
 
247
195
  ---
248
196
 
249
- ## Дизайн-токены
197
+ ## Примеры использования
250
198
 
251
- **Нет.** Чистая инфраструктура анимации.
199
+ ### Default
252
200
 
253
- ---
201
+ `stories/ExpandAnimation/examples/Default.tsx`
254
202
 
255
- ## Связанные компоненты
203
+ ```tsx
204
+ import React, { useState } from "react";
205
+ import { Button, EButtonTheme, EComponentSize, ETextSize, ExpandAnimation, Text } from "@sberbusiness/triplex-next";
206
+
207
+ export const Default = () => {
208
+ const [expanded, setExpanded] = useState(true);
209
+
210
+ return (
211
+ <div style={{ maxWidth: 480 }}>
212
+ <Button
213
+ theme={EButtonTheme.SECONDARY}
214
+ size={EComponentSize.MD}
215
+ aria-expanded={expanded}
216
+ aria-controls="expand-animation-default"
217
+ onClick={() => setExpanded((prev) => !prev)}
218
+ >
219
+ {expanded ? "Свернуть" : "Развернуть"}
220
+ </Button>
221
+
222
+ <ExpandAnimation id="expand-animation-default" expanded={expanded}>
223
+ <Text tag="div" size={ETextSize.B1} style={{ paddingTop: 16 }}>
224
+ Содержимое разворачивается и сворачивается по высоте. В свёрнутом состоянии блок скрыт через
225
+ visibility, поэтому не получает фокус по Tab.
226
+ </Text>
227
+ </ExpandAnimation>
228
+ </div>
229
+ );
230
+ };
231
+ ```
256
232
 
257
- - `AccordionBase` (`src/components/AccordionBase/protected/AccordionBase`) —
258
- главный потребитель. Оборачивает ExpandAnimation + добавляет
259
- `aria-expanded`, заголовок, клик.
260
- - `IslandAccordion` (`src/components/IslandAccordion`) — использует
261
- ExpandAnimation напрямую или через AccordionBase.
262
- - `Spoiler` (`src/components/Spoiler`) — альтернатива без заголовка-
263
- кнопки.
264
- - `CollapsableTree` (`src/components/CollapsableTree`) — composes
265
- ExpandAnimation через AccordionBase.
266
- - `react-transition-group` — внешняя зависимость.
233
+ ### Animation time
267
234
 
268
- ---
235
+ `stories/ExpandAnimation/examples/AnimationTime.tsx`
269
236
 
270
- ## Stories
237
+ ```tsx
238
+ import React, { useState } from "react";
239
+ import { Button, EButtonTheme, EComponentSize, ETextSize, ExpandAnimation, Text } from "@sberbusiness/triplex-next";
240
+
241
+ const ANIMATION_TIMES = [100, 300, 1000];
242
+
243
+ interface IAnimationTimeItemProps {
244
+ animationTime: number;
245
+ }
246
+
247
+ const AnimationTimeItem = ({ animationTime }: IAnimationTimeItemProps) => {
248
+ const [expanded, setExpanded] = useState(true);
249
+ const contentId = `expand-animation-time-${animationTime}`;
250
+
251
+ return (
252
+ <div style={{ maxWidth: 480 }}>
253
+ <div style={{ marginBottom: "8px", fontSize: "16px", fontWeight: "700" }}>{animationTime} мс</div>
254
+
255
+ <Button
256
+ theme={EButtonTheme.SECONDARY}
257
+ size={EComponentSize.MD}
258
+ aria-expanded={expanded}
259
+ aria-controls={contentId}
260
+ onClick={() => setExpanded((prev) => !prev)}
261
+ >
262
+ {expanded ? "Свернуть" : "Развернуть"}
263
+ </Button>
264
+
265
+ <ExpandAnimation id={contentId} expanded={expanded} animationTime={animationTime}>
266
+ <Text tag="div" size={ETextSize.B1} style={{ paddingTop: 16 }}>
267
+ Скорость раскрытия задаётся свойством animationTime.
268
+ </Text>
269
+ </ExpandAnimation>
270
+ </div>
271
+ );
272
+ };
271
273
 
272
- **Нет собственных stories.** Инфраструктурный компонент. Видно в
273
- действии через AccordionBase/Spoiler/IslandAccordion stories.
274
+ export const AnimationTime = () => (
275
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
276
+ {ANIMATION_TIMES.map((animationTime) => (
277
+ <AnimationTimeItem key={animationTime} animationTime={animationTime} />
278
+ ))}
279
+ </div>
280
+ );
281
+ ```
274
282
 
275
- ---
283
+ ### Callbacks
276
284
 
277
- ## История изменений
285
+ `stories/ExpandAnimation/examples/Callbacks.tsx`
286
+
287
+ ```tsx
288
+ import React, { useState } from "react";
289
+ import { Button, EButtonTheme, EComponentSize, ETextSize, ExpandAnimation, Text } from "@sberbusiness/triplex-next";
290
+
291
+ export const Callbacks = () => {
292
+ const [expanded, setExpanded] = useState(true);
293
+ const [animating, setAnimating] = useState(false);
294
+
295
+ return (
296
+ <div style={{ maxWidth: 480 }}>
297
+ <Button
298
+ theme={EButtonTheme.SECONDARY}
299
+ size={EComponentSize.MD}
300
+ aria-expanded={expanded}
301
+ aria-controls="expand-animation-callbacks"
302
+ onClick={() => setExpanded((prev) => !prev)}
303
+ >
304
+ {expanded ? "Свернуть" : "Развернуть"}
305
+ </Button>
306
+
307
+ <Text tag="div" size={ETextSize.B1} style={{ paddingTop: 8 }}>
308
+ Состояние: {animating ? "анимация идёт" : "анимация завершена"}
309
+ </Text>
310
+
311
+ <ExpandAnimation
312
+ id="expand-animation-callbacks"
313
+ expanded={expanded}
314
+ onStart={() => setAnimating(true)}
315
+ onEnd={() => setAnimating(false)}
316
+ >
317
+ <Text tag="div" size={ETextSize.B1} style={{ paddingTop: 16 }}>
318
+ onStart вызывается в начале анимации, onEnd — в конце. Оба коллбэка срабатывают и при
319
+ разворачивании, и при сворачивании.
320
+ </Text>
321
+ </ExpandAnimation>
322
+ </div>
323
+ );
324
+ };
325
+ ```
278
326
 
279
- | Дата | Изменение |
280
- |---|---|
281
- | 2026-04-18 | Создан документ |