refactor(toast): HUD 상태 라인 피드백 통합 및 우선순위 큐 적용
This commit is contained in:
103
src/shared/lib/useHudStatusLine.ts
Normal file
103
src/shared/lib/useHudStatusLine.ts
Normal 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]);
|
||||
};
|
||||
Reference in New Issue
Block a user