Files
DorkNet/DorkNet.Server/admin-ui/src/components/Toast.tsx
T
Alexa d471b418b9 Import march-2020-03-10 from private repo (scrubbed 2026-05-27)
Targets Rec Room 2020.03.10 client (also serves 2020.03.06).

Re-import after adding:
- DorkNet.ClientMod/dorknet-clientmod.json.template — config schema
  the launcher's manifest-driven patcher renders into UserData/.
- tools/build-patcher-zip.ps1 — produces dorknet-clientpatch-{key}.zip
  for v1-march-* Release uploads.
- .github/workflows/release.yml — on tag push publishes both
  dorknet-server-march_2020_03_10-win-x64.zip and
  dorknet-clientpatch-march_2020_03_10.zip as Release assets.

Same scrub rules as december; tools/build-patcher-zip.ps1 added to
the maintainer-side allow-list so it survives.
2026-05-27 01:06:12 +03:00

51 lines
1.8 KiB
TypeScript

import { createContext, useCallback, useContext, useEffect, useState } from 'react';
type Kind = 'info' | 'success' | 'error';
interface ToastMsg { id: number; kind: Kind; text: string }
interface Ctx { push: (text: string, kind?: Kind) => void }
const ToastCtx = createContext<Ctx | null>(null);
export function useToast(): Ctx {
const ctx = useContext(ToastCtx);
if (!ctx) throw new Error('useToast must be used inside <ToastProvider>');
return ctx;
}
export function ToastProvider({ children }: { children: React.ReactNode }) {
const [items, setItems] = useState<ToastMsg[]>([]);
const push = useCallback((text: string, kind: Kind = 'info') => {
const id = Date.now() + Math.random();
setItems(prev => [...prev, { id, kind, text }]);
setTimeout(() => setItems(prev => prev.filter(t => t.id !== id)), 4000);
}, []);
return (
<ToastCtx.Provider value={{ push }}>
{children}
<div className="pointer-events-none fixed bottom-6 right-6 z-50 flex flex-col gap-2">
{items.map(t => (
<ToastItem key={t.id} msg={t} />
))}
</div>
</ToastCtx.Provider>
);
}
function ToastItem({ msg }: { msg: ToastMsg }) {
const [open, setOpen] = useState(false);
useEffect(() => { requestAnimationFrame(() => setOpen(true)); }, []);
const colors =
msg.kind === 'success' ? 'bg-success/15 text-success border-success/30'
: msg.kind === 'error' ? 'bg-danger/15 text-danger border-danger/30'
: 'bg-ink-800 text-ink-100 border-ink-700';
return (
<div
className={`pointer-events-auto min-w-[240px] max-w-sm rounded-lg border px-3.5 py-2 text-sm shadow-card transition-all duration-200 ${colors} ${open ? 'translate-x-0 opacity-100' : 'translate-x-2 opacity-0'}`}
role="status"
>
{msg.text}
</div>
);
}