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

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Link from 'next/link'; 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 { 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 { AvatarWithPresence } from '@/components/family/avatar-with-presence';
import { FamilyBotChat, BotMiniAppSheet } from '@/components/family/family-bot-chat'; import { FamilyBotChat, BotMiniAppSheet } from '@/components/family/family-bot-chat';
@@ -704,7 +705,11 @@ export function MiniFamilyChat() {
[group?.members, user?.id] [group?.members, user?.id]
); );
const pathname = usePathname();
const onFamilyChatPage = Boolean(pathname && /^\/family\/[^/]+$/.test(pathname));
if (!user || !token || isPinLocked) return null; if (!user || !token || isPinLocked) return null;
if (onFamilyChatPage) return null;
return ( return (
<> <>

View File

@@ -27,6 +27,7 @@ export function IdShell({
<EmojiSpriteProvider /> <EmojiSpriteProvider />
<div className="min-h-screen overflow-x-hidden bg-white"> <div className="min-h-screen overflow-x-hidden bg-white">
<Sidebar active={active} /> <Sidebar active={active} />
{!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"> <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"> <Link href="/" className="shrink-0">
<BrandLogo size="sm" variant="dark" /> <BrandLogo size="sm" variant="dark" />
@@ -36,14 +37,17 @@ export function IdShell({
<UserMenu /> <UserMenu />
</div> </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"> <div className="fixed right-4 top-4 z-40 hidden items-center gap-2 lg:right-8 lg:top-6 lg:flex">
<NotificationBell /> <NotificationBell />
<UserMenu /> <UserMenu />
</div> </div>
<main <main
className={cn( 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', 'pb-[calc(4.5rem+env(safe-area-inset-bottom))] 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' fullBleed
? 'px-0 pt-0 lg:px-8'
: 'px-0 pt-14 lg:px-0'
)} )}
> >
<div <div