fix(communications): compact mobile messenger workspace
This commit is contained in:
parent
f2ac97a841
commit
3425be1b8a
10 changed files with 70 additions and 73 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"generatedAt": "2026-09-12T05:25:45.504Z",
|
"generatedAt": "2026-09-12T06:26:42.753Z",
|
||||||
"module": "communications",
|
"module": "communications",
|
||||||
"version": "v3.0",
|
"version": "v3.0",
|
||||||
"referenceMockups": [
|
"referenceMockups": [
|
||||||
|
|
@ -128,19 +128,19 @@
|
||||||
"filename": "mobile-chat.png",
|
"filename": "mobile-chat.png",
|
||||||
"theme": "state-capture",
|
"theme": "state-capture",
|
||||||
"viewport": "mobile-chat",
|
"viewport": "mobile-chat",
|
||||||
"capturedAt": "2026-09-12T05:25:36.209Z"
|
"capturedAt": "2026-09-12T06:26:42.411Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "mobile-contact.png",
|
"filename": "mobile-contact.png",
|
||||||
"theme": "state-capture",
|
"theme": "state-capture",
|
||||||
"viewport": "mobile-contact",
|
"viewport": "mobile-contact",
|
||||||
"capturedAt": "2026-09-12T05:25:36.390Z"
|
"capturedAt": "2026-09-12T06:26:42.582Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "mobile-list.png",
|
"filename": "mobile-list.png",
|
||||||
"theme": "state-capture",
|
"theme": "state-capture",
|
||||||
"viewport": "mobile-list",
|
"viewport": "mobile-list",
|
||||||
"capturedAt": "2026-09-12T05:25:35.998Z"
|
"capturedAt": "2026-09-12T06:26:42.215Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename": "noble-green-desktop-1280.png",
|
"filename": "noble-green-desktop-1280.png",
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 68 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 78 KiB |
|
|
@ -134,7 +134,7 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3
|
||||||
await expect(backToListBtn).toBeVisible();
|
await expect(backToListBtn).toBeVisible();
|
||||||
|
|
||||||
// Check bottom contact teaser button
|
// Check bottom contact teaser button
|
||||||
const profileTeaserBtn = activeChat.locator('button[aria-label="Открыть карточку контакта"]');
|
const profileTeaserBtn = activeChat.getByRole('button', { name: /Открыть.*контакт/i });
|
||||||
await expect(profileTeaserBtn).toBeVisible();
|
await expect(profileTeaserBtn).toBeVisible();
|
||||||
|
|
||||||
// Check message composer is positioned within viewport
|
// Check message composer is positioned within viewport
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { QuickActionsModal } from '../shell/QuickActionsModal';
|
||||||
import { NotificationsDrawer } from '../shell/NotificationsDrawer';
|
import { NotificationsDrawer } from '../shell/NotificationsDrawer';
|
||||||
import { Drawer } from '../../shared/ui';
|
import { Drawer } from '../../shared/ui';
|
||||||
import { useResponsive } from '../../shared/lib/useResponsive';
|
import { useResponsive } from '../../shared/lib/useResponsive';
|
||||||
|
import { useCommunicationsStore } from '../../features/communications/services/communicationsService';
|
||||||
|
|
||||||
export const AppShell: React.FC = () => {
|
export const AppShell: React.FC = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -25,6 +26,7 @@ export const AppShell: React.FC = () => {
|
||||||
const profile = getActiveProfile();
|
const profile = getActiveProfile();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isCommunicationsPage = location.pathname.startsWith('/communications');
|
const isCommunicationsPage = location.pathname.startsWith('/communications');
|
||||||
|
const isMobileChatOpen = useCommunicationsStore((state) => state.isMobileChatOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.documentElement.setAttribute('data-theme', theme);
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
|
@ -80,13 +82,13 @@ export const AppShell: React.FC = () => {
|
||||||
|
|
||||||
{/* Center Work Area */}
|
{/* Center Work Area */}
|
||||||
<div className="flex-1 flex flex-col min-w-0 overflow-hidden bg-krona-workspace">
|
<div className="flex-1 flex flex-col min-w-0 overflow-hidden bg-krona-workspace">
|
||||||
<Topbar />
|
{!(isEffectiveMobile && isCommunicationsPage && isMobileChatOpen) && <Topbar />}
|
||||||
|
|
||||||
{/* Main Content Router Outlet */}
|
{/* Main Content Router Outlet */}
|
||||||
<main
|
<main
|
||||||
className={`flex-1 min-h-0 ${
|
className={`flex-1 min-h-0 ${
|
||||||
isCommunicationsPage
|
isCommunicationsPage
|
||||||
? `overflow-hidden flex flex-col pt-0 px-0 ${isEffectiveMobile ? 'pb-20' : 'pb-0'}`
|
? `overflow-hidden flex flex-col pt-0 px-0 ${isEffectiveMobile ? 'pb-14' : 'pb-0'}`
|
||||||
: `overflow-y-auto ${isEffectiveMobile ? 'pb-24' : 'pb-8'} p-3 sm:p-4 md:p-6`
|
: `overflow-y-auto ${isEffectiveMobile ? 'pb-24' : 'pb-8'} p-3 sm:p-4 md:p-6`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@ export const BottomNav: React.FC = () => {
|
||||||
<nav
|
<nav
|
||||||
aria-label="Мобильная навигация"
|
aria-label="Мобильная навигация"
|
||||||
data-testid="bottom-nav"
|
data-testid="bottom-nav"
|
||||||
className="fixed bottom-0 inset-x-0 bg-krona-surface text-krona-secondary border-t border-krona-border flex items-center justify-around z-40 px-1 pt-1 pb-[max(0.5rem,env(safe-area-inset-bottom))] shadow-lg backdrop-blur-md"
|
className="fixed bottom-0 inset-x-0 bg-krona-surface text-krona-secondary border-t border-krona-border flex items-center justify-around z-40 px-1 pt-0.5 pb-[max(0.25rem,env(safe-area-inset-bottom))] shadow-lg backdrop-blur-md"
|
||||||
>
|
>
|
||||||
{/* 1. Главная (KPI Сводка) */}
|
{/* 1. Главная (KPI Сводка) */}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/?tab=dashboard')}
|
onClick={() => navigate('/?tab=dashboard')}
|
||||||
className={`flex-1 min-h-[48px] min-w-[44px] flex flex-col items-center justify-center gap-1 p-1 rounded-xl transition-colors select-none ${
|
className={`flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-0.5 p-0.5 rounded-lg transition-colors select-none ${
|
||||||
isDashboardActive
|
isDashboardActive
|
||||||
? 'text-krona-gold font-bold bg-krona-gold/10'
|
? 'text-krona-gold font-bold bg-krona-gold/10'
|
||||||
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
||||||
|
|
@ -41,7 +41,7 @@ export const BottomNav: React.FC = () => {
|
||||||
{/* 2. Модули (Единый каталог) */}
|
{/* 2. Модули (Единый каталог) */}
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/')}
|
onClick={() => navigate('/')}
|
||||||
className={`flex-1 min-h-[48px] min-w-[44px] flex flex-col items-center justify-center gap-1 p-1 rounded-xl transition-colors select-none ${
|
className={`flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-0.5 p-0.5 rounded-lg transition-colors select-none ${
|
||||||
isCatalogActive
|
isCatalogActive
|
||||||
? 'text-krona-gold font-bold bg-krona-gold/10'
|
? 'text-krona-gold font-bold bg-krona-gold/10'
|
||||||
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
||||||
|
|
@ -56,7 +56,7 @@ export const BottomNav: React.FC = () => {
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/tasks"
|
to="/tasks"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex-1 min-h-[48px] min-w-[44px] flex flex-col items-center justify-center gap-1 p-1 rounded-xl transition-colors select-none ${
|
`flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-0.5 p-0.5 rounded-lg transition-colors select-none ${
|
||||||
isActive
|
isActive
|
||||||
? 'text-krona-gold font-bold bg-krona-gold/10'
|
? 'text-krona-gold font-bold bg-krona-gold/10'
|
||||||
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
||||||
|
|
@ -72,7 +72,7 @@ export const BottomNav: React.FC = () => {
|
||||||
<NavLink
|
<NavLink
|
||||||
to="/communications"
|
to="/communications"
|
||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`flex-1 min-h-[48px] min-w-[44px] flex flex-col items-center justify-center gap-1 p-1 rounded-xl transition-colors select-none ${
|
`flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-0.5 p-0.5 rounded-lg transition-colors select-none ${
|
||||||
isActive
|
isActive
|
||||||
? 'text-krona-gold font-bold bg-krona-gold/10'
|
? 'text-krona-gold font-bold bg-krona-gold/10'
|
||||||
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
||||||
|
|
@ -87,7 +87,7 @@ export const BottomNav: React.FC = () => {
|
||||||
{/* 5. Ещё */}
|
{/* 5. Ещё */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsDrawerOpen(true)}
|
onClick={() => setIsDrawerOpen(true)}
|
||||||
className={`flex-1 min-h-[48px] min-w-[44px] flex flex-col items-center justify-center gap-1 p-1 rounded-xl transition-colors select-none ${
|
className={`flex-1 min-h-[44px] min-w-[44px] flex flex-col items-center justify-center gap-0.5 p-0.5 rounded-lg transition-colors select-none ${
|
||||||
isDrawerOpen
|
isDrawerOpen
|
||||||
? 'text-krona-gold font-bold bg-krona-gold/10'
|
? 'text-krona-gold font-bold bg-krona-gold/10'
|
||||||
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
: 'text-krona-secondary hover:text-krona-primary active:bg-krona-workspace'
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ export const Topbar: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-30 bg-krona-surface/95 backdrop-blur-md border-b border-krona-border text-krona-primary shadow-xs">
|
<header className="sticky top-0 z-30 bg-krona-surface/95 backdrop-blur-md border-b border-krona-border text-krona-primary shadow-xs">
|
||||||
{/* Mobile Topbar (< 768px): Minimal, clean, zero overflow */}
|
{/* Mobile Topbar (< 768px): Minimal, clean, zero overflow */}
|
||||||
<div className="flex md:hidden h-14 px-3 items-center justify-between gap-2">
|
<div className="flex md:hidden h-12 px-2.5 items-center justify-between gap-2">
|
||||||
{/* Left: Logo & Current Section Title */}
|
{/* Left: Logo & Current Section Title */}
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<div
|
<div
|
||||||
|
|
@ -99,7 +99,7 @@ export const Topbar: React.FC = () => {
|
||||||
{/* AI Trigger */}
|
{/* AI Trigger */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsAiDrawerOpen(true)}
|
onClick={() => setIsAiDrawerOpen(true)}
|
||||||
className="w-11 h-11 min-w-[44px] min-h-[44px] rounded-xl bg-krona-workspace text-krona-gold hover:text-krona-gold-text border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
className="w-10 h-10 min-w-10 min-h-10 rounded-xl bg-krona-workspace text-krona-gold hover:text-krona-gold-text border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
||||||
title="Krona AI"
|
title="Krona AI"
|
||||||
aria-label="Krona AI"
|
aria-label="Krona AI"
|
||||||
>
|
>
|
||||||
|
|
@ -109,7 +109,7 @@ export const Topbar: React.FC = () => {
|
||||||
{/* Notifications Trigger */}
|
{/* Notifications Trigger */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsNotificationsOpen(true)}
|
onClick={() => setIsNotificationsOpen(true)}
|
||||||
className="relative w-11 h-11 min-w-[44px] min-h-[44px] rounded-xl bg-krona-workspace text-krona-secondary hover:text-krona-primary border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
className="relative w-10 h-10 min-w-10 min-h-10 rounded-xl bg-krona-workspace text-krona-secondary hover:text-krona-primary border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
||||||
title="Уведомления"
|
title="Уведомления"
|
||||||
aria-label="Уведомления"
|
aria-label="Уведомления"
|
||||||
data-testid="mobile-notifications-btn"
|
data-testid="mobile-notifications-btn"
|
||||||
|
|
@ -123,7 +123,7 @@ export const Topbar: React.FC = () => {
|
||||||
{/* Profile / Menu Drawer Trigger */}
|
{/* Profile / Menu Drawer Trigger */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsMobileDrawerOpen(true)}
|
onClick={() => setIsMobileDrawerOpen(true)}
|
||||||
className="w-11 h-11 min-w-[44px] min-h-[44px] rounded-xl bg-krona-workspace text-krona-primary border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
className="w-10 h-10 min-w-10 min-h-10 rounded-xl bg-krona-workspace text-krona-primary border border-krona-border flex items-center justify-center active:scale-95 transition-all"
|
||||||
title="Меню управления"
|
title="Меню управления"
|
||||||
aria-label="Открыть меню управления"
|
aria-label="Открыть меню управления"
|
||||||
data-testid="mobile-menu-btn"
|
data-testid="mobile-menu-btn"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
|
|
||||||
export function CommunicationsModule() {
|
export function CommunicationsModule() {
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const { activeTab, setActiveTab } = useCommunicationsStore();
|
const { activeTab, setActiveTab, isMobileChatOpen } = useCommunicationsStore();
|
||||||
|
|
||||||
const [isComposeEmailOpen, setIsComposeEmailOpen] = useState(false);
|
const [isComposeEmailOpen, setIsComposeEmailOpen] = useState(false);
|
||||||
const [isCreateChannelOpen, setIsCreateChannelOpen] = useState(false);
|
const [isCreateChannelOpen, setIsCreateChannelOpen] = useState(false);
|
||||||
|
|
@ -38,13 +38,15 @@ export function CommunicationsModule() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full min-h-0 flex flex-col bg-[var(--krona-bg-workspace)] overflow-hidden">
|
<div className="h-full min-h-0 flex flex-col bg-[var(--krona-bg-workspace)] overflow-hidden">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<CommunicationsHeader
|
<div className={currentTab === 'messengers' && isMobileChatOpen ? 'hidden sm:block' : ''}>
|
||||||
activeTab={currentTab}
|
<CommunicationsHeader
|
||||||
onTabChange={handleTabChange}
|
activeTab={currentTab}
|
||||||
unreadChats={48}
|
onTabChange={handleTabChange}
|
||||||
unreadEmails={10}
|
unreadChats={48}
|
||||||
unreadChannels={4}
|
unreadEmails={10}
|
||||||
/>
|
unreadChannels={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Body Views */}
|
{/* Body Views */}
|
||||||
<main className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
<main className="flex-1 min-h-0 flex flex-col overflow-hidden">
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import React, { useState, useRef, useEffect, type FormEvent } from 'react';
|
||||||
import {
|
import {
|
||||||
Search, ArrowUpDown, Star, Phone, Video, UserPlus,
|
Search, ArrowUpDown, Star, Phone, Video, UserPlus,
|
||||||
MoreVertical, Paperclip, Sparkles, Smile, Mic, Send, Download, FileText,
|
MoreVertical, Paperclip, Sparkles, Smile, Mic, Send, Download, FileText,
|
||||||
ArrowLeft, ChevronRight, CheckCheck, Plus,
|
ArrowLeft, CheckCheck, Plus,
|
||||||
Briefcase, CheckSquare, ShieldCheck
|
Briefcase, CheckSquare, ShieldCheck
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useCommunicationsStore } from '../services/communicationsService';
|
import { useCommunicationsStore } from '../services/communicationsService';
|
||||||
|
|
@ -171,11 +171,15 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
// Dialogs list section
|
// Dialogs list section
|
||||||
const dialogListSection = (
|
const dialogListSection = (
|
||||||
<section
|
<section
|
||||||
className="w-full h-full bg-[var(--krona-surface-raised)] rounded-2xl border border-[var(--krona-border-subtle)] shadow-[var(--krona-shadow-soft)] flex flex-col min-h-0 overflow-hidden"
|
className={`w-full h-full bg-[var(--krona-surface-raised)] flex flex-col min-h-0 overflow-hidden ${
|
||||||
|
layoutMode === 'compact'
|
||||||
|
? 'rounded-none border-0 shadow-none'
|
||||||
|
: 'rounded-2xl border border-[var(--krona-border-subtle)] shadow-[var(--krona-shadow-soft)]'
|
||||||
|
}`}
|
||||||
aria-label="Список диалогов"
|
aria-label="Список диалогов"
|
||||||
>
|
>
|
||||||
{/* Search and filters header */}
|
{/* Search and filters header */}
|
||||||
<div className="p-3.5 border-b border-[var(--krona-border-subtle)] space-y-2.5 shrink-0">
|
<div className={`${layoutMode === 'compact' ? 'p-2 space-y-1.5' : 'p-3.5 space-y-2.5'} border-b border-[var(--krona-border-subtle)] shrink-0`}>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-[var(--krona-text-muted)]" />
|
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-[var(--krona-text-muted)]" />
|
||||||
<input
|
<input
|
||||||
|
|
@ -255,7 +259,9 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
setIsMobileChatOpen(true);
|
setIsMobileChatOpen(true);
|
||||||
setIsContactDrawerOpen(false);
|
setIsContactDrawerOpen(false);
|
||||||
}}
|
}}
|
||||||
className={`w-full p-3.5 text-left flex items-start gap-3 transition-colors border-l-4 min-h-[76px] ${
|
className={`w-full text-left flex items-start transition-colors border-l-4 ${
|
||||||
|
layoutMode === 'compact' ? 'px-3 py-2.5 gap-2.5 min-h-[64px]' : 'p-3.5 gap-3 min-h-[76px]'
|
||||||
|
} ${
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[var(--krona-surface-selected)] border-l-[var(--krona-primary)]'
|
? 'bg-[var(--krona-surface-selected)] border-l-[var(--krona-primary)]'
|
||||||
: 'border-l-transparent hover:bg-[var(--krona-surface-hover)]'
|
: 'border-l-transparent hover:bg-[var(--krona-surface-hover)]'
|
||||||
|
|
@ -316,17 +322,21 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
// Active chat section
|
// Active chat section
|
||||||
const activeChatSection = (
|
const activeChatSection = (
|
||||||
<section
|
<section
|
||||||
className="w-full h-full bg-[var(--krona-surface-raised)] rounded-2xl border border-[var(--krona-border-subtle)] shadow-[var(--krona-shadow-soft)] flex flex-col min-h-0 overflow-hidden"
|
className={`w-full h-full bg-[var(--krona-surface-raised)] flex flex-col min-h-0 overflow-hidden ${
|
||||||
|
layoutMode === 'compact'
|
||||||
|
? 'rounded-none border-0 shadow-none'
|
||||||
|
: 'rounded-2xl border border-[var(--krona-border-subtle)] shadow-[var(--krona-shadow-soft)]'
|
||||||
|
}`}
|
||||||
aria-label="Активный диалог"
|
aria-label="Активный диалог"
|
||||||
>
|
>
|
||||||
{/* Chat Header */}
|
{/* Chat Header */}
|
||||||
<div className="h-16 px-4 bg-[var(--krona-surface)] border-b border-[var(--krona-border-subtle)] flex items-center justify-between gap-3 shrink-0">
|
<div className={`${layoutMode === 'compact' ? 'h-12 px-2' : 'h-16 px-4'} bg-[var(--krona-surface)] border-b border-[var(--krona-border-subtle)] flex items-center justify-between gap-2 shrink-0`}>
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
{layoutMode === 'compact' && (
|
{layoutMode === 'compact' && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsMobileChatOpen(false)}
|
onClick={() => setIsMobileChatOpen(false)}
|
||||||
className="w-9 h-9 -ml-1 rounded-lg flex items-center justify-center text-[var(--krona-text-secondary)] hover:bg-[var(--krona-surface-hover)]"
|
className="w-9 h-9 -ml-1 rounded-full flex items-center justify-center text-[var(--krona-text-secondary)] hover:bg-[var(--krona-surface-hover)]"
|
||||||
aria-label="Назад к списку чатов"
|
aria-label="Назад к списку чатов"
|
||||||
>
|
>
|
||||||
<ArrowLeft className="w-5 h-5" />
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
|
@ -379,7 +389,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
type: 'info',
|
type: 'info',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] flex items-center justify-center transition-colors"
|
className="hidden sm:flex w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] items-center justify-center transition-colors"
|
||||||
title="Аудиозвонок"
|
title="Аудиозвонок"
|
||||||
aria-label="Аудиозвонок"
|
aria-label="Аудиозвонок"
|
||||||
>
|
>
|
||||||
|
|
@ -394,7 +404,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
type: 'info',
|
type: 'info',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
className="w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] flex items-center justify-center transition-colors"
|
className="hidden sm:flex w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] items-center justify-center transition-colors"
|
||||||
title="Видеозвонок"
|
title="Видеозвонок"
|
||||||
aria-label="Видеозвонок"
|
aria-label="Видеозвонок"
|
||||||
>
|
>
|
||||||
|
|
@ -414,6 +424,18 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{layoutMode === 'compact' && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setIsContactDrawerOpen(true)}
|
||||||
|
className="w-9 h-9 rounded-full flex items-center justify-center text-[var(--krona-text-secondary)] hover:bg-[var(--krona-surface-hover)]"
|
||||||
|
title="Карточка контакта"
|
||||||
|
aria-label="Открыть карточку контакта"
|
||||||
|
>
|
||||||
|
<UserPlus className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] flex items-center justify-center transition-colors"
|
className="w-9 h-9 rounded-lg hover:bg-[var(--krona-surface-hover)] flex items-center justify-center transition-colors"
|
||||||
|
|
@ -428,7 +450,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
{/* Messages Feed */}
|
{/* Messages Feed */}
|
||||||
<div
|
<div
|
||||||
ref={messagesContainerRef}
|
ref={messagesContainerRef}
|
||||||
className="flex-1 min-h-0 overflow-y-auto p-4 space-y-3 bg-[var(--krona-bg-workspace)]/40"
|
className={`flex-1 min-h-0 overflow-y-auto bg-[var(--krona-bg-workspace)]/40 ${layoutMode === 'compact' ? 'px-2.5 py-2 space-y-2' : 'p-4 space-y-3'}`}
|
||||||
>
|
>
|
||||||
<div className="text-center my-2">
|
<div className="text-center my-2">
|
||||||
<span className="px-3 py-1 rounded-full text-[11px] font-medium bg-[var(--krona-surface)] text-[var(--krona-text-muted)] border border-[var(--krona-border-subtle)] shadow-2xs">
|
<span className="px-3 py-1 rounded-full text-[11px] font-medium bg-[var(--krona-surface)] text-[var(--krona-text-muted)] border border-[var(--krona-border-subtle)] shadow-2xs">
|
||||||
|
|
@ -439,7 +461,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
{activeChat.messages.map((msg) => (
|
{activeChat.messages.map((msg) => (
|
||||||
<div
|
<div
|
||||||
key={msg.id}
|
key={msg.id}
|
||||||
className={`flex gap-3 ${
|
className={`flex ${layoutMode === 'compact' ? 'gap-2' : 'gap-3'} ${
|
||||||
msg.direction === 'outgoing' ? 'justify-end' : 'justify-start'
|
msg.direction === 'outgoing' ? 'justify-end' : 'justify-start'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -455,7 +477,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`max-w-[85%] sm:max-w-[75%] rounded-2xl p-3.5 shadow-xs text-sm transition-colors ${
|
className={`max-w-[88%] sm:max-w-[75%] rounded-2xl shadow-xs text-sm transition-colors ${layoutMode === 'compact' ? 'px-3 py-2' : 'p-3.5'} ${
|
||||||
msg.direction === 'outgoing'
|
msg.direction === 'outgoing'
|
||||||
? 'bg-[var(--krona-primary-soft)] border border-[var(--krona-border-subtle)] text-[var(--krona-text-primary)] rounded-tr-xs'
|
? 'bg-[var(--krona-primary-soft)] border border-[var(--krona-border-subtle)] text-[var(--krona-text-primary)] rounded-tr-xs'
|
||||||
: 'bg-[var(--krona-surface)] border border-[var(--krona-border-subtle)] text-[var(--krona-text-primary)] rounded-tl-xs'
|
: 'bg-[var(--krona-surface)] border border-[var(--krona-border-subtle)] text-[var(--krona-text-primary)] rounded-tl-xs'
|
||||||
|
|
@ -515,7 +537,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Quick Replies Bar */}
|
{/* Quick Replies Bar */}
|
||||||
<div className="px-4 py-2 bg-[var(--krona-surface)]/60 border-t border-[var(--krona-border-subtle)] flex items-center gap-2 overflow-x-auto scrollbar-none shrink-0">
|
{layoutMode !== 'compact' && <div className="px-4 py-2 bg-[var(--krona-surface)]/60 border-t border-[var(--krona-border-subtle)] flex items-center gap-2 overflow-x-auto scrollbar-none shrink-0">
|
||||||
<span className="text-[11px] text-[var(--krona-text-muted)] font-medium shrink-0 flex items-center gap-1">
|
<span className="text-[11px] text-[var(--krona-text-muted)] font-medium shrink-0 flex items-center gap-1">
|
||||||
<Sparkles className="w-3 h-3 text-[var(--krona-gold)]" /> Быстрые:
|
<Sparkles className="w-3 h-3 text-[var(--krona-gold)]" /> Быстрые:
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -529,12 +551,12 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
{qrText}
|
{qrText}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>}
|
||||||
|
|
||||||
{/* Message Composer */}
|
{/* Message Composer */}
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSend}
|
onSubmit={handleSend}
|
||||||
className="p-3 sm:p-4 bg-[var(--krona-surface)] border-t border-[var(--krona-border-subtle)] shrink-0"
|
className="p-1.5 sm:p-4 bg-[var(--krona-surface)] border-t border-[var(--krona-border-subtle)] shrink-0"
|
||||||
>
|
>
|
||||||
<div className="flex items-end gap-2 bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] rounded-xl p-1.5 focus-within:border-[var(--krona-primary)] transition-colors">
|
<div className="flex items-end gap-2 bg-[var(--krona-bg-workspace)] border border-[var(--krona-border)] rounded-xl p-1.5 focus-within:border-[var(--krona-primary)] transition-colors">
|
||||||
<button
|
<button
|
||||||
|
|
@ -613,35 +635,6 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{/* Mobile Bottom Contact Teaser Bar (Level 2 -> Level 3 drilldown) */}
|
|
||||||
{layoutMode === 'compact' && (
|
|
||||||
<div className="p-3 bg-[var(--krona-surface)] border-t border-[var(--krona-border-subtle)] flex items-center justify-between gap-3 shrink-0">
|
|
||||||
<div className="flex items-center gap-2.5 min-w-0">
|
|
||||||
<Avatar
|
|
||||||
src={activeChat.contact.avatarUrl}
|
|
||||||
alt={activeChat.contact.name}
|
|
||||||
initials={activeChat.contact.initials}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<div className="min-w-0">
|
|
||||||
<span className="font-semibold text-xs text-[var(--krona-text-primary)] truncate block">
|
|
||||||
{activeChat.contact.name}
|
|
||||||
</span>
|
|
||||||
<span className="text-[11px] text-[var(--krona-text-secondary)] truncate block">
|
|
||||||
{activeChat.contact.company}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setIsContactDrawerOpen(true)}
|
|
||||||
className="p-1.5 rounded-lg text-[var(--krona-primary)] hover:bg-[var(--krona-surface-hover)]"
|
|
||||||
aria-label="Открыть карточку контакта"
|
|
||||||
>
|
|
||||||
<ChevronRight className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -652,7 +645,7 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
className="flex-1 min-h-0 flex flex-col bg-[var(--krona-bg-workspace)] overflow-hidden"
|
className="flex-1 min-h-0 flex flex-col bg-[var(--krona-bg-workspace)] overflow-hidden"
|
||||||
>
|
>
|
||||||
{/* 1. Channel Switcher Row (# 17. Channel Switcher) */}
|
{/* 1. Channel Switcher Row (# 17. Channel Switcher) */}
|
||||||
<div className="bg-[var(--krona-surface)]/80 backdrop-blur-xs border-b border-[var(--krona-border-subtle)] px-3 sm:px-6 py-2 flex items-center justify-between gap-3 min-w-0 shrink-0">
|
{!(layoutMode === 'compact' && isMobileChatOpen) && <div className="bg-[var(--krona-surface)]/80 backdrop-blur-xs border-b border-[var(--krona-border-subtle)] px-2 sm:px-6 py-1.5 sm:py-2 flex items-center justify-between gap-2 sm:gap-3 min-w-0 shrink-0">
|
||||||
<div className="relative flex-1 min-w-0 flex items-center overflow-x-auto scrollbar-none py-0.5">
|
<div className="relative flex-1 min-w-0 flex items-center overflow-x-auto scrollbar-none py-0.5">
|
||||||
<div className="flex items-center gap-2 shrink-0 pr-6">
|
<div className="flex items-center gap-2 shrink-0 pr-6">
|
||||||
<ConnectorPill
|
<ConnectorPill
|
||||||
|
|
@ -722,10 +715,10 @@ export function MessengersView({ onOpenConnectModal, onOpenAddContactModal }: Me
|
||||||
<Plus className="w-4 h-4 text-[var(--krona-primary)]" />
|
<Plus className="w-4 h-4 text-[var(--krona-primary)]" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
|
|
||||||
{/* 2. Main Workspace Canvas */}
|
{/* 2. Main Workspace Canvas */}
|
||||||
<div className="flex-1 min-h-0 p-3 sm:p-4 lg:p-5 flex gap-4 overflow-hidden">
|
<div className={`flex-1 min-h-0 flex gap-4 overflow-hidden ${layoutMode === 'compact' ? 'p-0' : 'p-3 sm:p-4 lg:p-5'}`}>
|
||||||
{/* COMPACT MODE (<600px): strictly 1 level at a time */}
|
{/* COMPACT MODE (<600px): strictly 1 level at a time */}
|
||||||
{layoutMode === 'compact' && (
|
{layoutMode === 'compact' && (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue