feat: dynamic PiP collision avoidance, reaction pill redesign, channel dedup fix
PiP positioning: - Replace hardcoded layout constants with dynamic DOM measurement system - Obstacle elements declare themselves via data-pip-obstacle="left|bottom" - getPipBounds() queries actual element rects at runtime via getBoundingClientRect - MutationObserver + ResizeObserver re-clamp PiP when obstacles appear/resize - PiP reappears after close when navigating away from voice channel Reactions: - Align reaction pills with Aether Drift prototype - Own-reaction accent: purple → mint (bg, border, count color) - Default pills: subtle white overlay bg + border-soft border - Count text: 12px/semibold/txt-secondary per prototype spec Bug fix: - Deduplicate channel insertion in createChannel store action - Prevents double-add race between REST response and WS broadcast
This commit is contained in:
@@ -248,14 +248,14 @@ export function Message({ message, isCompact, isFirstInGroup }: MessageProps) {
|
||||
<button
|
||||
key={emoji}
|
||||
onClick={() => toggleReaction(emoji)}
|
||||
className={`flex items-center gap-1.5 px-1.5 py-0.5 rounded-[8px] text-[14px] font-medium border transition-colors ${
|
||||
className={`flex items-center gap-1 px-2 py-0.5 rounded-md text-[13px] border transition-all duration-[120ms] ease-out cursor-pointer ${
|
||||
me
|
||||
? 'bg-accent-primary/15 border-accent-primary text-accent-primary'
|
||||
: 'bg-surface-channel border-transparent text-txt-tertiary hover:border-txt-tertiary/30'
|
||||
? 'bg-accent-mint/10 border-accent-mint/25 hover:bg-accent-mint/15'
|
||||
: 'bg-white/[0.04] border-border-soft hover:bg-white/[0.08] hover:border-white/[0.12]'
|
||||
}`}
|
||||
>
|
||||
<span>{emoji}</span>
|
||||
<span className={me ? 'text-accent-primary' : 'text-txt-message'}>{count}</span>
|
||||
<span className={`text-xs font-semibold ${me ? 'text-accent-mint' : 'text-txt-secondary'}`}>{count}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useRef, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import { useChatStore } from '../../stores/chatStore';
|
||||
import { isDmChannel, useServerStore } from '../../stores/serverStore';
|
||||
import { wsSend } from '../../hooks/useWebSocket';
|
||||
@@ -30,6 +30,18 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
const members = useServerStore((s) => s.members);
|
||||
const typingTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
// Auto-focus textarea on channel navigation
|
||||
useEffect(() => {
|
||||
textareaRef.current?.focus();
|
||||
}, [channelId]);
|
||||
|
||||
// Auto-focus textarea when replying
|
||||
useEffect(() => {
|
||||
if (replyTo) {
|
||||
textareaRef.current?.focus();
|
||||
}
|
||||
}, [replyTo]);
|
||||
|
||||
// Filter members for the mention popover
|
||||
const filteredMembers = useMemo(() => {
|
||||
if (!mentionState) return [];
|
||||
@@ -216,7 +228,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div 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="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]">
|
||||
{replyTo && (
|
||||
<div className="bg-interactive-hover rounded-t-lg px-4 py-2 flex items-center justify-between border-b border-border-hard/50">
|
||||
<div className="flex items-center gap-1 text-[14px] text-txt-message truncate">
|
||||
|
||||
Reference in New Issue
Block a user