Fix typing indicator composer positioning (#512)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
thomaspblock
2026-05-08 15:21:56 -04:00
committed by GitHub
parent e357d53d55
commit d4c21ca0eb
5 changed files with 32 additions and 21 deletions

View File

@@ -357,14 +357,6 @@ export const ChannelPane = React.memo(function ChannelPane({
) : (
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10">
<div className="pointer-events-auto">
{hasTypingActivity ? (
<TypingIndicatorRow
channel={activeChannel}
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={typingPubkeys}
/>
) : null}
<MessageComposer
channelId={activeChannel?.id ?? null}
channelName={activeChannel?.name ?? "channel"}
@@ -395,6 +387,17 @@ export const ChannelPane = React.memo(function ChannelPane({
}
showTopBorder={false}
/>
<div className="h-6 bg-background">
{hasTypingActivity ? (
<TypingIndicatorRow
channel={activeChannel}
className="px-4 pb-1 pt-0 sm:px-6"
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={typingPubkeys}
/>
) : null}
</div>
</div>
</div>
)}

View File

@@ -544,7 +544,7 @@ export function MessageComposer({
return (
<footer
className={cn(
"relative shrink-0 bg-transparent px-4 pb-4 pt-0",
"relative shrink-0 bg-transparent px-4 pb-2 pt-0",
showTopBorder ? "border-t border-border/40 pt-3" : "",
)}
>
@@ -554,7 +554,7 @@ export function MessageComposer({
/>
<div className="relative mx-auto flex w-full max-w-4xl flex-col gap-3">
<form
className="relative isolate rounded-2xl border border-border/50 bg-background/70 px-3 py-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
className="relative isolate rounded-2xl border border-border/50 bg-background/70 px-3 pb-2 pt-3 shadow-[0_4px_24px_rgba(0,0,0,0.08)] backdrop-blur-xl supports-[backdrop-filter]:bg-background/55 dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] sm:px-4"
data-testid="message-composer"
onDragOver={media.handleDragOver}
onDrop={(e) => {

View File

@@ -192,7 +192,7 @@ export function MessageThreadPanel({
onScroll={syncScrollState}
ref={threadBodyRef}
>
<div className="pb-8" ref={contentRef}>
<div className="pb-10" ref={contentRef}>
<div className="px-3 pb-1 pt-0" data-testid="message-thread-head">
<div className="rounded-2xl">
<MessageRow
@@ -273,7 +273,7 @@ export function MessageThreadPanel({
</div>
{!isAtBottom ? (
<div className="pointer-events-none absolute inset-x-0 bottom-32 z-20 flex justify-center px-4">
<div className="pointer-events-none absolute inset-x-0 bottom-36 z-20 flex justify-center px-4">
<Button
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-border/50 bg-background/85 px-2.5 text-[11px] font-medium text-muted-foreground shadow-sm backdrop-blur-sm hover:bg-muted/70 hover:text-foreground [&_svg]:size-3.5"
data-testid="thread-scroll-to-latest"
@@ -292,12 +292,6 @@ export function MessageThreadPanel({
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10">
<div className="pointer-events-auto">
<TypingIndicatorRow
channel={channel}
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={threadTypingPubkeys}
/>
<MessageComposer
channelId={channelId}
channelName={channelName}
@@ -315,6 +309,17 @@ export function MessageThreadPanel({
typingParentEventId={threadHead.id}
typingRootEventId={threadHead.rootId}
/>
<div className="h-6 bg-background">
{threadTypingPubkeys.length > 0 ? (
<TypingIndicatorRow
channel={channel}
className="px-4 pb-1 pt-0 sm:px-6"
currentPubkey={currentPubkey}
profiles={profiles}
typingPubkeys={threadTypingPubkeys}
/>
) : null}
</div>
</div>
</div>
</aside>

View File

@@ -133,7 +133,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
ref={scrollContainerRef}
>
<div
className="mx-auto flex w-full max-w-4xl flex-col gap-2 pb-8 pt-14"
className="mx-auto flex w-full max-w-4xl flex-col gap-2 pb-10 pt-14"
ref={contentRef}
>
<div ref={topSentinelRef} aria-hidden className="h-px" />
@@ -197,7 +197,7 @@ export const MessageTimeline = React.memo(function MessageTimeline({
</div>
{!isAtBottom ? (
<div className="pointer-events-none absolute inset-x-0 bottom-32 z-20 flex justify-center px-4">
<div className="pointer-events-none absolute inset-x-0 bottom-36 z-20 flex justify-center px-4">
<Button
className="pointer-events-auto h-7 min-h-7 gap-1.5 rounded-full border-border/50 bg-background/85 px-2.5 text-[11px] font-medium text-muted-foreground shadow-sm backdrop-blur-sm hover:bg-muted/70 hover:text-foreground [&_svg]:size-3.5"
data-testid="message-scroll-to-latest"

View File

@@ -6,9 +6,11 @@ import {
} from "@/features/profile/lib/identity";
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import type { Channel } from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
type TypingIndicatorRowProps = {
channel: Channel | null;
className?: string;
currentPubkey?: string;
profiles?: UserProfileLookup;
typingPubkeys: string[];
@@ -48,6 +50,7 @@ function formatTypingLabel(names: string[]) {
export function TypingIndicatorRow({
channel,
className,
currentPubkey,
profiles,
typingPubkeys,
@@ -69,7 +72,7 @@ export function TypingIndicatorRow({
return (
<div
aria-live="polite"
className="shrink-0 bg-transparent px-4 py-2 sm:px-6"
className={cn("shrink-0 bg-transparent px-4 py-2 sm:px-6", className)}
{...(labels.length > 0
? { "data-testid": "message-typing-indicator" }
: {})}