- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Toast
Временное уведомление, которое появляется на экране, чтобы проинформировать пользователей.
"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description: "Понедельник, 3 января, 18:00",
title: "Событие создано",
});
}}
variant="outline"
>
Обычное уведомление
</Button>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/toast
Добавьте ToastProvider и AnchoredToastProvider в своё приложение.
import { AnchoredToastProvider, ToastProvider } from "@/components/ui/toast"
export default function RootLayout({ children }) {
return (
<html lang="en">
<head />
<body>
<ToastProvider>
<AnchoredToastProvider>
<main>{children}</main>
</AnchoredToastProvider>
</ToastProvider>
</body>
</html>
)
}Использование
Стопка уведомлений
import { toastManager } from "@/components/ui/toast"toastManager.add({
title: "Событие создано",
description: "Понедельник, 3 января, 18:00",
})По умолчанию уведомления появляются в правом нижнем углу. Это можно изменить, задав свойство position у ToastProvider.
Допустимые значения: top-left, top-center, top-right, bottom-left, bottom-center, bottom-right. Например:
<ToastProvider position="top-center">{children}</ToastProvider>Уведомления без дублирования (upsert)
Передавайте стабильный id при вызове toastManager.add. Если уведомление с таким id уже существует, Base UI обновляет его на месте, сбрасывает таймер автоматического скрытия и увеличивает updateKey. При каждом обновлении стилизованное уведомление повторно проигрывает короткую анимацию повторного оповещения.
toastManager.add({
id: "save-status",
title: "Сохранено",
description: "Ваш черновик обновлён.",
})"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
const DEDUP_ID = "oracul-demo-dedup-toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description:
"Повторные нажатия обновляют это уведомление, а не создают новое.",
id: DEDUP_ID,
title: "Сохранено",
type: "success",
});
}}
variant="outline"
>
Одно уведомление об успехе
</Button>
);
}
Привязанные уведомления
Для уведомлений, позиционируемых относительно конкретного элемента, используйте anchoredToastManager. Обычно AnchoredToastProvider добавляется в макет вашего приложения (рядом с ToastProvider), поэтому вы можете использовать anchoredToastManager напрямую в своих компонентах:
anchoredToastManager.add({
title: "Скопировано!",
positionerProps: {
anchor: buttonRef.current,
},
})Вы также можете стилизовать привязанные уведомления как подсказки, передав data: { tooltipStyle: true }. При использовании стиля подсказки отображается только title (описание и прочее содержимое игнорируются):
anchoredToastManager.add({
title: "Скопировано!",
positionerProps: {
anchor: buttonRef.current,
},
data: {
tooltipStyle: true,
},
})"use client";
import { IconDeviceFloppy } from "@tabler/icons-react";
import { useRef } from "react";
import { Button } from "@/components/ui/button";
import { anchoredToastManager } from "@/components/ui/toast";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip";
const ANCHORED_SAVE_TOAST_ID = "oracul-demo-anchored-save-toast";
export default function Particle() {
const saveButtonRef = useRef<HTMLButtonElement>(null);
const toastTimeout = 2000;
function handleSave() {
if (!saveButtonRef.current) return;
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
id: ANCHORED_SAVE_TOAST_ID,
positionerProps: {
anchor: saveButtonRef.current,
sideOffset: 6,
},
timeout: toastTimeout,
title: "Черновик сохранён",
});
}
return (
<Tooltip>
<TooltipTrigger
delay={0}
render={
<Button
aria-label="Сохранить"
onClick={handleSave}
ref={saveButtonRef}
size="icon"
variant="outline"
/>
}
>
<IconDeviceFloppy aria-hidden="true" stroke={1.5} />
</TooltipTrigger>
<TooltipPopup>
<p>Сохранить</p>
</TooltipPopup>
</Tooltip>
);
}
Справочник API
ToastProvider
Компонент-провайдер для уведомлений в стопке. Оборачивает Toast.Provider из Base UI.
| Свойство | Тип | По умолчанию | Описание |
|---|---|---|---|
position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "bottom-right" | Положение области отображения уведомлений |
AnchoredToastProvider
Компонент-провайдер для уведомлений, привязанных к конкретным элементам. Используйте вместе с anchoredToastManager.
toastManager
Объект-менеджер для создания уведомлений в стопке. Используйте toastManager.add(), чтобы показать уведомление. Передайте тот же id при последующем вызове add, чтобы обновить это уведомление на месте (без дублирования) вместо добавления дубликата в стопку.
anchoredToastManager
Объект-менеджер для создания привязанных уведомлений. Используйте anchoredToastManager.add() вместе с positionerProps.anchor, чтобы показать уведомление, привязанное к элементу. Повторные вызовы add с тем же id обновляют уведомление на месте — так же, как и для уведомлений в стопке.
ToastViewport
Контейнер области отображения для уведомлений. Стилизованная обёртка для Toast.Viewport из Base UI.
Toast
Контейнер отдельного уведомления. Стилизованная обёртка для Toast.Root из Base UI.
ToastTitle
Текст заголовка уведомления. Стилизованная обёртка для Toast.Title из Base UI.
ToastDescription
Текст описания уведомления. Стилизованная обёртка для Toast.Description из Base UI.
ToastAction
Кнопка действия уведомления. Стилизованная обёртка для Toast.Action из Base UI.
ToastClose
Кнопка закрытия уведомления. Стилизованная обёртка для Toast.Close из Base UI.
Примеры
Со статусом
"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
export default function Particle() {
return (
<div className="flex flex-wrap gap-2">
<Button
onClick={() => {
toastManager.add({
description: "Изменения успешно сохранены.",
title: "Готово!",
type: "success",
});
}}
variant="outline"
>
Уведомление об успехе
</Button>
<Button
onClick={() => {
toastManager.add({
description: "При обработке запроса возникла проблема.",
title: "Упс! Что-то пошло не так.",
type: "error",
});
}}
variant="outline"
>
Уведомление об ошибке
</Button>
<Button
onClick={() => {
toastManager.add({
description: "Компоненты можно добавлять в приложение через CLI.",
title: "Обратите внимание!",
type: "info",
});
}}
variant="outline"
>
Информационное уведомление
</Button>
<Button
onClick={() => {
toastManager.add({
description: "Срок вашей сессии скоро истечёт.",
title: "Внимание!",
type: "warning",
});
}}
variant="outline"
>
Предупреждающее уведомление
</Button>
</div>
);
}
Загрузка
"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.add({
description: "Пожалуйста, подождите — мы обрабатываем ваш запрос.",
title: "Загрузка…",
type: "loading",
});
}}
variant="outline"
>
Уведомление о загрузке
</Button>
);
}
С действием
"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
export default function Particle() {
return (
<Button
onClick={() => {
const id = toastManager.add({
actionProps: {
children: "Отменить",
onClick: () => {
toastManager.close(id);
toastManager.add({
description: "Действие отменено.",
title: "Действие отменено",
type: "info",
});
},
},
description: "Это действие можно отменить.",
timeout: 1000000,
title: "Действие выполнено",
type: "success",
});
}}
variant="outline"
>
Выполнить действие
</Button>
);
}
Промис
"use client";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
export default function Particle() {
return (
<Button
onClick={() => {
toastManager.promise(
new Promise<string>((resolve, reject) => {
const shouldSucceed = Math.random() > 0.3;
setTimeout(() => {
if (shouldSucceed) {
resolve("Данные успешно загружены");
} else {
reject(new Error("Не удалось загрузить данные"));
}
}, 2000);
}),
{
error: () => ({
description: "Попробуйте ещё раз.",
title: "Что-то пошло не так",
}),
loading: {
description: "Промис выполняется.",
title: "Загрузка…",
},
success: (data: string) => ({
description: `Готово: ${data}`,
title: "Операция выполнена успешно!",
}),
},
);
}}
variant="outline"
>
Запустить промис
</Button>
);
}
С разной высотой
"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { toastManager } from "@/components/ui/toast";
const TEXTS = [
"Короткое сообщение.",
"Сообщение чуть подлиннее, занимающее две строки.",
"Это более длинное описание, которое намеренно занимает больше места по вертикали, чтобы показать наложение уведомлений разной высоты.",
"Ещё более длинное описание, растянутое на несколько строк, чтобы можно было проверить ограниченную высоту в свёрнутом состоянии и плавную анимацию разворачивания при наведении или фокусе на области уведомлений.",
];
export default function Particle() {
const [count, setCount] = useState(0);
function createToast() {
setCount((prev) => prev + 1);
const description = TEXTS[Math.floor(Math.random() * TEXTS.length)];
toastManager.add({
description,
title: `Уведомление ${count + 1} создано`,
});
}
return (
<Button onClick={createToast} variant="outline">
Разной высоты
</Button>
);
}
Кнопка копирования с привязанным уведомлением
"use client";
import { IconCheck, IconCopy } from "@tabler/icons-react";
import { useRef } from "react";
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard";
import { Button } from "@/components/ui/button";
import { anchoredToastManager } from "@/components/ui/toast";
import {
Tooltip,
TooltipPopup,
TooltipTrigger,
} from "@/components/ui/tooltip";
export default function Particle() {
const copyButtonRef = useRef<HTMLButtonElement>(null);
const toastTimeout = 2000;
const { copyToClipboard, isCopied } = useCopyToClipboard({
onCopy: () => {
if (copyButtonRef.current) {
anchoredToastManager.add({
data: {
tooltipStyle: true,
},
positionerProps: {
anchor: copyButtonRef.current,
},
timeout: toastTimeout,
title: "Скопировано!",
});
}
},
timeout: toastTimeout,
});
function handleCopy() {
const url = "https://localhost:4001";
copyToClipboard(url);
}
return (
<Tooltip>
<TooltipTrigger
render={
<Button
aria-label="Скопировать ссылку"
disabled={isCopied}
onClick={handleCopy}
ref={copyButtonRef}
size="icon"
variant="outline"
/>
}
>
{isCopied ? (
<IconCheck className="size-4" stroke={1.5} />
) : (
<IconCopy className="size-4" stroke={1.5} />
)}
</TooltipTrigger>
<TooltipPopup>
<p>Скопировать в буфер обмена</p>
</TooltipPopup>
</Tooltip>
);
}
Кнопка отправки с уведомлением об ошибке
"use client";
import { useRef, useState } from "react";
import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { anchoredToastManager } from "@/components/ui/toast";
export default function Particle() {
const submitRef = useRef<HTMLButtonElement>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const toastIdRef = useRef<string | null>(null);
function handleSubmit() {
if (!submitRef.current || isSubmitting) return;
if (toastIdRef.current) {
anchoredToastManager.close(toastIdRef.current);
toastIdRef.current = null;
}
setIsSubmitting(true);
new Promise<void>((_, reject) => {
setTimeout(() => {
setIsSubmitting(false);
reject(new Error("Сервер не отвечает. Повторите попытку позже."));
}, 2000);
}).catch((error: Error) => {
toastIdRef.current = anchoredToastManager.add({
description: error.message,
positionerProps: {
anchor: submitRef.current,
sideOffset: 4,
},
title: "Ошибка при отправке формы",
type: "error",
});
});
}
return (
<Button
disabled={isSubmitting}
onClick={handleSubmit}
ref={submitRef}
variant="outline"
>
{isSubmitting ? (
<>
<Spinner />
Отправка…
</>
) : (
"Отправить"
)}
</Button>
);
}
На этой странице