Новый стиль
This commit is contained in:
@@ -9,6 +9,7 @@ import { useLang } from '../../../core/infrastructure/i18n';
|
||||
import type { Message, UserBasic, CallInfo, ChatMember } from '../../../core/domain/types';
|
||||
import { Send, Check, Phone, PhoneOff } from 'lucide-react';
|
||||
import Sidebar from '../../../core/presentation/layouts/Sidebar';
|
||||
import GlobalNavBar from '../../../core/presentation/layouts/GlobalNavBar';
|
||||
import ChatView from './components/ChatView';
|
||||
import CallModal from '../../calls/presentation/components/CallModal';
|
||||
import GroupCallModal from '../../calls/presentation/components/GroupCallModal';
|
||||
@@ -362,14 +363,25 @@ export default function ChatPage() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="h-full flex bg-surface sm:p-3 sm:gap-3 overflow-hidden"
|
||||
className="h-screen w-screen flex bg-surface-dim overflow-hidden antialiased font-body selection:bg-primary/30"
|
||||
>
|
||||
<div className={`${activeChat ? 'hidden sm:block' : 'block'} w-full sm:w-[340px] flex-shrink-0`}>
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className={`${activeChat ? 'block' : 'hidden sm:block'} flex-1 h-full min-w-0`}>
|
||||
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
|
||||
</div>
|
||||
<GlobalNavBar activeTab="chats" onTabChange={() => {}} />
|
||||
|
||||
<main className="ml-20 flex-1 flex flex-row relative h-full">
|
||||
{/* Chat List (Sidebar) */}
|
||||
<div
|
||||
className={`${activeChat ? 'hidden lg:block' : 'block'} w-full lg:w-[360px] flex-shrink-0 bg-surface-container-low tonal-transition-no-border h-full`}
|
||||
>
|
||||
<Sidebar />
|
||||
</div>
|
||||
|
||||
{/* Selected Chat View (Main Area) */}
|
||||
<div
|
||||
className={`${activeChat ? 'block' : 'hidden lg:block'} flex-1 h-full min-w-0 bg-surface-container-lowest m-4 rounded-3xl overflow-hidden relative group slide-on-ice`}
|
||||
>
|
||||
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
|
||||
</div>
|
||||
</main>
|
||||
<CallModal
|
||||
key={callSessionId}
|
||||
isOpen={callOpen}
|
||||
|
||||
@@ -141,18 +141,20 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
|
||||
<button
|
||||
onClick={handleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
className={`w-full flex items-center gap-3 px-3 py-3 transition-colors text-left ${
|
||||
isActive ? 'bg-accent/15 border-r-2 border-accent' : 'hover:bg-surface-hover'
|
||||
className={`w-full flex items-center gap-4 px-4 py-3.5 transition-all duration-300 slide-on-ice text-left rounded-2xl mx-1 my-0.5 w-[calc(100%-8px)] ${
|
||||
isActive ? 'bg-primary/10' : 'hover:bg-surface-container-highest/20'
|
||||
}`}
|
||||
>
|
||||
{/* Аватар */}
|
||||
<div className="relative flex-shrink-0">
|
||||
{isFavorites ? (
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg">
|
||||
<Bookmark size={22} className="text-white" />
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary-container flex items-center justify-center shadow-lg shadow-primary/10 border-2 border-outline-variant/10">
|
||||
<span className="material-symbols-outlined text-on-primary-container text-[24px]">bookmark</span>
|
||||
</div>
|
||||
) : (
|
||||
<Avatar src={chatAvatar || undefined} name={chatName || '??'} size="lg" online={isOnline ? true : false} />
|
||||
<div className="avatar-knot-container group-hover:scale-105 transition-transform">
|
||||
<Avatar src={chatAvatar || undefined} name={chatName || '??'} size="lg" online={isOnline ? true : false} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -160,28 +162,26 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
{isPinned && <Pin size={12} className="text-knot-400 flex-shrink-0 rotate-45" />}
|
||||
<span className="text-sm font-medium text-white truncate">{chatName}</span>
|
||||
{isPinned && <span className="material-symbols-outlined text-[14px] text-primary rotate-45">push_pin</span>}
|
||||
<span className={`text-sm font-bold tracking-tight truncate ${isActive ? 'text-primary' : 'text-on-surface'}`}>{chatName}</span>
|
||||
</div>
|
||||
{timeStr && <span className="text-xs text-zinc-500 flex-shrink-0 ml-2">{timeStr}</span>}
|
||||
{timeStr && <span className="text-[10px] uppercase font-bold tracking-wider text-on-surface-variant/50 flex-shrink-0 ml-2">{timeStr}</span>}
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-0.5">
|
||||
<div className="flex items-center gap-1 min-w-0 flex-1">
|
||||
{isMine && lastMessage && !lastMessage.isDeleted && (
|
||||
<span className="flex-shrink-0">
|
||||
{isRead ? (
|
||||
<CheckCheck size={14} className="text-knot-400" />
|
||||
) : (
|
||||
<Check size={14} className="text-zinc-500" />
|
||||
)}
|
||||
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
|
||||
{isRead ? 'done_all' : 'done'}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<p className={`text-xs truncate ${isTyping ? 'text-knot-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
|
||||
{isTyping ? t('typing') : draft ? <><span className="font-medium">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
|
||||
<p className={`text-[13px] truncate leading-tight ${isTyping ? 'text-tertiary font-bold' : draft ? 'text-error font-medium' : 'text-on-surface-variant/60'}`}>
|
||||
{isTyping ? t('typing') : draft ? <><span className="font-bold">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
|
||||
</p>
|
||||
</div>
|
||||
{chat.unreadCount > 0 && !isActive && (
|
||||
<span className="ml-2 flex-shrink-0 min-w-[20px] h-5 px-1.5 rounded-full bg-accent flex items-center justify-center text-[11px] text-white font-medium">
|
||||
<span className="ml-2 flex-shrink-0 min-w-[20px] h-5 px-1.5 rounded-full bg-primary text-on-primary-container flex items-center justify-center text-[10px] font-black shadow-lg shadow-primary/20 animate-pulse">
|
||||
{chat.unreadCount}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -501,15 +501,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
<div
|
||||
ref={chatViewRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
className={`flex-1 flex flex-col h-full sm:rounded-[2rem] overflow-hidden shadow-[0_0_120px_-20px_rgba(0,0,0,0.5)] border-x sm:border border-border/50 relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
|
||||
className={`flex-1 flex flex-col h-full overflow-hidden bg-surface-container-lowest relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
|
||||
>
|
||||
<div className="absolute inset-0 pointer-events-none bg-gradient-to-b from-primary/5 to-transparent h-32 opacity-30" />
|
||||
{selectionMode ? (
|
||||
<div className="h-[76px] flex items-center justify-between px-4 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 animate-in slide-in-from-top-2">
|
||||
<div className="flex items-center gap-2 sm:gap-4 text-white">
|
||||
<button onClick={() => { setSelectionMode(false); setSelectedMessages(new Set()); }} className="p-2 -ml-2 rounded-full hover:bg-white/10 transition">
|
||||
<X size={20} className="text-zinc-300" />
|
||||
<div className="h-[76px] flex items-center justify-between px-6 bg-surface-container-highest/80 backdrop-blur-2xl z-20 flex-shrink-0 animate-in slide-in-from-top-2 border-none">
|
||||
<div className="flex items-center gap-4 text-on-surface">
|
||||
<button onClick={() => { setSelectionMode(false); setSelectedMessages(new Set()); }} className="p-2 -ml-2 rounded-xl hover:bg-on-surface/10 transition slide-on-ice">
|
||||
<span className="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
<span className="font-medium text-[15px]">{selectedMessages.size} {t('selected') || 'выбрано'}</span>
|
||||
<span className="text-lg font-bold font-headline">{selectedMessages.size} {t('selected')}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="relative" ref={deleteMenuRef}>
|
||||
@@ -560,16 +561,16 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[76px] flex items-center justify-between px-3 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 gap-2">
|
||||
<div className="h-[76px] flex items-center justify-between px-6 bg-surface-container-lowest/40 backdrop-blur-xl z-20 flex-shrink-0 gap-2 border-none">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
onClick={() => setActiveChat(null)}
|
||||
className="sm:hidden p-2 -ml-1 rounded-xl hover:bg-surface-hover text-zinc-400 transition-colors"
|
||||
className="lg:hidden p-2 -ml-1 rounded-xl hover:bg-surface-container-highest/30 text-on-surface-variant transition-colors"
|
||||
>
|
||||
<ArrowLeft size={22} />
|
||||
<span className="material-symbols-outlined">arrow_back</span>
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-3 min-w-0 flex-1 group transition-all"
|
||||
<div
|
||||
className="flex items-center gap-4 min-w-0 flex-1 group cursor-pointer"
|
||||
onClick={() => {
|
||||
if (chat.type === 'personal' && otherMember) {
|
||||
setProfileUserId(otherMember.user.id);
|
||||
@@ -578,10 +579,10 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative flex-shrink-0 transform transition-transform duration-300 group-hover:scale-105">
|
||||
<div className="relative flex-shrink-0 transform transition-all duration-500 group-hover:scale-105 active:scale-95">
|
||||
{isFavorites ? (
|
||||
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300">
|
||||
<Bookmark size={20} className="text-white" />
|
||||
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-primary to-primary-container flex items-center justify-center shadow-lg shadow-primary/10 border-2 border-outline-variant/10">
|
||||
<span className="material-symbols-outlined text-on-primary-container text-[20px]">bookmark</span>
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
@@ -589,27 +590,27 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
name={chatName}
|
||||
size="md"
|
||||
online={isOnline ? true : undefined}
|
||||
className="ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300 rounded-full"
|
||||
className="avatar-knot-container group-hover:border-primary/30"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
<h3 className="text-base font-semibold text-white truncate drop-shadow-sm group-hover:text-accent/90 transition-colors">{chatName}</h3>
|
||||
<p className="text-xs text-zinc-400 truncate">
|
||||
<h3 className="text-lg font-bold font-headline text-on-surface truncate group-hover:text-primary transition-colors leading-none mb-1">{chatName}</h3>
|
||||
<p className="text-[11px] font-bold uppercase tracking-widest text-on-surface-variant/50">
|
||||
{isFavorites
|
||||
? t('favoritesDescription')
|
||||
: typingInChat.length > 0
|
||||
? <span className="text-accent font-medium">{t('typing')}</span>
|
||||
? <span className="text-primary font-black animate-pulse">{t('typing')}</span>
|
||||
: isOnline
|
||||
? <span className="text-emerald-400">{t('online')}</span>
|
||||
? <span className="text-success">{t('online')}</span>
|
||||
: chat.type === 'personal' && otherMember?.user.lastSeen
|
||||
? `${t('lastSeenAt')} ${formatLastSeen(otherMember.user.lastSeen, lang)}`
|
||||
? `${formatLastSeen(otherMember.user.lastSeen, lang)}`
|
||||
: chat.type === 'group'
|
||||
? `${chat.members.length} ${t('members')}`
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 ml-4">
|
||||
@@ -627,7 +628,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
placeholder={t('searchMessages')}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
className="w-full px-3 py-1.5 rounded-lg bg-surface-tertiary text-sm text-white placeholder-zinc-500 border border-border focus:border-accent"
|
||||
className="w-full px-4 py-2 rounded-xl bg-surface-container text-sm text-on-surface placeholder-on-surface-variant/30 border-none focus:ring-2 focus:ring-primary/20"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -643,9 +644,9 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
openSearch();
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
|
||||
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice"
|
||||
>
|
||||
{showSearch ? <X size={18} /> : <Search size={18} />}
|
||||
{showSearch ? <span className="material-symbols-outlined">close</span> : <span className="material-symbols-outlined">search</span>}
|
||||
</button>
|
||||
|
||||
{!isFavorites && config?.enableCalls && (
|
||||
@@ -658,8 +659,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
onStartGroupCall?.(chat.id, chat.name || 'Group', 'voice');
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white" title={t('call')}>
|
||||
<Phone size={18} />
|
||||
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice" title={t('call')}>
|
||||
<span className="material-symbols-outlined">call</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -669,8 +670,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
onStartGroupCall?.(chat.id, chat.name || 'Group', 'video');
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white" title={t('videoCall')}>
|
||||
<Video size={18} />
|
||||
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice" title={t('videoCall')}>
|
||||
<span className="material-symbols-outlined">videocam</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@@ -678,9 +679,9 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
<div className="relative" ref={topMenuRef}>
|
||||
<button
|
||||
onClick={() => setShowTopMenu(!showTopMenu)}
|
||||
className="p-2 rounded-lg hover:bg-surface-hover transition-colors text-zinc-400 hover:text-white"
|
||||
className="p-2 rounded-xl hover:bg-surface-container-highest/40 transition-all duration-300 text-on-surface-variant hover:text-primary slide-on-ice"
|
||||
>
|
||||
<MoreVertical size={18} />
|
||||
<span className="material-symbols-outlined">more_vert</span>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{showTopMenu && (
|
||||
|
||||
@@ -426,12 +426,12 @@ function MessageBubble({
|
||||
onContextMenu={handleContextMenu}
|
||||
onDoubleClick={handleReply}
|
||||
title={t('reply') ? `${t('reply')} (Double Click)` : 'Double click to reply'}
|
||||
className={`cursor-pointer max-w-full min-w-[40px] rounded-[1.25rem] transition-all duration-300 overflow-hidden ${
|
||||
className={`cursor-pointer max-w-full min-w-[60px] transition-all duration-500 overflow-hidden ${
|
||||
!needsFrame
|
||||
? 'p-0 shadow-none border-none bg-transparent'
|
||||
: isMine
|
||||
? 'bubble-sent text-white shadow-sm px-[14px] py-[8px] hover:shadow-md rounded-br-sm'
|
||||
: 'bubble-received text-zinc-100 shadow-sm px-[14px] py-[8px] hover:shadow-md rounded-bl-[4px]'
|
||||
? 'bubble-sent px-4 py-2.5 hover:bg-primary/[0.12] rounded-2xl rounded-br-[4px]'
|
||||
: 'bubble-received px-4 py-2.5 hover:bg-surface-container-highest/80 rounded-2xl rounded-bl-[4px]'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -451,7 +451,7 @@ function MessageBubble({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p className={`text-[13.5px] font-semibold mb-0.5 truncate ${isMine ? 'text-white' : 'text-knot-500'}`}>
|
||||
<p className={`text-[13px] font-bold mb-0.5 truncate ${isMine ? 'text-primary' : 'text-primary'}`}>
|
||||
{message.replyTo.sender?.displayName || message.replyTo.sender?.userName || message.replyTo.sender?.username || ''}
|
||||
</p>
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -481,7 +481,7 @@ function MessageBubble({
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<p className={`text-[13px] line-clamp-2 break-words whitespace-pre-wrap ${isMine ? 'text-white/80' : 'text-zinc-600 dark:text-zinc-300'}`}>
|
||||
<p className={`text-[13px] line-clamp-2 break-words whitespace-pre-wrap ${isMine ? 'text-on-surface-variant' : 'text-on-surface-variant'}`}>
|
||||
{message.quote || message.replyTo.content || (message.replyTo.media && message.replyTo.media.length > 0 ? (() => {
|
||||
const m = message.replyTo.media[0];
|
||||
if (m.type === 'image' && m.url?.toLowerCase().endsWith('.mp4')) return 'GIF';
|
||||
@@ -619,14 +619,12 @@ function MessageBubble({
|
||||
|
||||
{!message.content && (
|
||||
<div className="absolute bottom-1.5 right-1.5 z-10 pointer-events-none flex justify-end">
|
||||
<span className="text-[10px] text-white/80 bg-black/40 shadow-sm px-2 py-0.5 rounded-full flex items-center gap-1 backdrop-blur-md pointer-events-auto">
|
||||
<span className="text-[10px] font-bold text-on-surface-variant/40 bg-surface-container-highest/40 px-2 py-0.5 rounded-full flex items-center gap-1 backdrop-blur-md pointer-events-auto">
|
||||
{timeStr}
|
||||
{isMine && !message.scheduledAt && (
|
||||
isRead ? (
|
||||
<CheckCheck size={13} className="text-sky-300" />
|
||||
) : (
|
||||
<Check size={13} />
|
||||
)
|
||||
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
|
||||
{isRead ? 'done_all' : 'done'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -677,8 +675,8 @@ function MessageBubble({
|
||||
<div
|
||||
key={i}
|
||||
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
|
||||
? isMine ? 'bg-white/80' : 'bg-knot-400'
|
||||
: isMine ? 'bg-white/20' : 'bg-white/10'
|
||||
? isMine ? 'bg-primary/40' : 'bg-primary'
|
||||
: isMine ? 'bg-primary/10' : 'bg-on-surface-variant/10'
|
||||
}`}
|
||||
style={{ height: `${barHeight}%` }}
|
||||
/>
|
||||
@@ -797,17 +795,14 @@ function MessageBubble({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className={`text-[10.5px] flex-shrink-0 flex items-center gap-0.5 self-end float-right leading-none ${isOnlyEmojis ? '-mb-1' : 'mb-0.5'} ${isMine ? 'text-white/60' : 'text-zinc-500'
|
||||
}`}>
|
||||
<span className={`text-[10px] font-bold flex-shrink-0 flex items-center gap-0.5 self-end float-right leading-none ${isOnlyEmojis ? '-mb-1' : 'mb-0.5'} ${isMine ? 'text-primary/60' : 'text-on-surface-variant/40'}`}>
|
||||
{message.isEdited && <span className="mr-0.5">{t('edited')}</span>}
|
||||
{message.scheduledAt && <Clock size={11} className="text-amber-400 mr-0.5" />}
|
||||
{message.scheduledAt && <span className="material-symbols-outlined text-[12px] text-amber-400 mr-0.5">schedule</span>}
|
||||
{timeStr}
|
||||
{isMine && !message.scheduledAt && (
|
||||
isRead ? (
|
||||
<CheckCheck size={14} className="text-sky-300 ml-0.5" />
|
||||
) : (
|
||||
<Check size={14} className="ml-0.5" />
|
||||
)
|
||||
<span className={`material-symbols-outlined text-[14px] ${isRead ? 'text-primary fill-1' : 'text-on-surface-variant/40'}`} style={{ fontVariationSettings: `'FILL' ${isRead ? 1 : 0}` }}>
|
||||
{isRead ? 'done_all' : 'done'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -589,11 +589,11 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-knot-500/10 border-2 border-dashed border-knot-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
|
||||
className="absolute inset-0 z-50 rounded-3xl mx-6 mb-6 mt-2 bg-primary/10 border-2 border-dashed border-primary/40 backdrop-blur-sm flex items-center justify-center pointer-events-none"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 text-knot-300">
|
||||
<FileText size={32} className="animate-bounce" />
|
||||
<p className="font-semibold">{t('dropFileHere')}</p>
|
||||
<div className="flex flex-col items-center gap-4 text-primary">
|
||||
<span className="material-symbols-outlined text-5xl animate-bounce">upload_file</span>
|
||||
<p className="font-bold text-lg font-headline uppercase tracking-widest">{t('dropFileHere')}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
@@ -603,27 +603,25 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
<AnimatePresence>
|
||||
{(replyTo || editingMessage) && (
|
||||
<motion.div
|
||||
initial={{ height: 0, opacity: 0, y: 10, scale: 0.95 }}
|
||||
animate={{ height: 'auto', opacity: 1, y: 0, scale: 1 }}
|
||||
exit={{ height: 0, opacity: 0, y: 10, scale: 0.95 }}
|
||||
className="mb-2 max-w-3xl mx-auto overflow-hidden px-1.5"
|
||||
initial={{ height: 0, opacity: 0, scale: 0.98 }}
|
||||
animate={{ height: 'auto', opacity: 1, scale: 1 }}
|
||||
exit={{ height: 0, opacity: 0, scale: 0.98 }}
|
||||
className="mb-2 max-w-4xl mx-auto overflow-hidden"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-4 py-2.5 bg-white/[0.04] backdrop-blur-2xl border border-white/10 rounded-2xl relative shadow-xl">
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-knot-400 to-purple-500 rounded-r-md" />
|
||||
<div className="w-6 h-6 rounded-full bg-white/5 flex items-center justify-center flex-shrink-0">
|
||||
{editingMessage ? (
|
||||
<Pencil size={12} className="text-knot-400" />
|
||||
) : (
|
||||
<Reply size={12} className="text-knot-400" />
|
||||
)}
|
||||
<div className="flex items-center gap-4 px-5 py-3 bg-surface-container-high/80 backdrop-blur-2xl border-none rounded-2xl relative shadow-xl slide-on-ice">
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1.5 h-3/5 bg-primary rounded-r-full" />
|
||||
<div className="w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center flex-shrink-0">
|
||||
<span className="material-symbols-outlined text-primary text-[20px]">
|
||||
{editingMessage ? 'edit' : 'reply'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||
<p className="text-xs font-semibold text-knot-400 mb-0.5">
|
||||
<p className="text-[11px] font-black tracking-widest uppercase text-primary mb-0.5">
|
||||
{editingMessage
|
||||
? t('editing')
|
||||
: `${t('replyTo')} ${replyTo?.sender?.displayName || replyTo?.sender?.userName || replyTo?.sender?.username || ''}`}
|
||||
</p>
|
||||
<div className="text-xs text-zinc-300 truncate opacity-80 border-l border-white/20 pl-2 ml-1">
|
||||
<div className="text-[13px] text-on-surface-variant truncate opacity-80 pl-1 border-l border-on-surface-variant/10 ml-0.5">
|
||||
{replyTo?.quote ? `«${replyTo.quote}»` : (editingMessage || replyTo)?.content || t('media') || 'Медиа'}
|
||||
</div>
|
||||
</div>
|
||||
@@ -634,9 +632,9 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
setAttachments([]);
|
||||
setText('');
|
||||
}}
|
||||
className="w-7 h-7 rounded-full flex items-center justify-center text-white/40 hover:text-white hover:bg-white/10 transition-colors"
|
||||
className="w-8 h-8 rounded-full flex items-center justify-center text-on-surface-variant/40 hover:text-primary hover:bg-primary/10 transition-all slide-on-ice"
|
||||
>
|
||||
<X size={14} />
|
||||
<span className="material-symbols-outlined text-[20px]">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -711,21 +709,23 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
|
||||
{/* Recording UI */}
|
||||
{isRecording ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex items-center gap-4 bg-surface-container-high rounded-[2rem] px-6 py-4 w-full max-w-4xl mx-auto shadow-2xl animate-in slide-in-from-bottom-4 slide-on-ice">
|
||||
<button
|
||||
onClick={cancelRecording}
|
||||
className="p-2 rounded-lg text-red-400 hover:bg-red-500/10 transition-colors flex-shrink-0"
|
||||
className="w-10 h-10 rounded-full flex items-center justify-center text-error hover:bg-error/10 transition-all slide-on-ice"
|
||||
>
|
||||
<X size={20} />
|
||||
<span className="material-symbols-outlined">delete</span>
|
||||
</button>
|
||||
<div className="flex-1 flex items-center gap-3">
|
||||
<span className="w-2.5 h-2.5 rounded-full bg-red-500 animate-pulse" />
|
||||
<span className="text-sm text-zinc-300 font-mono">{formatTime(recordingTime)}</span>
|
||||
<div className="flex-1 flex items-center gap-0.5 h-6">
|
||||
<div className="flex-1 flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-3 h-3 rounded-full bg-error animate-pulse shadow-[0_0_8px_rgba(244,67,54,0.5)]" />
|
||||
<span className="text-sm font-bold font-mono text-on-surface tracking-tighter w-12">{formatTime(recordingTime)}</span>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center gap-[3px] h-8 items-center">
|
||||
{liveBars.map((h, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-1 bg-knot-400/60 rounded-full transition-all duration-100"
|
||||
className="flex-1 bg-primary/40 rounded-full transition-all duration-100"
|
||||
style={{ height: `${h}%` }}
|
||||
/>
|
||||
))}
|
||||
@@ -733,20 +733,20 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
</div>
|
||||
<button
|
||||
onClick={stopRecording}
|
||||
className="p-3 rounded-full bg-accent hover:bg-accent-hover transition-colors text-white flex-shrink-0"
|
||||
className="w-12 h-12 rounded-2xl bg-primary text-on-primary flex items-center justify-center hover:scale-105 active:scale-95 transition-all shadow-lg shadow-primary/20 slide-on-ice"
|
||||
>
|
||||
<Send size={18} />
|
||||
<span className="material-symbols-outlined">send</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-end gap-1.5 bg-surface-secondary rounded-2xl border border-border/40 px-2 py-1.5 w-full max-w-4xl mx-auto transition-all duration-200 focus-within:border-border/80">
|
||||
<div className="flex items-end gap-2 bg-surface-container-high rounded-[2rem] px-4 py-2 w-full max-w-4xl mx-auto shadow-sm transition-all duration-500 focus-within:shadow-xl focus-within:bg-surface-container-highest slide-on-ice">
|
||||
{/* Attach */}
|
||||
<div className="relative mb-0 flex-shrink-0 self-center">
|
||||
<button
|
||||
onClick={() => setShowAttachMenu(!showAttachMenu)}
|
||||
className="p-2 rounded-full text-zinc-400 hover:text-accent hover:bg-surface-hover transition-colors"
|
||||
className="w-10 h-10 rounded-full text-on-surface-variant hover:text-primary hover:bg-primary/10 transition-all slide-on-ice flex items-center justify-center"
|
||||
>
|
||||
<Paperclip size={22} strokeWidth={1.5} />
|
||||
<span className="material-symbols-outlined text-[24px]">add</span>
|
||||
</button>
|
||||
<AnimatePresence>
|
||||
{showAttachMenu && (
|
||||
@@ -887,14 +887,13 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
onContextMenu={handleInputContextMenu}
|
||||
className="w-full bg-transparent border-none focus:ring-0 text-on-surface placeholder-on-surface-variant/30 text-[15px] leading-relaxed resize-none max-h-[150px] custom-scrollbar scroll-smooth outline-none py-1"
|
||||
placeholder={attachments.length > 0 ? t('addCaption') : t('message')}
|
||||
rows={1}
|
||||
className="w-full resize-none bg-transparent text-[15px] text-white placeholder-zinc-500 leading-[22px] py-[10px] px-2 border-none focus:ring-0 max-h-[150px] outline-none"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Emoji */}
|
||||
<div className="relative mb-0 flex-shrink-0 self-center">
|
||||
{/* Action Buttons */}
|
||||
<div className="flex items-center gap-1 self-center pb-2 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => setShowEmoji(!showEmoji)}
|
||||
className="p-2 rounded-full text-zinc-400 hover:text-accent hover:bg-surface-hover transition-colors"
|
||||
|
||||
Reference in New Issue
Block a user