refactor(toast): HUD 상태 라인 피드백 통합 및 우선순위 큐 적용

This commit is contained in:
2026-03-04 20:46:19 +09:00
parent c451175b9c
commit 06dbee8d63
6 changed files with 186 additions and 32 deletions

View File

@@ -0,0 +1,103 @@
'use client';
import { useCallback, useEffect, useMemo, useState } from 'react';
export type HudStatusLinePriority = 'normal' | 'undo';
export interface HudStatusLineAction {
label: string;
onClick: () => void;
}
export interface HudStatusLinePayload {
message: string;
durationMs?: number;
action?: HudStatusLineAction;
priority?: HudStatusLinePriority;
}
export interface HudStatusLineItem extends HudStatusLinePayload {
id: number;
priority: HudStatusLinePriority;
}
const MAX_PENDING_MESSAGES = 2;
const MAX_TOTAL_MESSAGES = MAX_PENDING_MESSAGES + 1;
const DEFAULT_DURATION_MS = 2000;
const DEFAULT_UNDO_DURATION_MS = 4200;
export const useHudStatusLine = (enabled = true) => {
const [queue, setQueue] = useState<HudStatusLineItem[]>([]);
useEffect(() => {
if (enabled) {
return;
}
setQueue([]);
}, [enabled]);
useEffect(() => {
if (!enabled || queue.length === 0) {
return;
}
const active = queue[0];
const durationMs =
active.durationMs ?? (active.action ? DEFAULT_UNDO_DURATION_MS : DEFAULT_DURATION_MS);
const timerId = window.setTimeout(() => {
setQueue((current) => current.slice(1));
}, durationMs);
return () => {
window.clearTimeout(timerId);
};
}, [enabled, queue]);
const pushStatusLine = useCallback((payload: HudStatusLinePayload) => {
const nextItem: HudStatusLineItem = {
id: Date.now() + Math.floor(Math.random() * 10000),
...payload,
priority: payload.priority ?? (payload.action ? 'undo' : 'normal'),
};
setQueue((current) => {
if (current.length === 0) {
return [nextItem];
}
const [active, ...pending] = current;
const nextQueue =
nextItem.priority === 'undo'
? [active, nextItem, ...pending]
: [active, ...pending, nextItem];
return nextQueue.slice(0, MAX_TOTAL_MESSAGES);
});
}, []);
const dismissActiveStatus = useCallback(() => {
setQueue((current) => current.slice(1));
}, []);
const runActiveAction = useCallback(() => {
const active = queue[0];
if (!active?.action) {
dismissActiveStatus();
return;
}
active.action.onClick();
dismissActiveStatus();
}, [dismissActiveStatus, queue]);
return useMemo(() => {
return {
activeStatus: queue[0] ?? null,
pushStatusLine,
runActiveAction,
dismissActiveStatus,
};
}, [dismissActiveStatus, pushStatusLine, queue, runActiveAction]);
};