'use client'; import { useEffect, useRef, useState } from 'react'; export type ChatPresencePhase = 'hidden' | 'entering' | 'visible' | 'leaving'; /** * Presence state machine for floating popovers. * * Drives a four-phase lifecycle so enter/leave CSS transitions actually fire: * * hidden → entering (mount, transition class starts) → visible * visible → leaving (transition runs) → hidden (unmount) * * Mounting in `entering` and ticking to `visible` on the next paint is what * lets transition classes animate. Without it the element appears already * at its final state and CSS transitions never observe a change. * * @param open - controlled open state * @param exitDurationMs - how long the leave transition runs; should match CSS */ export function useChatPresence(open: boolean, exitDurationMs = 200): ChatPresencePhase { const [phase, setPhase] = useState('hidden'); const timerRef = useRef | null>(null); useEffect(() => { if (timerRef.current) clearTimeout(timerRef.current); if (open) { setPhase('entering'); // One paint later: switch to 'visible' so the CSS transition runs. timerRef.current = setTimeout(() => setPhase('visible'), 16); } else { setPhase('leaving'); timerRef.current = setTimeout(() => setPhase('hidden'), exitDurationMs); } return () => { if (timerRef.current) clearTimeout(timerRef.current); }; }, [open, exitDurationMs]); return phase; }