Files
DECNET/decnet_web/src/components/Toasts/ToastProvider.tsx
anti ccbe949238 feat(web): command palette, toasts, and global shell chrome
- CommandPalette (Alt+K): fuzzy action launcher with keyboard nav.
- Toasts: ephemeral notification stack + provider.
- useGlobalHotkeys: Alt+K palette toggle, G-chord navigation
  (G D/F/M/L/B/A/S/U/E/C), respects editable-element focus.
- Layout/App: wire ToastProvider at root, mount the palette inside the
  authed shell, introduce the global search box in the top bar.
- MazeNETRoute now renders TopologyList inline when no ?topology is
  present, instead of bouncing through a redirect.
- index.css: a few global token tweaks consumed by the new chrome.

Fixes a latent breakage: Config.tsx and MazeNET already imported
./Toasts/useToast but the directory was never committed.
2026-04-22 17:15:19 -04:00

34 lines
1.0 KiB
TypeScript

import React, { useCallback, useEffect, useRef, useState } from 'react';
import Toasts from './Toasts';
import { ToastContext } from './toast-context';
import type { Toast, ToastInput } from './toast-context';
const DISMISS_MS = 3200;
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [items, setItems] = useState<Toast[]>([]);
const timers = useRef<Map<number, ReturnType<typeof setTimeout>>>(new Map());
const push = useCallback((t: ToastInput) => {
const id = Date.now() + Math.random();
setItems(prev => [...prev, { ...t, id }]);
const timer = setTimeout(() => {
setItems(prev => prev.filter(x => x.id !== id));
timers.current.delete(id);
}, DISMISS_MS);
timers.current.set(id, timer);
}, []);
useEffect(() => {
const map = timers.current;
return () => { map.forEach(clearTimeout); map.clear(); };
}, []);
return (
<ToastContext.Provider value={{ push }}>
{children}
<Toasts items={items} />
</ToastContext.Provider>
);
};