refactor(guest): retire legacy guest app and move shared modules
This commit is contained in:
79
resources/js/shared/guest/components/ToastHost.tsx
Normal file
79
resources/js/shared/guest/components/ToastHost.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
type ToastAction = { label: string; onClick: () => void };
|
||||
type Toast = {
|
||||
id: number;
|
||||
text: string;
|
||||
type?: 'success' | 'error' | 'info';
|
||||
action?: ToastAction;
|
||||
durationMs?: number;
|
||||
};
|
||||
const Ctx = React.createContext<{ push: (t: Omit<Toast,'id'>) => void } | null>(null);
|
||||
|
||||
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
||||
const [list, setList] = React.useState<Toast[]>([]);
|
||||
const push = React.useCallback((t: Omit<Toast,'id'>) => {
|
||||
const id = Date.now() + Math.random();
|
||||
const durationMs = t.durationMs ?? 3000;
|
||||
setList((arr) => [...arr, { id, ...t, durationMs }]);
|
||||
if (durationMs > 0) {
|
||||
setTimeout(() => setList((arr) => arr.filter((x) => x.id !== id)), durationMs);
|
||||
}
|
||||
}, []);
|
||||
const dismiss = React.useCallback((id: number) => {
|
||||
setList((arr) => arr.filter((x) => x.id !== id));
|
||||
}, []);
|
||||
const contextValue = React.useMemo(() => ({ push }), [push]);
|
||||
React.useEffect(() => {
|
||||
const onEvt = (e: CustomEvent<Omit<Toast, 'id'>>) => push(e.detail);
|
||||
window.addEventListener('guest-toast', onEvt);
|
||||
return () => window.removeEventListener('guest-toast', onEvt);
|
||||
}, [push]);
|
||||
return (
|
||||
<Ctx.Provider value={contextValue}>
|
||||
{children}
|
||||
<div className="pointer-events-none fixed inset-x-0 bottom-4 z-50 flex justify-center px-4">
|
||||
<div className="flex w-full max-w-sm flex-col gap-2">
|
||||
{list.map((t) => (
|
||||
<div
|
||||
key={t.id}
|
||||
className={`pointer-events-auto rounded-md border p-3 shadow-sm ${
|
||||
t.type === 'error'
|
||||
? 'border-red-300 bg-red-50 text-red-700'
|
||||
: t.type === 'info'
|
||||
? 'border-blue-300 bg-blue-50 text-blue-700'
|
||||
: 'border-green-300 bg-green-50 text-green-700'
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<span className="text-sm">{t.text}</span>
|
||||
{t.action ? (
|
||||
<button
|
||||
type="button"
|
||||
className="pointer-events-auto rounded-full border border-current/30 px-3 py-1 text-xs font-semibold uppercase tracking-wide transition hover:border-current"
|
||||
onClick={() => {
|
||||
try {
|
||||
t.action?.onClick();
|
||||
} finally {
|
||||
dismiss(t.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t.action.label}
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Ctx.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useToast() {
|
||||
const ctx = React.useContext(Ctx);
|
||||
if (!ctx) throw new Error('ToastProvider missing');
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user