fix(shell): compact communications header and theme sidebar

This commit is contained in:
ochenstarik 2026-09-12 12:27:19 +07:00
parent 42929d7284
commit f2ac97a841
17 changed files with 51 additions and 106 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View file

@ -1,5 +1,5 @@
{
"generatedAt": "2026-09-11T12:29:57.828Z",
"generatedAt": "2026-09-12T05:25:45.504Z",
"module": "communications",
"version": "v3.0",
"referenceMockups": [
@ -68,13 +68,13 @@
"filename": "desktop-with-krona-ai.png",
"theme": "state-capture",
"viewport": "desktop-with-krona-ai",
"capturedAt": "2026-09-11T12:29:50.764Z"
"capturedAt": "2026-09-12T05:25:45.132Z"
},
{
"filename": "desktop-without-krona-ai.png",
"theme": "state-capture",
"viewport": "desktop-without-krona-ai",
"capturedAt": "2026-09-11T12:29:49.887Z"
"capturedAt": "2026-09-12T05:25:44.475Z"
},
{
"filename": "graphite-forest-dark-desktop-1280.png",
@ -86,7 +86,7 @@
"filename": "graphite-forest-dark-desktop-1440.png",
"theme": "graphite-forest-dark",
"viewport": "desktop-1440",
"capturedAt": "2026-09-11T12:22:07.332Z"
"capturedAt": "2026-09-12T05:25:42.065Z"
},
{
"filename": "graphite-forest-dark-desktop-1920.png",
@ -104,7 +104,7 @@
"filename": "graphite-forest-dark-mobile-390.png",
"theme": "graphite-forest-dark",
"viewport": "mobile-390",
"capturedAt": "2026-09-11T12:21:02.940Z"
"capturedAt": "2026-09-12T05:25:33.933Z"
},
{
"filename": "graphite-forest-dark-mobile-430.png",
@ -128,19 +128,19 @@
"filename": "mobile-chat.png",
"theme": "state-capture",
"viewport": "mobile-chat",
"capturedAt": "2026-09-11T12:29:13.147Z"
"capturedAt": "2026-09-12T05:25:36.209Z"
},
{
"filename": "mobile-contact.png",
"theme": "state-capture",
"viewport": "mobile-contact",
"capturedAt": "2026-09-11T12:29:13.337Z"
"capturedAt": "2026-09-12T05:25:36.390Z"
},
{
"filename": "mobile-list.png",
"theme": "state-capture",
"viewport": "mobile-list",
"capturedAt": "2026-09-11T12:29:12.902Z"
"capturedAt": "2026-09-12T05:25:35.998Z"
},
{
"filename": "noble-green-desktop-1280.png",
@ -152,7 +152,7 @@
"filename": "noble-green-desktop-1440.png",
"theme": "noble-green",
"viewport": "desktop-1440",
"capturedAt": "2026-09-11T12:22:06.701Z"
"capturedAt": "2026-09-12T05:25:41.554Z"
},
{
"filename": "noble-green-desktop-1920.png",
@ -170,7 +170,7 @@
"filename": "noble-green-mobile-390.png",
"theme": "noble-green",
"viewport": "mobile-390",
"capturedAt": "2026-09-11T12:21:02.534Z"
"capturedAt": "2026-09-12T05:25:33.543Z"
},
{
"filename": "noble-green-mobile-430.png",
@ -200,7 +200,7 @@
"filename": "parchment-light-desktop-1440.png",
"theme": "parchment-light",
"viewport": "desktop-1440",
"capturedAt": "2026-09-11T12:22:06.120Z"
"capturedAt": "2026-09-12T05:25:41.001Z"
},
{
"filename": "parchment-light-desktop-1920.png",
@ -218,7 +218,7 @@
"filename": "parchment-light-mobile-390.png",
"theme": "parchment-light",
"viewport": "mobile-390",
"capturedAt": "2026-09-11T12:21:02.124Z"
"capturedAt": "2026-09-12T05:25:33.154Z"
},
{
"filename": "parchment-light-mobile-430.png",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View file

@ -43,23 +43,16 @@ test.describe('Scenario 13: Communications Visual & Responsive Design System V3
}
});
test('2. Header, Slogan Watermark, and Channel Switcher validation', async ({ page }) => {
test('2. Compact header and channel switcher validation', async ({ page }) => {
const vp = page.viewportSize();
const isMobile = (vp?.width ?? 1200) < 640;
const isDesktop = (vp?.width ?? 1200) >= 1024;
// Check main header
await expect(page.locator('h1', { hasText: 'Коммуникации' })).toBeVisible();
if (!isMobile) {
await expect(
page.locator('text=Все сообщения, почта и каналы компании в одном пространстве')
).toBeVisible();
}
if (isDesktop) {
await expect(page.locator('text=Больше, чем общение с поддержкой')).toBeVisible();
}
await expect(page.getByText('Все сообщения, почта и каналы компании в одном пространстве')).toHaveCount(0);
await expect(page.getByText('Больше, чем общение с поддержкой')).toHaveCount(0);
await expect(page.getByRole('button', { name: /добавить канал/i })).toHaveCount(0);
// Check all 5 tabs
await expect(page.getByRole('tab', { name: /мессенджеры/i })).toBeVisible();

View file

@ -66,12 +66,12 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
return (
<aside
data-testid="sidebar"
className={`relative h-screen bg-[#252722] text-[#D8D8D0] hidden md:flex flex-col border-r border-[#3A3D35] transition-all duration-200 z-20 select-none ${
className={`relative h-screen bg-[var(--sidebar-bg)] text-[var(--sidebar-text)] hidden md:flex flex-col border-r border-[var(--sidebar-border)] transition-[width,background-color,color,border-color] duration-300 z-20 select-none ${
collapsed ? 'w-16' : 'w-56'
}`}
>
{/* Brand Header */}
<div className="h-14 px-3.5 flex items-center justify-between border-b border-[#3A3D35] bg-[#20221E]">
<div className="h-14 px-3.5 flex items-center justify-between border-b border-[var(--sidebar-border)] bg-[var(--sidebar-deep)] transition-colors duration-300">
<div className="flex items-center gap-2.5 overflow-hidden">
{/* Logo asset from public/branding/krona-symbol.png */}
<img
@ -81,10 +81,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
/>
{!collapsed && (
<div className="flex flex-col min-w-0 leading-tight select-none">
<span className="text-sm font-bold tracking-wider text-[#E5B842] uppercase truncate">
<span className="text-sm font-bold tracking-wider text-[var(--krona-gold)] uppercase truncate">
Крона
</span>
<span className="text-[9px] uppercase tracking-widest text-[#B78335] font-semibold">
<span className="text-[9px] uppercase tracking-widest text-[var(--krona-gold)] font-semibold">
Бизнес-платформа
</span>
</div>
@ -93,7 +93,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
<button
onClick={() => setUserCollapsed(!collapsed)}
className="p-1 rounded text-[#858980] hover:text-[#D8D8D0] hover:bg-[#30332C] transition-colors"
className="p-1 rounded text-[var(--sidebar-text-muted)] hover:text-[var(--sidebar-text)] hover:bg-[var(--sidebar-hover)] transition-colors"
title={collapsed ? 'Развернуть меню' : 'Свернуть меню'}
>
{collapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
@ -110,8 +110,8 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
className={({ isActive }) =>
`group relative flex items-center gap-2.5 px-2.5 py-2 rounded-md text-xs font-medium transition-all ${
isActive
? 'bg-[#34382F] text-[#F5F0E5] font-semibold'
: 'text-[#BFC1B9] hover:text-[#F5F0E5] hover:bg-[#30332C]'
? 'bg-[var(--sidebar-active)] text-[var(--sidebar-text-active)] font-semibold'
: 'text-[var(--sidebar-text-secondary)] hover:text-[var(--sidebar-text)] hover:bg-[var(--sidebar-hover)]'
}`
}
title={collapsed ? item.label : undefined}
@ -120,10 +120,10 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
<>
{/* Active indicator bar */}
{isActive && (
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[#B78335] rounded-r" />
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[var(--sidebar-indicator)] rounded-r" />
)}
<div className={`shrink-0 ${isActive ? 'text-[#D4B16D]' : 'text-[#BFC1B9]'}`}>
<div className={`shrink-0 ${isActive ? 'text-[var(--sidebar-icon-active)]' : 'text-[var(--sidebar-icon)]'}`}>
{ICON_MAP[item.iconName] || <LayoutDashboard className="w-4 h-4 shrink-0" />}
</div>
@ -131,7 +131,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
<div className="flex-1 flex items-center justify-between truncate">
<span className="truncate">{item.label}</span>
{item.badge && (
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-[#3A3D35] text-[#D8D8D0] font-mono">
<span className="text-[10px] px-1.5 py-0.5 rounded-full bg-[var(--sidebar-surface)] text-[var(--sidebar-text)] font-mono">
{item.badge}
</span>
)}
@ -144,9 +144,9 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
{/* Custom Entities Section */}
{customEntities.length > 0 && (
<div className="pt-2 mt-2 border-t border-[#3A3D35]/80">
<div className="pt-2 mt-2 border-t border-[var(--sidebar-border)]">
{!collapsed && (
<div className="px-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-[#858980]">
<div className="px-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-[var(--sidebar-text-muted)]">
Пользовательские модули
</div>
)}
@ -157,8 +157,8 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
className={({ isActive }) =>
`group relative flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${
isActive
? 'bg-[#34382F] text-[#F5F0E5] font-semibold'
: 'text-[#BFC1B9] hover:text-[#F5F0E5] hover:bg-[#30332C]'
? 'bg-[var(--sidebar-active)] text-[var(--sidebar-text-active)] font-semibold'
: 'text-[var(--sidebar-text-secondary)] hover:text-[var(--sidebar-text)] hover:bg-[var(--sidebar-hover)]'
}`
}
title={collapsed ? ent.name : undefined}
@ -166,7 +166,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
{({ isActive }) => (
<>
{isActive && (
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[#B78335] rounded-r" />
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[var(--sidebar-indicator)] rounded-r" />
)}
<div className="shrink-0 text-krona-gold">
<Sparkles className="w-3.5 h-3.5" />
@ -188,8 +188,8 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
className={({ isActive }) =>
`group relative flex items-center gap-2.5 px-2.5 py-1.5 rounded-md text-xs font-medium transition-all ${
isActive
? 'bg-[#34382F] text-[#F5F0E5] font-semibold'
: 'text-[#BFC1B9] hover:text-[#F5F0E5] hover:bg-[#30332C]'
? 'bg-[var(--sidebar-active)] text-[var(--sidebar-text-active)] font-semibold'
: 'text-[var(--sidebar-text-secondary)] hover:text-[var(--sidebar-text)] hover:bg-[var(--sidebar-hover)]'
}`
}
title={collapsed ? 'Конструктор сущностей' : undefined}
@ -197,7 +197,7 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
{({ isActive }) => (
<>
{isActive && (
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[#B78335] rounded-r" />
<div className="absolute left-0 top-1.5 bottom-1.5 w-1 bg-[var(--sidebar-indicator)] rounded-r" />
)}
<div className="shrink-0 text-krona-green">
<Workflow className="w-3.5 h-3.5" />
@ -212,14 +212,14 @@ export const Sidebar: React.FC<SidebarProps> = ({ isTabletCompact = false }) =>
</div>
{/* Footer / Role indicator */}
<div className="p-2.5 border-t border-[#3A3D35] bg-[#20221E]/60 text-xs">
<div className="p-2.5 border-t border-[var(--sidebar-border)] bg-[var(--sidebar-deep)] text-xs transition-colors duration-300">
{!collapsed ? (
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-krona-green animate-pulse" />
<div className="truncate">
<div className="text-[11px] font-semibold text-[#F5F0E5] truncate">{role.label}</div>
<div className="text-[9px] text-[#858980]">RBAC: {role.badge}</div>
<div className="text-[11px] font-semibold text-[var(--sidebar-text)] truncate">{role.label}</div>
<div className="text-[9px] text-[var(--sidebar-text-muted)]">RBAC: {role.badge}</div>
</div>
</div>
</div>

View file

@ -54,6 +54,7 @@ export const Topbar: React.FC = () => {
if (m.route === '/') return currentPath === '/';
return currentPath.startsWith(m.route);
}) || SYSTEM_MODULES[0];
const isCommunicationsModule = currentModule.id === 'communications';
// For operations, subtabs are dynamic based on active profile & capabilities
const getSubtabs = () => {
@ -351,8 +352,16 @@ export const Topbar: React.FC = () => {
</div>
</div>
{/* Communications owns its local tabs inside the module. Keep only the
module title here so the shell does not render a duplicate menu. */}
{isCommunicationsModule && (
<div className="hidden md:flex h-10 px-5 border-t border-krona-border/60 bg-krona-workspace/40 items-center shrink-0">
<span className="font-serif text-lg font-bold text-krona-primary">Коммуникации</span>
</div>
)}
{/* Subheader / Subtabs Navigation Bar (Desktop & Tablet) */}
{activeSubtabs.length > 0 && (
{!isCommunicationsModule && activeSubtabs.length > 0 && (
<div className="h-10 px-4 border-t border-krona-border/60 bg-krona-workspace/40 flex items-center overflow-x-auto gap-1 text-xs">
{activeSubtabs.map((tab) => {
const isActive =

View file

@ -41,7 +41,6 @@ export function CommunicationsModule() {
<CommunicationsHeader
activeTab={currentTab}
onTabChange={handleTabChange}
onAddChannel={() => setIsConnectChannelOpen(true)}
unreadChats={48}
unreadEmails={10}
unreadChannels={4}

View file

@ -1,12 +1,11 @@
import React from 'react';
import { MessageSquare, Mail, Hash, Users, Settings, Plus } from 'lucide-react';
import { MessageSquare, Mail, Hash, Users, Settings } from 'lucide-react';
import { CommunicationTab } from '../types';
import { Button, Tabs } from '@krona/ui';
import { Tabs } from '@krona/ui';
interface CommunicationsHeaderProps {
activeTab: CommunicationTab;
onTabChange: (tab: CommunicationTab) => void;
onAddChannel: () => void;
unreadChats: number;
unreadEmails: number;
unreadChannels: number;
@ -15,7 +14,6 @@ interface CommunicationsHeaderProps {
export function CommunicationsHeader({
activeTab,
onTabChange,
onAddChannel,
unreadChats,
unreadEmails,
unreadChannels,
@ -52,51 +50,8 @@ export function CommunicationsHeader({
];
return (
<header className="relative bg-[var(--krona-surface)] border-b border-[var(--krona-border-subtle)] px-3 sm:px-6 pt-3 sm:pt-5 pb-2 sm:pb-3 shrink-0 overflow-hidden">
<div className="hidden lg:flex items-center gap-3 absolute right-8 top-4 text-right pointer-events-none opacity-50 font-serif italic text-sm text-[var(--krona-text-secondary)]">
<span>Больше, чем общение с поддержкой</span>
<svg
className="w-8 h-8 text-[var(--krona-gold)] shrink-0"
viewBox="0 0 32 32"
fill="none"
stroke="currentColor"
strokeWidth="1.2"
aria-hidden="true"
>
<path d="M16 28C16 18 20 10 28 6" strokeLinecap="round" />
<path d="M16 24C12 20 8 18 4 17" strokeLinecap="round" />
<path d="M16 19C20 16 24 15 27 15" strokeLinecap="round" />
<path d="M16 14C12 11 9 10 6 10" strokeLinecap="round" />
<path d="M18 10C21 8 24 7 26 7" strokeLinecap="round" />
<circle cx="28" cy="6" r="1.5" fill="currentColor" />
<circle cx="4" cy="17" r="1.5" fill="currentColor" />
<circle cx="27" cy="15" r="1.5" fill="currentColor" />
<circle cx="6" cy="10" r="1.5" fill="currentColor" />
</svg>
</div>
<div className="flex items-center justify-between gap-3 mb-2 sm:mb-4">
<div>
<h1 className="text-xl sm:text-3xl font-bold font-serif text-[var(--krona-text-primary)]">
Коммуникации
</h1>
<p className="hidden sm:block text-sm text-[var(--krona-text-secondary)] mt-1">
Все сообщения, почта и каналы компании в одном пространстве
</p>
</div>
{/* Mobile compact action button */}
<button
type="button"
onClick={onAddChannel}
className="sm:hidden h-10 w-10 rounded-lg border border-[var(--krona-border)] bg-[var(--krona-surface)] hover:bg-[var(--krona-surface-hover)] text-[var(--krona-primary)] font-medium flex items-center justify-center shrink-0 transition-colors active:scale-95"
aria-label="Добавить канал"
>
<Plus className="w-4 h-4 text-[var(--krona-primary)]" />
<span className="sr-only">Добавить канал</span>
</button>
</div>
<header className="bg-[var(--krona-surface)] border-b border-[var(--krona-border-subtle)] px-3 sm:px-5 py-1.5 shrink-0 overflow-hidden">
<h1 className="sr-only">Коммуникации</h1>
<div className="flex items-center justify-between gap-3 min-w-0">
<div className="flex-1 min-w-0 overflow-x-auto scrollbar-none">
<Tabs
@ -107,17 +62,6 @@ export function CommunicationsHeader({
/>
</div>
{/* Desktop / Tablet full button */}
<div className="hidden sm:flex items-center gap-2 shrink-0">
<Button
variant="secondary"
size="sm"
onClick={onAddChannel}
leftIcon={<Plus className="w-4 h-4 text-[var(--krona-primary)]" />}
>
Добавить канал
</Button>
</div>
</div>
</header>
);