import { FloatingPortal } from "@floating-ui/react"; import { NodeViewContent, NodeViewWrapper, type NodeViewProps } from "@tiptap/react"; import { useCallback, useRef, useState } from "react"; import { CalloutKindContent, getCalloutKindMeta } from "@/shared/editor/presentation/callout"; import { preserveEditorSelectionOnMouseDown, useEditorPopover } from "../../controllers/menu/popover"; import { useEditorAffordance } from "../../editor-affordance-context"; import { CALLOUT_KINDS, normalizeCalloutKind, type CalloutKind } from "./kinds"; export function CalloutView({ node, updateAttributes }: NodeViewProps) { const { documentEditable } = useEditorAffordance(); const [open, setOpen] = useState(false); const btnRef = useRef(null); const kind = normalizeCalloutKind(node.attrs.kind); const { label } = getCalloutKindMeta(kind); const { floatingStyles, getFloatingProps, refs } = useEditorPopover({ open, onClose: () => setOpen(false), anchorRef: btnRef, // Open above the pill so the callout body stays readable while the user // changes kind; flip middleware falls back to bottom-start near viewport top. placement: "top-start", offset: 6, padding: 8, }); const selectKind = useCallback( (next: CalloutKind) => { updateAttributes({ kind: next }); setOpen(false); }, [updateAttributes], ); return ( {documentEditable && open && (
{ refs.setFloating(el); }} role="menu" aria-label="Callout kind" className="tiptap-menu-surface tiptap-callout-kind-menu" style={{ ...floatingStyles, zIndex: 50, animation: "var(--animate-menu)" }} {...getFloatingProps({ onMouseDownCapture: (e) => preserveEditorSelectionOnMouseDown(e), })} > {CALLOUT_KINDS.map((candidate) => { const meta = getCalloutKindMeta(candidate); const active = candidate === kind; return ( ); })}
)}
); }