@sberbusiness/triplex-next-mcp-server 1.43.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.
- package/bundle/components/AlertProcess.md +3 -2
- package/bundle/components/BodyPage.md +72 -4
- package/bundle/components/CollapsibleTreeExtended.md +6 -1
- package/bundle/components/DatePickerExtended.md +409 -262
- package/bundle/components/ExpandAnimation.md +263 -218
- package/bundle/components/Footer.md +235 -278
- package/bundle/components/Island.md +36 -1
- package/bundle/components/LightBox.md +11 -6
- package/bundle/components/Overlay.md +24 -1
- package/bundle/components/Pagination.md +12 -13
- package/bundle/components/TableBasic.md +2294 -0
- package/bundle/components/Tag.md +259 -78
- package/bundle/components/TopOverlay.md +366 -90
- package/bundle/components/TreeView.md +343 -166
- package/bundle/components/TreeViewNode.md +436 -0
- package/bundle/guides/commits.md +106 -26
- package/bundle/manifest.json +25 -8
- package/bundle/release-notes/1.44.0.mdx +113 -0
- package/bundle/release-notes/1.45.0.mdx +7 -0
- package/package.json +1 -1
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
component: ExpandAnimation
|
|
3
|
-
category:
|
|
4
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
блок.
|
|
14
|
+
Обёртка, анимирующая раскрытие и сворачивание содержимого по высоте. Своего
|
|
15
|
+
состояния не хранит: раскрытость целиком задаётся пропом `expanded` со стороны
|
|
16
|
+
потребителя. Внутри — `Transition` из `react-transition-group`, поверх которого
|
|
17
|
+
надстроены измерение высоты контента и инлайн-стили фаз анимации.
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
цикла Transition.
|
|
19
|
+
Собственного оформления у компонента нет — только `transition-property: height`
|
|
20
|
+
и `transition-timing-function: ease-in-out`. Это строительный блок, а не
|
|
21
|
+
готовый UI.
|
|
26
22
|
|
|
27
|
-
Используй когда:
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
+
`IExpandAnimationProps` расширяет `React.HTMLAttributes<HTMLDivElement>` —
|
|
36
|
+
`...rest` уходит на корневой `<div>`.
|
|
61
37
|
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
`
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
150
|
-
**is** reflow trigger. Без него анимация не работает.
|
|
96
|
+
## Дизайн-токены
|
|
151
97
|
|
|
152
|
-
|
|
98
|
+
Токенов нет. `ExpandAnimation.module.less` содержит один класс без
|
|
99
|
+
CSS-переменных:
|
|
153
100
|
|
|
154
|
-
```
|
|
155
|
-
|
|
101
|
+
```text
|
|
102
|
+
.expandAnimation {
|
|
103
|
+
transition-property: height;
|
|
104
|
+
transition-timing-function: ease-in-out;
|
|
105
|
+
}
|
|
156
106
|
```
|
|
157
107
|
|
|
158
|
-
|
|
159
|
-
совместимости (вместо `findDOMNode`). `nodeRef` в ExpandAnimation
|
|
160
|
-
ставится через `setRef` (который также форвардит внешний ref).
|
|
108
|
+
Все размеры и цвета — на стороне контента и потребителя.
|
|
161
109
|
|
|
162
|
-
|
|
110
|
+
---
|
|
163
111
|
|
|
164
|
-
|
|
165
|
-
<Transition
|
|
166
|
-
{...transitionProps}
|
|
167
|
-
onEnter={handleEnter} // переопределяет transitionProps.onEnter
|
|
168
|
-
onEntered={handleEntered}
|
|
169
|
-
onExit={handleExit}
|
|
170
|
-
onExited={handleExited}
|
|
171
|
-
>
|
|
172
|
-
```
|
|
112
|
+
## Инварианты
|
|
173
113
|
|
|
174
|
-
**`
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
137
|
+
---
|
|
179
138
|
|
|
180
|
-
|
|
139
|
+
## Accessibility
|
|
181
140
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
...transitionStylesRef.current[state],
|
|
186
|
-
...style,
|
|
187
|
-
}}
|
|
188
|
-
```
|
|
141
|
+
Компонент **не выставляет ARIA-атрибутов сам** — он не знает, что за контент
|
|
142
|
+
оборачивает и какой элемент им управляет. Связывание с триггером —
|
|
143
|
+
ответственность потребителя:
|
|
189
144
|
|
|
190
|
-
|
|
191
|
-
`
|
|
192
|
-
|
|
145
|
+
- `aria-expanded` ставится на кнопку-триггер, не на блок анимации.
|
|
146
|
+
- `aria-controls` на триггере указывает на `id` блока анимации; сам `id`
|
|
147
|
+
передаётся в `ExpandAnimation` через `...rest`.
|
|
148
|
+
- `role="region"` и `aria-labelledby` при необходимости тоже передаются через
|
|
149
|
+
`...rest` (так делает `IslandAccordion`).
|
|
193
150
|
|
|
194
|
-
|
|
151
|
+
Что компонент обеспечивает сам: в свёрнутом состоянии (`exited`) корневой блок
|
|
152
|
+
получает `visibility: hidden`, поэтому скрытое содержимое не фокусируется по
|
|
153
|
+
Tab и не читается скринридером. Во время анимации сворачивания
|
|
154
|
+
(`exiting`) `visibility` ещё не выставлен — содержимое кратковременно доступно,
|
|
155
|
+
это осознанный компромисс ради видимого перехода.
|
|
195
156
|
|
|
196
|
-
|
|
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
|
-
|
|
159
|
+
---
|
|
205
160
|
|
|
206
|
-
|
|
161
|
+
## Связанные компоненты
|
|
207
162
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
##
|
|
189
|
+
## История изменений
|
|
235
190
|
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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 | Создан документ |
|