import { useCallback, useState } from "react"; import { createPortal } from "react-dom"; import { toast } from "sonner"; import type { Particle } from "@/api/types"; import { particlePath, parseParticlePath, toFirestoreDocPath, type ParticlePath, } from "@/lib/particle-path"; import { editTextParticleContent } from "@/lib/firestore-particles"; import { TextEditor } from "@/features/compose/text-editor"; type TextParticle = Extract; interface TextEditOverlayProps { particle: TextParticle; streamPath: ParticlePath; onClose: () => void; } export function TextEditOverlay({ particle, streamPath, onClose, }: TextEditOverlayProps) { const [textContent, setTextContent] = useState(particle.properties.content); const [saving, setSaving] = useState(false); const handleSubmit = useCallback(async () => { if (saving) return; const trimmed = textContent.trim(); if (!trimmed) return; if (trimmed === particle.properties.content) { onClose(); return; } setSaving(true); try { const { networkId, segments } = parseParticlePath(streamPath); const docPath = toFirestoreDocPath( particlePath(networkId, [...segments, particle.id]), ); await editTextParticleContent(docPath, trimmed); onClose(); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to save"); setSaving(false); } }, [saving, textContent, particle.properties.content, particle.id, streamPath, onClose]); return createPortal( // React synthetic events bubble through the React tree (not the DOM tree), // so clicks here would reach stream-view's click-to-navigate handler even // though we're portaled to document.body. Stop propagation at the root.
e.stopPropagation()}>
, document.body, ); }