import React, { useEffect, useRef, useState } from 'react'; import { useAtomValue } from 'jotai'; import { Box, Text, config } from 'folds'; import { roomIdToMsgDraftAtomFamily } from '../../state/room/roomInputDrafts'; import { toPlainText } from '../../components/editor'; import { DraftDot, DraftDotPulse, DraftIndicatorBase } from './DraftIndicator.css'; const PULSE_DURATION = 600; type DraftIndicatorProps = { roomId: string; }; /** * Subtle, non-distracting status shown near the composer when the current room * has a persisted (unsent) message draft. It reacts to the shared draft atom * (`roomIdToMsgDraftAtomFamily`) — the same source that backs the * `draft-msg-${roomId}` localStorage persistence — so it never introduces a * parallel persistence path. * * A short "Saved" pulse plays the moment a draft becomes persisted, then the * indicator settles into a quiet, muted resting state. The pulse is gated behind * `prefers-reduced-motion` in CSS, so motion-averse users only ever see the * static label. */ export function DraftIndicator({ roomId }: DraftIndicatorProps) { const draft = useAtomValue(roomIdToMsgDraftAtomFamily(roomId)); // Real content, not just an empty paragraph. const hasDraft = toPlainText(draft, false).trim().length > 0; const [pulse, setPulse] = useState(false); const hadDraft = useRef(false); useEffect(() => { if (hasDraft && !hadDraft.current) { hadDraft.current = true; setPulse(true); const timeout = setTimeout(() => setPulse(false), PULSE_DURATION); return () => clearTimeout(timeout); } hadDraft.current = hasDraft; return undefined; }, [hasDraft]); if (!hasDraft) return null; return ( Draft saved ); }