ToastUserAction
Короткое уведомление об успешно выполненном действии пользователя — copy/save/delete и т.п. Один label, без описания. Опционально — ссылка-отмена и таймер обратного отсчёта. Auto-dismiss 2s.
Когда использовать
- Подтвердить локальное действие («Скопировано», «Сохранено», «Удалено»).
- Дать возможность отмены через
action.onClick. - Показать short-lived loading-состояние через
loading: true+ потомupdate.*.
Когда не нужен UserAction:
- Системные события / асинхронные результаты — используй SystemEvent.
- Прогресс загрузки — используй Upload.
Анатомия
Appearance (default neutral)
neutral— нейтральный feedback (дефолт).success— успех.warning— предупреждение.error— ошибка.
Loading (default false)
Заменяет иконку appearance на анимированный Sun-спиннер. Используется для in-progress операций с последующим toaster.userAction.update.success(id, ...).
Timer (default false)
24×24 SVG-кольцо обратного отсчёта вокруг иконки. Длительность совпадает с AUTO_CLOSE_TIME[UserAction] (2s). Pause при hover контейнера.
Установка
import { toaster, ToasterContainer, TOASTER_TYPE } from '@cloud-ru/ds-toaster'
Примеры использования
Базовые appearance
import { APPEARANCE, Button } from '@cloud-ru/ds-button';
import { PortalContextProvider } from '@cloud-ru/ds-portal-context';
import { POSITION_USER_ACTION, toaster, TOASTER_TYPE, ToasterContainer } from '@cloud-ru/ds-toaster';
const CONTAINER_ID = 'demo-user-action-basic';
export function UserActionBasic() {
const dismiss = () => toaster.userAction.dismiss({ containerId: CONTAINER_ID });
return (
<PortalContextProvider>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
<Button
appearance={APPEARANCE.Primary}
label='Success'
onClick={() => toaster.userAction.success({ label: 'Скопировано', containerId: CONTAINER_ID })}
/>
<Button
appearance={APPEARANCE.Critical}
label='Error'
onClick={() => toaster.userAction.error({ label: 'Не удалось', containerId: CONTAINER_ID })}
/>
<Button
appearance={APPEARANCE.Neutral}
label='С отменой'
onClick={() =>
toaster.userAction.neutral({
label: 'Сохранено',
action: {
label: 'Отменить',
onClick: () => {
dismiss();
toaster.userAction.success({ label: 'Отменено', containerId: CONTAINER_ID });
},
},
containerId: CONTAINER_ID,
})
}
/>
<Button appearance={APPEARANCE.Neutral} label='Закрыть все' onClick={dismiss} />
<ToasterContainer
type={TOASTER_TYPE.UserAction}
containerId={CONTAINER_ID}
position={POSITION_USER_ACTION.BottomCenter}
limit={2}
autoClose={2000}
/>
</div>
</PortalContextProvider>
);
}Loading → success
import { APPEARANCE, Button } from '@cloud-ru/ds-button';
import { PortalContextProvider } from '@cloud-ru/ds-portal-context';
import { POSITION_USER_ACTION, toaster, TOASTER_TYPE, ToasterContainer } from '@cloud-ru/ds-toaster';
const CONTAINER_ID = 'demo-user-action-loading';
const OPERATION_MS = 1500;
export function UserActionLoading() {
const run = async () => {
const id = await toaster.userAction.neutral({
label: 'Загрузка…',
loading: true,
containerId: CONTAINER_ID,
});
setTimeout(() => {
toaster.userAction.update.success(id, { label: 'Готово!', containerId: CONTAINER_ID });
}, OPERATION_MS);
};
return (
<PortalContextProvider>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
<Button appearance={APPEARANCE.Primary} label='Запустить операцию' onClick={run} />
<Button
appearance={APPEARANCE.Neutral}
label='Закрыть все'
onClick={() => toaster.userAction.dismiss({ containerId: CONTAINER_ID })}
/>
<ToasterContainer
type={TOASTER_TYPE.UserAction}
containerId={CONTAINER_ID}
position={POSITION_USER_ACTION.BottomCenter}
limit={2}
autoClose={2000}
/>
</div>
</PortalContextProvider>
);
}С таймером обратного отсчёта
import { APPEARANCE, Button } from '@cloud-ru/ds-button';
import { PortalContextProvider } from '@cloud-ru/ds-portal-context';
import { POSITION_USER_ACTION, toaster, TOASTER_TYPE, ToasterContainer } from '@cloud-ru/ds-toaster';
const CONTAINER_ID = 'demo-user-action-timer';
export function UserActionTimer() {
const dismiss = () => toaster.userAction.dismiss({ containerId: CONTAINER_ID });
return (
<PortalContextProvider>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
<Button
appearance={APPEARANCE.Primary}
label='Тост с таймером'
onClick={() =>
toaster.userAction.success({
label: 'Скопировано',
timer: true,
containerId: CONTAINER_ID,
})
}
/>
<Button
appearance={APPEARANCE.Neutral}
label='С таймером и ссылкой'
onClick={() =>
toaster.userAction.neutral({
label: 'Сохранено',
timer: true,
action: { label: 'Отменить', onClick: dismiss },
containerId: CONTAINER_ID,
})
}
/>
<Button appearance={APPEARANCE.Neutral} label='Закрыть все' onClick={dismiss} />
<ToasterContainer
type={TOASTER_TYPE.UserAction}
containerId={CONTAINER_ID}
position={POSITION_USER_ACTION.BottomCenter}
limit={2}
autoClose={4000}
/>
</div>
</PortalContextProvider>
);
}Положения контейнера
import { APPEARANCE, Button } from '@cloud-ru/ds-button';
import { PortalContextProvider } from '@cloud-ru/ds-portal-context';
import { POSITION_USER_ACTION, toaster, TOASTER_TYPE, ToasterContainer, UserActionPosition } from '@cloud-ru/ds-toaster';
const POSITIONS: UserActionPosition[] = Object.values(POSITION_USER_ACTION);
const containerIdFor = (position: UserActionPosition) => `demo-user-action-position-${position}`;
export function UserActionPositions() {
const fire = (position: UserActionPosition) =>
toaster.userAction.success({
label: `Тост в ${position}`,
containerId: containerIdFor(position),
});
const dismissAll = () =>
POSITIONS.forEach(position => toaster.userAction.dismiss({ containerId: containerIdFor(position) }));
return (
<PortalContextProvider>
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'center' }}>
{POSITIONS.map(position => (
<Button key={position} appearance={APPEARANCE.Primary} label={position} onClick={() => fire(position)} />
))}
<Button appearance={APPEARANCE.Neutral} label='Закрыть все' onClick={dismissAll} />
{POSITIONS.map(position => (
<ToasterContainer
key={position}
type={TOASTER_TYPE.UserAction}
containerId={containerIdFor(position)}
position={position}
limit={2}
autoClose={3000}
/>
))}
</div>
</PortalContextProvider>
);
}Императивный API
Тосты создаются через toaster.userAction.<appearance>({...}) — один метод на каждый visual вариант. Обновление существующего тоста — toaster.userAction.update.<appearance>(id, {...}). Закрытие — toaster.userAction.dismiss(...).
import { toaster, type UserActionOptions } from '@cloud-ru/ds-toaster'
const id = await toaster.userAction.warning({
id: 'unsaved-changes',
containerId: 'main',
title: 'Есть несохранённые изменения',
description: 'Сохранить перед выходом?',
actions: [
{ label: 'Сохранить', onClick: save },
{ label: 'Отмена', onClick: dismiss },
],
autoClose: false,
onClose: id => log('closed', id),
})
toaster.userAction.dismiss(id)
Props
Types
ToastUserActionProps| Prop | Type | Default | Required | Description |
|---|---|---|---|---|
action | ToastUserActionAction<"button"> | ToastUserActionAction<"a"> | — | no | Slot для action-кнопки (по умолчанию `<button>`, через `as='a'` — линка) |
appearance | "error" | "neutral" | "success" | "warning" | neutral | no | Цветовая схема (status в Figma) |
className | string | — | no | Доп. класс корневого элемента |
closeToast | (() => void) | — | no | |
data-test-id | string | — | no | |
label | string | — | yes | Текст-уведомление |
loading | boolean | false | no | Loading-состояние с заменой иконки на Sun-спиннер |
timer | boolean | false | no | Показывать ли таймер обратного отсчёта (24×24 SVG-кольцо). Анимация = `AUTO_CLOSE_TIME[UserAction]`. Пауза при hover. Figma axis: `timer=true|false`. |
toastProps | { autoClose?: number | false; } | — | no |
Unions
Types
ToastUserActionProps
ToastUserActionAction
Unions
ToastUserActionAppearance
Storybook
Playground — все props через controls (appearance, loading, timer, action-preset, width):
VisualMatrix · все оси
Appearance × Width, Loading & Timer, набор action-пресетов (none / labelOnly / link):
Связанные стори контейнера:
- Mobile —
width: full+position: bottom-center, кастомныйtoasterParent, swipe-to-dismiss:components-toaster-toaster--composition. - Triggers — шорткаты
toaster.userAction.*рядом с другими типами:components-toaster-toaster--imperative-api.
Figma
Смотри также
- Toaster (root) — обзор пакета.
- SystemEvent — системные события.