From ce373e3e03bd1359388ef9e5e215cfa21c6cffb1 Mon Sep 17 00:00:00 2001 From: gosangam Date: Sat, 8 Aug 2026 00:49:39 +0530 Subject: [PATCH] fix: profile bio --- components/profile/profile-header.tsx | 132 +++++++++++-------------- components/social/mention-textarea.tsx | 32 +++++- 2 files changed, 90 insertions(+), 74 deletions(-) diff --git a/components/profile/profile-header.tsx b/components/profile/profile-header.tsx index 8c0f99a6..04181fe6 100644 --- a/components/profile/profile-header.tsx +++ b/components/profile/profile-header.tsx @@ -1,14 +1,9 @@ "use client"; /** - * ProfileHeader — the header at the top of /u/me and /u/[handle]. - * - * Layout is two stacked blocks: - * 1. An identity ROW — avatar on the left, with display name, @handle, and - * the posts/followers/following stat line stacked to its right. - * 2. Full-width rows beneath it — the bio, then the action row. Both span the - * avatar's width too, so a 200-character bio gets the whole column rather - * than the narrow gutter beside the avatar. + * ProfileHeader — Instagram-style centered header used at the top of + * /u/me and /u/[handle]. Avatar, display name, @handle, a single stat + * line, and an action row. * * The action row hosts: * • "Edit profile" link — only on the owner's own profile (via the @@ -95,8 +90,8 @@ export function ProfileHeader({ * on the avatar; tapping it explains the mark. */ isDirectDisciple?: boolean; /** The profile's bio. Plain text — `@handle` mentions are linkified on - * render. Rendered left-aligned across the full column below the centred - * identity block; omitted entirely when empty. */ + * render. Sits between the stat line and the action buttons, centred like + * the rest of the header. Omitted entirely when empty. */ bio?: string | null; }) { // Owner affordances ride on `editHref`; the follow-action row rides on @@ -127,77 +122,70 @@ export function ProfileHeader({ return ( <> -
- {/* Identity row — avatar on the left, name + handle + stats stacked to - its right. The bio and action row sit below at full width, so long - prose gets the whole column instead of a narrow gutter. */} -
- {/* Avatar, with Śrīla Prabhupāda's portrait tucked into the - lower-right for his direct disciples (verification-tick - placement). */} -
- setZoomOpen(true) : undefined} - ariaLabel={`View ${name}'s profile picture`} - /> - {isDirectDisciple ? ( - - ) : null} -
- - {/* min-w-0 lets the long-name truncation actually kick in — without - it the flex child refuses to shrink below its content width. */} -
-

+
+ {/* Avatar, with Śrīla Prabhupāda's portrait tucked into the lower-right + for his direct disciples (verification-tick placement). */} +
+ setZoomOpen(true) : undefined} + ariaLabel={`View ${name}'s profile picture`} + /> + {isDirectDisciple ? ( + + ) : null} +
+
+
+

{name}

-

- @{handle} -

+

@{handle}

+
- {/* Stat line: posts · followers · following */} -
- {typeof postCount === "number" ? ( - - ) : null} - {showCounts ? ( - <> - setFollowList("followers") - : undefined - } - /> - setFollowList("following") - : undefined - } - /> - - ) : null} -
+ {/* Stat line: posts · followers · following */} +
+ {typeof postCount === "number" ? ( + + ) : null} + {showCounts ? ( + <> + setFollowList("followers") + : undefined + } + /> + setFollowList("following") + : undefined + } + /> + + ) : null}
- {/* Bio — spans the full column beneath the avatar AND the name/stats - block, so a 200-character bio wraps across the whole width instead - of being squeezed into the column beside the avatar. */} + {/* Bio — between the stats and the action buttons, centred to match the + rest of the header. `self-stretch` overrides the header's + `items-center` so the text box spans the full column and each + wrapped line centres against the same axis as the name and stats + (without it the box would shrink to its content and centre oddly). */} {bio?.trim() ? ( ) : null} diff --git a/components/social/mention-textarea.tsx b/components/social/mention-textarea.tsx index da16145e..fde97056 100644 --- a/components/social/mention-textarea.tsx +++ b/components/social/mention-textarea.tsx @@ -69,6 +69,7 @@ export const MentionTextarea = forwardRef< const [pos, setPos] = useState(null); const reqRef = useRef(0); const blurTimer = useRef(null); + const listRef = useRef(null); // Detect the active @token from the text up to the caret. function detect(el: HTMLTextAreaElement) { @@ -78,8 +79,10 @@ export const MentionTextarea = forwardRef< setMention(null); return; } - setMention({ start: caret - m[2].length - 1, query: m[2] }); - setActive(0); + const next = { start: caret - m[2].length - 1, query: m[2] }; + + // Reposition unconditionally — the field grows as text wraps, so the panel + // has to stay pinned to it even when the token itself hasn't changed. const r = el.getBoundingClientRect(); // Prefer opening ABOVE the field: that's what the comment composer needs // (it sits at the bottom of a drawer, often above the on-screen keyboard). @@ -94,6 +97,18 @@ export const MentionTextarea = forwardRef< ? { bottom: window.innerHeight - r.top + 6 } : { top: r.bottom + 6 }), }); + + // Bail out if the token is byte-for-byte what we already have. detect() + // also runs on keyup and click, so re-seeding an identical mention would: + // • reset `active` to 0 — which cancelled the ↑/↓ highlight the instant + // the key came back up, making arrow navigation look dead; and + // • hand the fetch effect a fresh object identity, re-requesting + // suggestions on every single keystroke. + if (mention && mention.start === next.start && mention.query === next.query) { + return; + } + setMention(next); + setActive(0); } // Fetch suggestions (debounced) whenever the active token changes. @@ -136,6 +151,17 @@ export const MentionTextarea = forwardRef< const listOpen = !!mention && (loading || items.length > 0); + // Keep the highlighted row visible: the panel caps at max-h-56, which holds + // ~5 of the 8 suggestions, so arrowing past the fifth would otherwise move + // the highlight out of sight. `block: "nearest"` scrolls the panel the + // minimum amount and never scrolls the page behind it. + useEffect(() => { + if (!listOpen) return; + listRef.current + ?.querySelector(`[data-idx="${active}"]`) + ?.scrollIntoView({ block: "nearest" }); + }, [active, listOpen]); + function onKeyDown(e: React.KeyboardEvent) { if (listOpen && items.length > 0) { if (e.key === "ArrowDown") { @@ -195,6 +221,7 @@ export const MentionTextarea = forwardRef< {listOpen && pos && typeof document !== "undefined" ? createPortal(