mobile adaptation
This commit is contained in:
@@ -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" />
|
||||
Все семьи
|
||||
<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>
|
||||
<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 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 ? (
|
||||
<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>
|
||||
) : null}
|
||||
{room.isPinned ? <Pin className="h-3.5 w-3.5 shrink-0 fill-[#8a8f9e] text-[#8a8f9e]" /> : null}
|
||||
</p>
|
||||
{room.lastMessage ? (
|
||||
<span className="shrink-0 text-[11px] text-[#a8adbc]">{formatMessageTime(room.lastMessage.createdAt)}</span>
|
||||
{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>
|
||||
<p className="truncate text-sm text-[#667085]">
|
||||
{getChatListPreviewText(room.lastMessage) || (room.type === 'GENERAL' ? 'Общий чат семьи' : 'Нет сообщений')}
|
||||
</p>
|
||||
</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 ? (
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -27,6 +27,7 @@ export function IdShell({
|
||||
<EmojiSpriteProvider />
|
||||
<div className="min-h-screen overflow-x-hidden bg-white">
|
||||
<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">
|
||||
<Link href="/" className="shrink-0">
|
||||
<BrandLogo size="sm" variant="dark" />
|
||||
@@ -36,14 +37,17 @@ export function IdShell({
|
||||
<UserMenu />
|
||||
</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
|
||||
|
||||
Reference in New Issue
Block a user