mobile adaptation

This commit is contained in:
lendry
2026-06-27 00:41:03 +03:00
parent 4b86c64cc4
commit ee28a7b1db
3 changed files with 86 additions and 51 deletions

View File

@@ -58,6 +58,8 @@ import { InlineEditableTitle } from '@/components/chat/inline-editable-title';
import { RepliedMessagePreview, scrollToChatMessage } from '@/components/chat/replied-message-preview';
import { TypingIndicator } from '@/components/chat/typing-indicator';
import { VoiceMessagePlayer } from '@/components/chat/voice-message-player';
import { NotificationBell } from '@/components/notifications/notification-bell';
import { UserMenu } from '@/components/id/user-menu';
import { UserAvatar } from '@/components/id/user-avatar';
import { useAuth } from '@/components/id/auth-provider';
import { useToast } from '@/components/id/toast-provider';
@@ -1472,29 +1474,44 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
}
if (!isReady || loading) {
return <div className="py-20 text-center text-[#667085]">Загрузка семьи...</div>;
return (
<div className="flex h-[calc(100dvh-4.5rem-env(safe-area-inset-bottom))] items-center justify-center text-[#667085] lg:h-auto lg:py-20">
Загрузка семьи...
</div>
);
}
return (
<div
className={cn(
'flex overflow-hidden bg-[#eef2f8]',
'h-[calc(100dvh-3.5rem-env(safe-area-inset-bottom)-4.5rem)] min-h-0 lg:h-[calc(100vh-120px)] lg:min-h-[640px]',
'flex w-full min-w-0 max-w-full overflow-hidden bg-[#eef2f8]',
'h-[calc(100dvh-4.5rem-env(safe-area-inset-bottom))] min-h-0 lg:h-[calc(100vh-120px)] lg:min-h-[640px]',
'pt-[env(safe-area-inset-top)] lg:pt-0',
'rounded-none border-0 lg:rounded-[28px] lg:border lg:border-[#eceef4]'
)}
>
<aside
className={cn(
'flex shrink-0 flex-col border-r border-[#e4e8ef] bg-white',
'w-full lg:w-[300px]',
'flex min-w-0 max-w-full flex-col overflow-hidden border-r border-[#e4e8ef] bg-white',
'w-full lg:w-[300px] lg:shrink-0',
mobileChatOpen ? 'hidden lg:flex' : 'flex'
)}
>
<div className="border-b border-[#eceef4] p-4">
<button type="button" className="mb-3 flex items-center gap-2 text-sm text-[#667085]" onClick={() => router.push('/family')}>
<ArrowLeft className="h-4 w-4" />
Все семьи
</button>
<div className="shrink-0 border-b border-[#eceef4] p-3 sm:p-4">
<div className="mb-3 flex items-center justify-between gap-2">
<button
type="button"
className="flex min-w-0 items-center gap-2 text-sm text-[#667085]"
onClick={() => router.push('/family')}
>
<ArrowLeft className="h-4 w-4 shrink-0" />
<span className="truncate">Все семьи</span>
</button>
<div className="flex shrink-0 items-center gap-1 lg:hidden">
<NotificationBell />
<UserMenu className="border-0 bg-transparent p-0 pr-0 shadow-none" />
</div>
</div>
<div className="flex items-center gap-3">
<FamilySidebarAvatar
groupId={groupId}
@@ -1513,15 +1530,15 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
</div>
</div>
<div className="mt-3 flex gap-2">
<Button size="sm" variant="secondary" className="flex-1 rounded-xl" onClick={() => setInviteOpen(true)}>
<UserPlus className="mr-1 h-4 w-4" />
<Button size="sm" variant="secondary" className="h-10 flex-1 rounded-xl text-sm" onClick={() => setInviteOpen(true)}>
<UserPlus className="mr-1 h-4 w-4 shrink-0" />
Пригласить
</Button>
</div>
<button
type="button"
onClick={() => setFamilyMembersOpen(true)}
className="mt-3 flex w-full items-center gap-3 rounded-2xl bg-[#f4f5f8] px-3 py-3 text-left transition hover:bg-[#eceef4]"
className="mt-3 flex w-full min-w-0 items-center gap-3 rounded-2xl bg-[#f4f5f8] px-3 py-2.5 text-left transition hover:bg-[#eceef4] sm:py-3"
>
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[#3390ec]/10 text-[#3390ec]">
<Users className="h-5 w-5" />
@@ -1535,45 +1552,53 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
</div>
</button>
</div>
<div className="flex items-center justify-between px-4 py-3">
<div className="flex shrink-0 items-center justify-between px-3 py-2.5 sm:px-4 sm:py-3">
<p className="text-sm font-semibold text-[#667085]">Чаты</p>
<Button size="sm" variant="ghost" className="h-8 w-8 rounded-xl p-0" onClick={() => setCreateRoomOpen(true)}>
<Button size="sm" variant="ghost" className="h-8 w-8 shrink-0 rounded-xl p-0" onClick={() => setCreateRoomOpen(true)}>
<Plus className="h-4 w-4" />
</Button>
</div>
<div className="flex-1 overflow-y-auto">
{rooms.map((room) => (
<div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto overscroll-contain">
{rooms.map((room) => {
const roomLabel = user ? roomDisplayLabel(room, user.id) : room.name;
const preview =
getChatListPreviewText(room.lastMessage) || (room.type === 'GENERAL' ? 'Общий чат семьи' : 'Нет сообщений');
return (
<div
key={room.id}
className={cn(
'group flex items-stretch transition hover:bg-[#f7f9fc]',
'group relative min-w-0 overflow-hidden transition hover:bg-[#f7f9fc]',
activeRoomId === room.id && 'bg-[#3390ec]/10'
)}
>
<button
type="button"
onClick={() => setActiveRoomId(room.id)}
className="flex min-w-0 flex-1 items-start gap-3 px-4 py-3 text-left"
className="flex w-full min-w-0 items-start gap-3 py-3 pl-3 pr-11 text-left sm:pl-4 sm:pr-12"
>
<ChatRoomAvatarDisplay room={room} viewerUserId={user?.id ?? ''} token={token} />
<div className="min-w-0 flex-1">
<div className="flex items-center justify-between gap-2">
<p className="flex min-w-0 items-center gap-1 truncate font-medium">
<span className="truncate">{user ? roomDisplayLabel(room, user.id) : room.name}</span>
{room.peerLeftFamily ? (
<span className="shrink-0 rounded-md bg-amber-50 px-1.5 py-0.5 text-[10px] font-medium text-amber-700">
не в семье
</span>
) : null}
{room.isPinned ? <Pin className="h-3.5 w-3.5 shrink-0 fill-[#8a8f9e] text-[#8a8f9e]" /> : null}
</p>
<div className="shrink-0">
<ChatRoomAvatarDisplay room={room} viewerUserId={user?.id ?? ''} token={token} size="sm" />
</div>
<div className="min-w-0 flex-1 overflow-hidden">
<div className="flex items-center gap-2">
<span className="min-w-0 flex-1 truncate text-[15px] font-medium leading-tight">{roomLabel}</span>
{room.lastMessage ? (
<span className="shrink-0 text-[11px] text-[#a8adbc]">{formatMessageTime(room.lastMessage.createdAt)}</span>
<span className="shrink-0 tabular-nums text-[11px] leading-none text-[#a8adbc]">
{formatMessageTime(room.lastMessage.createdAt)}
</span>
) : null}
</div>
<p className="truncate text-sm text-[#667085]">
{getChatListPreviewText(room.lastMessage) || (room.type === 'GENERAL' ? 'Общий чат семьи' : 'Нет сообщений')}
</p>
<div className="mt-1 flex min-w-0 items-center gap-1.5">
{room.peerLeftFamily ? (
<span className="shrink-0 rounded bg-amber-50 px-1 py-0.5 text-[10px] font-medium leading-none text-amber-700">
не в семье
</span>
) : null}
{room.isPinned ? (
<Pin className="h-3 w-3 shrink-0 fill-[#8a8f9e] text-[#8a8f9e] lg:hidden" />
) : null}
<p className="min-w-0 flex-1 truncate text-sm leading-snug text-[#667085]">{preview}</p>
</div>
</div>
</button>
<button
@@ -1581,7 +1606,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
aria-label={room.isPinned ? 'Открепить чат' : 'Закрепить чат'}
disabled={pinningRoomId === room.id}
className={cn(
'flex h-8 w-8 shrink-0 self-center items-center justify-center rounded-lg text-[#8a8f9e] transition hover:bg-[#eceef4] hover:text-[#667085]',
'absolute right-1 top-1/2 hidden h-9 w-9 -translate-y-1/2 items-center justify-center rounded-lg text-[#8a8f9e] transition hover:bg-[#eceef4] hover:text-[#667085] sm:right-2 lg:flex',
room.isPinned ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
pinningRoomId === room.id && 'opacity-100'
)}
@@ -1594,13 +1619,14 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
)}
</button>
</div>
))}
);
})}
</div>
</aside>
<section
className={cn(
'min-w-0 flex-1 flex-col bg-[#e6ebf2]',
'min-w-0 max-w-full flex-1 flex-col overflow-hidden bg-[#e6ebf2]',
mobileChatOpen ? 'flex' : 'hidden lg:flex'
)}
>
@@ -1644,9 +1670,9 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
<ChatRoomAvatarDisplay room={activeRoom} viewerUserId={user?.id ?? ''} token={token} />
</div>
)}
<button type="button" className="min-w-0 flex-1 text-left" onClick={() => setMembersOpen(true)}>
<h2 className="font-semibold">{activeRoomTitle}</h2>
<p className="text-xs text-[#667085]">
<button type="button" className="min-w-0 flex-1 overflow-hidden text-left" onClick={() => setMembersOpen(true)}>
<h2 className="truncate text-base font-semibold sm:text-lg">{activeRoomTitle}</h2>
<p className="truncate text-xs text-[#667085]">
{activeRoomIsBot ? (
<>🤖 Бот · {activeRoom.members.length} участников</>
) : activeRoomIsE2E ? (

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { ArrowLeft, BarChart3, CalendarDays, ImageIcon, Loader2, MessageCircle, Paperclip, Search, Send, Settings, Smile, Trash2, X } from 'lucide-react';
import { AvatarWithPresence } from '@/components/family/avatar-with-presence';
import { FamilyBotChat, BotMiniAppSheet } from '@/components/family/family-bot-chat';
@@ -704,7 +705,11 @@ export function MiniFamilyChat() {
[group?.members, user?.id]
);
const pathname = usePathname();
const onFamilyChatPage = Boolean(pathname && /^\/family\/[^/]+$/.test(pathname));
if (!user || !token || isPinLocked) return null;
if (onFamilyChatPage) return null;
return (
<>

View File

@@ -27,23 +27,27 @@ export function IdShell({
<EmojiSpriteProvider />
<div className="min-h-screen overflow-x-hidden bg-white">
<Sidebar active={active} />
<div className="fixed left-0 right-0 top-0 z-40 flex items-center justify-between border-b border-[#eceef4] bg-white/95 px-4 py-3 backdrop-blur lg:hidden">
<Link href="/" className="shrink-0">
<BrandLogo size="sm" variant="dark" />
</Link>
<div className="flex items-center gap-2">
<NotificationBell />
<UserMenu />
{!fullBleed ? (
<div className="fixed left-0 right-0 top-0 z-40 flex items-center justify-between border-b border-[#eceef4] bg-white/95 px-4 py-3 backdrop-blur lg:hidden">
<Link href="/" className="shrink-0">
<BrandLogo size="sm" variant="dark" />
</Link>
<div className="flex items-center gap-2">
<NotificationBell />
<UserMenu />
</div>
</div>
</div>
) : null}
<div className="fixed right-4 top-4 z-40 hidden items-center gap-2 lg:right-8 lg:top-6 lg:flex">
<NotificationBell />
<UserMenu />
</div>
<main
className={cn(
'pb-[calc(4.5rem+env(safe-area-inset-bottom))] pt-14 lg:pb-8 lg:pt-8 lg:pl-[220px] lg:pr-8',
fullBleed && 'px-0 pb-[calc(4.5rem+env(safe-area-inset-bottom))] pt-14 lg:px-8'
'pb-[calc(4.5rem+env(safe-area-inset-bottom))] lg:pb-8 lg:pt-8 lg:pl-[220px] lg:pr-8',
fullBleed
? 'px-0 pt-0 lg:px-8'
: 'px-0 pt-14 lg:px-0'
)}
>
<div