From 3903423d06c54a6a3619d9467b7c2fb0227fa824 Mon Sep 17 00:00:00 2001 From: gosangam Date: Wed, 10 Jun 2026 19:21:32 +0530 Subject: [PATCH] fix: mention input zoom behaviour --- components/selection-toolbar/compose-post.tsx | 6 +- components/social/mention-autocomplete.tsx | 112 +++++++++--------- components/social/mentions-drawer.tsx | 11 +- 3 files changed, 66 insertions(+), 63 deletions(-) diff --git a/components/selection-toolbar/compose-post.tsx b/components/selection-toolbar/compose-post.tsx index d6084c43..55c4d03d 100644 --- a/components/selection-toolbar/compose-post.tsx +++ b/components/selection-toolbar/compose-post.tsx @@ -318,7 +318,7 @@ export function ComposePost({ Share to feed -
{body}
+
{body}
); @@ -331,7 +331,7 @@ export function ComposePost({ className="fixed inset-0 z-100 bg-foreground/40 backdrop-blur-sm" onClick={onClose} /> -
+

Share to feed @@ -344,7 +344,7 @@ export function ComposePost({

-
{body}
+
{body}
, document.body, diff --git a/components/social/mention-autocomplete.tsx b/components/social/mention-autocomplete.tsx index 5e42d451..641852ff 100644 --- a/components/social/mention-autocomplete.tsx +++ b/components/social/mention-autocomplete.tsx @@ -1,16 +1,24 @@ "use client"; /** - * MentionAutocomplete — a search input with a suggestions popover for tagging - * users. Suggestions come from the backend prioritised (following → followers - * → others), 20 per page, and the popover infinite-scrolls (≈5 rows visible). - * Already-selected users are filtered out. Calls onSelect with the chosen - * user; the caller manages the selected set. + * MentionAutocomplete — a search field for tagging users. Results render + * INLINE (in normal flow) directly below the input, not as a floating + * popover. This is deliberate: a floating popover inside a drawer/modal + * overflows the sheet and its outside-click listener fights the drawer's own + * dismiss. Inline results: + * • never resize/overflow the drawer (the container scrolls them), + * • close on blur (no document listener → outside clicks don't close the + * drawer), + * • only appear once the user types (no suggestions on bare focus). + * + * Suggestions come from the backend prioritised (following → followers → + * others), 20 per page, infinite-scrolling. Already-selected users are + * filtered out. Selecting a user adds it (via onSelect) and closes the list. */ import { useEffect, useRef, useState } from "react"; import Image from "next/image"; -import { Loader2, Search } from "lucide-react"; +import { Loader2 } from "lucide-react"; import { posts, type UserSummary } from "@/lib/social"; const PAGE = 20; @@ -30,22 +38,25 @@ export function MentionAutocomplete({ const [open, setOpen] = useState(false); const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); - const [hasMore, setHasMore] = useState(true); + const [hasMore, setHasMore] = useState(false); const reqRef = useRef(0); const busyRef = useRef(false); - const rootRef = useRef(null); + const blurTimer = useRef(null); - // (Re)load page 0 on open / query change (debounced — timer callback, so - // setState isn't synchronous-in-effect). + const query = q.trim(); + const showResults = open && query !== ""; + + // (Re)load page 0 when the query changes — debounced, only when non-empty. + // No fetch on bare focus, so suggestions appear "while typing" only. useEffect(() => { - if (!open) return; + if (query === "") return; const t = window.setTimeout(() => { const token = ++reqRef.current; busyRef.current = true; setLoading(true); posts - .mentionSuggest(q, { limit: PAGE, offset: 0 }) + .mentionSuggest(query, { limit: PAGE, offset: 0 }) .then((res) => { if (reqRef.current !== token) return; setItems(res); @@ -65,7 +76,7 @@ export function MentionAutocomplete({ }); }, 200); return () => window.clearTimeout(t); - }, [q, open]); + }, [query]); function loadMore() { if (busyRef.current || !hasMore) return; @@ -73,7 +84,7 @@ export function MentionAutocomplete({ busyRef.current = true; setLoading(true); posts - .mentionSuggest(q, { limit: PAGE, offset: items.length }) + .mentionSuggest(query, { limit: PAGE, offset: items.length }) .then((res) => { if (reqRef.current !== token) return; setItems((prev) => [...prev, ...res]); @@ -88,50 +99,33 @@ export function MentionAutocomplete({ }); } - // Close on outside click / Escape. - useEffect(() => { - if (!open) return; - function onDown(e: PointerEvent) { - if (rootRef.current && !rootRef.current.contains(e.target as Node)) { - setOpen(false); - } - } - function onKey(e: KeyboardEvent) { - if (e.key === "Escape") setOpen(false); - } - document.addEventListener("pointerdown", onDown); - document.addEventListener("keydown", onKey); - return () => { - document.removeEventListener("pointerdown", onDown); - document.removeEventListener("keydown", onKey); - }; - }, [open]); - const excluded = new Set(excludeIds); const visible = items.filter((u) => !u.id || !excluded.has(u.id)); return ( -
-
- - setOpen(true)} - onChange={(e) => { - setQ(e.target.value); - setOpen(true); - }} - placeholder={placeholder} - autoComplete="off" - // text-base (16px) on mobile prevents iOS Safari from auto-zooming - // on focus (it zooms any input < 16px); compact 14px on desktop. - className="w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-base sm:text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring" - /> -
+
+ setOpen(true)} + onChange={(e) => { + setQ(e.target.value); + setOpen(true); + }} + onBlur={() => { + // Defer so a result tap (which fires onMouseDown→onClick) isn't + // pre-empted by the list closing. Plain blur (tapping elsewhere) + // just closes the inline list — the drawer is unaffected. + blurTimer.current = window.setTimeout(() => setOpen(false), 120); + }} + placeholder={placeholder} + autoComplete="off" + // 16px on mobile so iOS Safari doesn't auto-zoom on focus. + className="w-full rounded-lg border border-border bg-background px-3 py-2 text-base sm:text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-2 focus-visible:ring-ring" + /> - {open ? ( + {showResults ? (
{ const el = e.currentTarget; @@ -139,21 +133,27 @@ export function MentionAutocomplete({ loadMore(); } }} - className="absolute z-50 mt-1 max-h-60 w-full overflow-y-auto overscroll-contain rounded-lg border border-border bg-card shadow-lg" + className="mt-2 max-h-56 overflow-y-auto overscroll-contain rounded-lg border border-border bg-card" > {visible.length === 0 && !loading ? (

- {q.trim() ? "No people found." : "No suggestions."} + No people found.

) : ( visible.map((u) => (