refactor(feedback): 전역 토스트 제거 및 HUD 오버레이 피드백 도입
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext, useCallback, useContext, useMemo, useState } from 'react';
|
||||
import { cn } from '@/shared/lib/cn';
|
||||
import { createContext, useCallback, useContext, useMemo } from 'react';
|
||||
|
||||
interface ToastPayload {
|
||||
title: string;
|
||||
@@ -14,10 +13,6 @@ interface ToastPayload {
|
||||
};
|
||||
}
|
||||
|
||||
interface ToastItem extends ToastPayload {
|
||||
id: number;
|
||||
}
|
||||
|
||||
interface ToastContextValue {
|
||||
pushToast: (payload: ToastPayload) => void;
|
||||
}
|
||||
@@ -25,58 +20,13 @@ interface ToastContextValue {
|
||||
const ToastContext = createContext<ToastContextValue | null>(null);
|
||||
|
||||
export const ToastProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([]);
|
||||
|
||||
const removeToast = useCallback((id: number) => {
|
||||
setToasts((current) => current.filter((toast) => toast.id !== id));
|
||||
}, []);
|
||||
|
||||
const pushToast = useCallback((payload: ToastPayload) => {
|
||||
const id = Date.now() + Math.floor(Math.random() * 10000);
|
||||
const durationMs = payload.durationMs ?? 2400;
|
||||
|
||||
setToasts((current) => [...current, { id, ...payload }]);
|
||||
|
||||
window.setTimeout(() => {
|
||||
removeToast(id);
|
||||
}, durationMs);
|
||||
}, [removeToast]);
|
||||
const pushToast = useCallback((_payload: ToastPayload) => {}, []);
|
||||
|
||||
const value = useMemo(() => ({ pushToast }), [pushToast]);
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={value}>
|
||||
{children}
|
||||
<div className="pointer-events-none fixed bottom-4 right-4 z-[70] flex w-[min(92vw,340px)] flex-col gap-2">
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
className={cn(
|
||||
'pointer-events-auto rounded-xl border border-white/15 bg-slate-950/92 px-4 py-3 text-sm text-white shadow-lg shadow-slate-950/60',
|
||||
'animate-[toast-in_180ms_ease-out] motion-reduce:animate-none',
|
||||
)}
|
||||
>
|
||||
<p className="font-medium">{toast.title}</p>
|
||||
{toast.description ? (
|
||||
<p className="mt-1 text-xs text-white/70">{toast.description}</p>
|
||||
) : null}
|
||||
{toast.action ? (
|
||||
<div className="mt-2 flex justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
toast.action?.onClick();
|
||||
removeToast(toast.id);
|
||||
}}
|
||||
className="rounded-full border border-white/25 bg-white/[0.08] px-2.5 py-1 text-[11px] font-medium text-white/88 transition-colors hover:bg-white/[0.16]"
|
||||
>
|
||||
{toast.action.label}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ToastContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user