fix: relocate typing indicator into the input bubble

Move TypingIndicator from document flow (between MessageList and
MessageInput) into MessageInput's outer wrapper using absolute
bottom-full positioning. Fixes indicator being hidden behind the
floating glass input bubble on desktop.
This commit is contained in:
Jannis Braun
2026-03-04 04:27:26 +01:00
parent f7809cf457
commit 2d6cdd0a3c
5 changed files with 12 additions and 9 deletions
@@ -3,6 +3,7 @@ import { useChatStore } from '../../stores/chatStore';
import { isDmChannel, getChannelOrigin, getApiForOrigin, useServerStore } from '../../stores/serverStore'; import { isDmChannel, getChannelOrigin, getApiForOrigin, useServerStore } from '../../stores/serverStore';
import { wsSend } from '../../hooks/useWebSocket'; import { wsSend } from '../../hooks/useWebSocket';
import { MentionPopover } from './MentionPopover'; import { MentionPopover } from './MentionPopover';
import { TypingIndicator } from './TypingIndicator';
import type { MemberWithUser } from '@backspace/shared'; import type { MemberWithUser } from '@backspace/shared';
interface MessageInputProps { interface MessageInputProps {
@@ -228,7 +229,8 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
}; };
return ( return (
<div data-pip-obstacle="bottom" className="px-3 pb-3 flex-shrink-0 md:absolute md:bottom-3 md:left-3 md:right-3 md:z-[110] md:px-0 md:pb-0 md:glass-bubble md:rounded-[14px]"> <div data-pip-obstacle="bottom" className="relative px-3 pb-3 flex-shrink-0 md:absolute md:bottom-3 md:left-3 md:right-3 md:z-[110] md:px-0 md:pb-0 md:glass-bubble md:rounded-[14px]">
<TypingIndicator channelId={channelId} />
{replyTo && ( {replyTo && (
<div className="bg-interactive-hover rounded-t-lg px-4 py-2 flex items-center justify-between border-b border-white/[0.06]"> <div className="bg-interactive-hover rounded-t-lg px-4 py-2 flex items-center justify-between border-b border-white/[0.06]">
<div className="flex items-center gap-1 text-[14px] text-txt-message truncate"> <div className="flex items-center gap-1 text-[14px] text-txt-message truncate">
@@ -30,7 +30,7 @@ export function TypingIndicator({ channelId }: TypingIndicatorProps) {
} }
return ( return (
<div className="h-[24px] px-4 flex items-center text-[12px] text-txt-primary font-medium select-none pointer-events-none"> <div className="absolute bottom-full left-1 md:left-4 mb-1 px-3 flex items-center text-[12px] text-txt-secondary font-medium select-none pointer-events-none animate-typing-in motion-reduce:animate-none">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="flex gap-[2px] bg-surface-elevated/20 rounded-full px-2 py-1"> <div className="flex gap-[2px] bg-surface-elevated/20 rounded-full px-2 py-1">
<div className="w-[5px] h-[5px] bg-txt-message rounded-full animate-bounce" style={{ animationDelay: '0ms', animationDuration: '0.8s' }} /> <div className="w-[5px] h-[5px] bg-txt-message rounded-full animate-bounce" style={{ animationDelay: '0ms', animationDuration: '0.8s' }} />
@@ -5,7 +5,6 @@ import { useUIStore } from '../../stores/uiStore';
import { useAuthStore } from '../../stores/authStore'; import { useAuthStore } from '../../stores/authStore';
import { MessageList } from '../chat/MessageList'; import { MessageList } from '../chat/MessageList';
import { MessageInput } from '../chat/MessageInput'; import { MessageInput } from '../chat/MessageInput';
import { TypingIndicator } from '../chat/TypingIndicator';
import { VoiceGrid } from '../voice/VoiceGrid'; import { VoiceGrid } from '../voice/VoiceGrid';
import { VoiceControlBar } from '../voice/VoiceControlBar'; import { VoiceControlBar } from '../voice/VoiceControlBar';
import { VoiceChatPanel } from '../voice/VoiceChatPanel'; import { VoiceChatPanel } from '../voice/VoiceChatPanel';
@@ -210,7 +209,6 @@ export function MainContent() {
</div> </div>
</div> </div>
<MessageList channelId={currentChannelId} /> <MessageList channelId={currentChannelId} />
<TypingIndicator channelId={currentChannelId} />
<MessageInput channelId={currentChannelId} channelName={`@${dmName}`} /> <MessageInput channelId={currentChannelId} channelName={`@${dmName}`} />
</div> </div>
); );
@@ -357,7 +355,6 @@ export function MainContent() {
</div> </div>
</div> </div>
<MessageList channelId={currentChannelId} /> <MessageList channelId={currentChannelId} />
<TypingIndicator channelId={currentChannelId} />
<MessageInput channelId={currentChannelId} channelName={channel.name} /> <MessageInput channelId={currentChannelId} channelName={channel.name} />
</div> </div>
); );
@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { MessageList } from '../chat/MessageList'; import { MessageList } from '../chat/MessageList';
import { MessageInput } from '../chat/MessageInput'; import { MessageInput } from '../chat/MessageInput';
import { TypingIndicator } from '../chat/TypingIndicator';
import { useUIStore } from '../../stores/uiStore'; import { useUIStore } from '../../stores/uiStore';
interface VoiceChatPanelProps { interface VoiceChatPanelProps {
@@ -31,9 +30,6 @@ export function VoiceChatPanel({ channelId, channelName }: VoiceChatPanelProps)
{/* Messages */} {/* Messages */}
<MessageList channelId={channelId} /> <MessageList channelId={channelId} />
{/* Typing indicator */}
<TypingIndicator channelId={channelId} />
{/* Input */} {/* Input */}
<MessageInput channelId={channelId} channelName={channelName} /> <MessageInput channelId={channelId} channelName={channelName} />
</div> </div>
+8
View File
@@ -238,6 +238,14 @@
animation: slideUp 0.15s ease-out; animation: slideUp 0.15s ease-out;
} }
@keyframes typingFadeIn {
from { transform: translateY(4px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.animate-typing-in {
animation: typingFadeIn 0.15s ease-out;
}
@keyframes gradientPulse { @keyframes gradientPulse {
0%, 100% { opacity: 0.7; } 0%, 100% { opacity: 0.7; }
50% { opacity: 1; } 50% { opacity: 1; }