Component
Toaster
Sonner notifications in the brand’s dress.
betav0.3.0 Chromium 103 ui-scale safe
Install
Copies the file into
components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.bunx shadcn@3 add @fastrp/toasterExamples
Default
API
<Toaster />Takes the props of what it wrapsSonner, dressed in the brand: square, a band ground, the status in a left rule. Pass `theme` from the app's own theme state — the component does not read one, so it runs in the game, which has no theme provider. Mount once; call `toast()` anywhere.
Dependencies
npm
- sonner@^2.0.7
Registry
Source
components/ds/toaster.tsxShowHide
'use client';
import * as React from 'react';
import { Toaster as Sonner, toast, useSonner } from 'sonner';
import { type UiSound, useUiSound } from '@/components/ds/ui-sounds';
// @fast-ds [email protected]
export type ToasterProps = React.ComponentProps<typeof Sonner>;
/**
* Sonner, dressed in the brand: square, a band ground, the status in a left rule. Pass `theme`
* from the app's own theme state — the component does not read one, so it runs in the game, which
* has no theme provider. Mount once; call `toast()` anywhere.
*/
function Toaster({ toastOptions, ...props }: ToasterProps) {
useToastSounds();
return (
<Sonner
className="toaster group"
toastOptions={{
...toastOptions,
classNames: {
toast:
'group toast !rounded-lg border !border-foreground/10 !bg-[hsl(var(--panel)/0.96)] !text-foreground !shadow-2xl data-[type=success]:border-l-2 data-[type=success]:!border-l-success data-[type=error]:border-l-2 data-[type=error]:!border-l-danger data-[type=warning]:border-l-2 data-[type=warning]:!border-l-warning data-[type=info]:border-l-2 data-[type=info]:!border-l-info',
title: '!font-display !text-base !uppercase !tracking-[0.04em]',
description: '!text-muted-foreground',
actionButton:
'!rounded-md !bg-foreground !font-display !uppercase !tracking-[0.04em] !text-background',
cancelButton: '!rounded-md !bg-foreground/10 !text-foreground',
icon: 'group-data-[type=success]:text-success-text group-data-[type=error]:text-danger-text group-data-[type=warning]:text-warning group-data-[type=info]:text-info',
...toastOptions?.classNames,
},
}}
{...props}
/>
);
}
const TOAST_SOUND: Partial<Record<string, UiSound>> = { success: 'success', error: 'error' };
/**
* A toast arriving is the one sound nobody asked for: `success` and `error` by its type, `notify`
* for the rest. A loading toast is silent until it turns into its outcome.
*/
function useToastSounds() {
const { toasts } = useSonner();
const sound = useUiSound();
const heard = React.useRef(new Set<string>());
React.useEffect(() => {
const present = new Set<string>();
for (const item of toasts) {
const key = `${item.id}:${item.type ?? 'default'}`;
present.add(key);
if (heard.current.has(key) || item.delete || item.type === 'loading') continue;
sound(TOAST_SOUND[item.type ?? ''] ?? 'notify');
}
heard.current = present;
}, [toasts, sound]);
}
export { Toaster, toast };