{error}
)} diff --git a/src/shared/components/BlockEditor/inline/LinkPopover.client.js b/src/shared/components/BlockEditor/inline/LinkPopover.client.js new file mode 100644 index 0000000..b86bc85 --- /dev/null +++ b/src/shared/components/BlockEditor/inline/LinkPopover.client.js @@ -0,0 +1,87 @@ +'use client'; + +import React, { useEffect, useLayoutEffect, useRef, useState } from 'react'; + +const GAP = 8; +const VIEWPORT_MARGIN = 8; + +export default function LinkPopover({ rect, mark, onSetLink, onRemoveLink, onClose }) { + const ref = useRef(null); + const [pos, setPos] = useState({ top: 0, left: 0 }); + const [url, setUrl] = useState(mark?.href ?? ''); + const [newTab, setNewTab] = useState(mark?.newTab ?? false); + + useLayoutEffect(() => { + if (!rect || typeof window === 'undefined') return; + const width = ref.current?.offsetWidth ?? 280; + const height = ref.current?.offsetHeight ?? 80; + const vw = window.innerWidth; + const vh = window.innerHeight; + let top = rect.bottom + GAP; + let left = rect.left; + if (left + width + VIEWPORT_MARGIN > vw) left = vw - width - VIEWPORT_MARGIN; + if (left < VIEWPORT_MARGIN) left = VIEWPORT_MARGIN; + if (top + height + VIEWPORT_MARGIN > vh) top = rect.top - height - GAP; + if (top < VIEWPORT_MARGIN) top = VIEWPORT_MARGIN; + setPos({ top, left }); + }, [rect]); + + useEffect(() => { + function onKey(e) { + if (e.key === 'Escape') { e.preventDefault(); onClose?.(); } + } + document.addEventListener('keydown', onKey); + return () => document.removeEventListener('keydown', onKey); + }, [onClose]); + + function handleSubmit() { + if (!url) return; + onSetLink?.(url, newTab); + onClose?.(); + } + + return ( +