From 7dc18bb6e7ec9ccb3661950568edfd8f18505811 Mon Sep 17 00:00:00 2001 From: Haze <709547807@qq.com> Date: Tue, 12 May 2026 18:37:40 +0800 Subject: [PATCH] refact(front): optimize front style (#1007) --- .../channels/ChannelConfigModal.tsx | 12 +- .../file-preview/SkillFileSections.tsx | 2 +- src/components/layout/MainLayout.tsx | 24 ++- src/components/layout/Sidebar.tsx | 162 ++++++++++++------ src/components/layout/TitleBar.tsx | 9 +- src/components/settings/ProvidersSettings.tsx | 16 +- src/pages/Agents/index.tsx | 6 +- src/pages/Chat/index.tsx | 14 +- src/pages/Cron/index.tsx | 18 +- src/pages/Dreams/index.tsx | 52 +++--- src/pages/Models/index.tsx | 20 +-- src/pages/Skills/index.tsx | 2 +- src/stores/settings.ts | 9 + src/styles/globals.css | 56 ++++-- tailwind.config.js | 11 ++ tests/e2e/main-navigation.spec.ts | 4 + tests/unit/main-layout.test.tsx | 29 ++++ tests/unit/stores.test.ts | 14 ++ tests/unit/title-bar.test.tsx | 5 +- 19 files changed, 331 insertions(+), 134 deletions(-) create mode 100644 tests/unit/main-layout.test.tsx diff --git a/src/components/channels/ChannelConfigModal.tsx b/src/components/channels/ChannelConfigModal.tsx index d85b474..e435c22 100644 --- a/src/components/channels/ChannelConfigModal.tsx +++ b/src/components/channels/ChannelConfigModal.tsx @@ -62,7 +62,7 @@ interface ChannelConfigModalProps { onChannelSaved?: (channelType: ChannelType) => void | Promise; } -const inputClasses = 'h-[44px] rounded-xl font-mono text-meta bg-surface-input border-black/10 dark:border-white/10 focus-visible:ring-2 focus-visible:ring-blue-500/50 focus-visible:border-blue-500 shadow-sm transition-all text-foreground placeholder:text-foreground/40'; +const inputClasses = 'h-[44px] rounded-xl font-mono text-meta bg-transparent border-black/10 dark:border-white/10 focus-visible:ring-2 focus-visible:ring-blue-500/50 focus-visible:border-blue-500 shadow-sm transition-all text-foreground placeholder:text-foreground/40'; const labelClasses = 'text-sm text-foreground/80 font-bold'; const outlineButtonClasses = 'h-9 text-meta font-medium rounded-full px-4 border-black/10 dark:border-white/10 bg-transparent hover:bg-black/5 dark:hover:bg-white/5 shadow-none text-foreground/80 hover:text-foreground'; const primaryButtonClasses = 'h-9 text-meta font-medium rounded-full px-4 shadow-none'; @@ -539,7 +539,7 @@ export function ChannelConfigModal({ key={type} onClick={() => setSelectedType(type)} className={cn( - 'group flex items-start gap-4 p-4 rounded-2xl transition-all text-left border relative overflow-hidden bg-surface-input shadow-sm', + 'group flex items-start gap-4 p-4 rounded-2xl transition-all text-left border relative overflow-hidden bg-transparent shadow-sm', isConfigured ? 'border-green-500/40 bg-green-500/5 dark:bg-green-500/10' : 'border-black/5 dark:border-white/10 hover:bg-black/5 dark:hover:bg-white/5' @@ -578,7 +578,7 @@ export function ChannelConfigModal({ ) : qrCode ? (
-
+
{qrCode.startsWith('data:image') || qrCode.startsWith('http://') || qrCode.startsWith('https://') ? ( Scan QR Code ) : ( @@ -604,7 +604,7 @@ export function ChannelConfigModal({
) : loadingConfig ? ( -
+
{t('dialog.loadingConfig')}
@@ -617,7 +617,7 @@ export function ChannelConfigModal({
)} -
+

{t('dialog.howToConnect')}

@@ -851,7 +851,7 @@ function ConfigField({ field, value, onChange, showSecret, onToggleSecret }: Con variant="outline" size="icon" onClick={onToggleSecret} - className="h-[44px] w-[44px] rounded-xl bg-surface-input border-black/10 dark:border-white/10 text-muted-foreground hover:text-foreground shrink-0 shadow-sm" + className="h-[44px] w-[44px] rounded-xl bg-transparent border-black/10 dark:border-white/10 text-muted-foreground hover:text-foreground shrink-0 shadow-sm" > {showSecret ? : } diff --git a/src/components/file-preview/SkillFileSections.tsx b/src/components/file-preview/SkillFileSections.tsx index c47799e..0b10997 100644 --- a/src/components/file-preview/SkillFileSections.tsx +++ b/src/components/file-preview/SkillFileSections.tsx @@ -138,7 +138,7 @@ function SkillFileSection({ title, description, files, onOpen }: SkillFileSectio type="button" onClick={() => onOpen(file)} className={cn( - 'flex items-center gap-2 rounded-xl border border-black/5 bg-surface-input px-3 py-2 text-left transition-colors', + 'flex items-center gap-2 rounded-xl border border-black/5 bg-transparent px-3 py-2 text-left transition-colors', 'hover:border-primary/40 hover:bg-primary/5', 'dark:border-white/5 dark:hover:bg-white/10', )} diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx index 4f64bce..879a748 100644 --- a/src/components/layout/MainLayout.tsx +++ b/src/components/layout/MainLayout.tsx @@ -1,21 +1,33 @@ /** * Main Layout Component - * TitleBar at top, then sidebar + content below. + * Platform-aware application shell. */ import { Outlet } from 'react-router-dom'; import { Sidebar } from './Sidebar'; import { TitleBar } from './TitleBar'; +import { cn } from '@/lib/utils'; export function MainLayout() { + const platform = window.electron?.platform; + const isMac = platform === 'darwin'; + return ( -
- {/* Title bar: drag region on macOS, icon + controls on Windows */} +
- {/* Below the title bar: sidebar + content */} -
+
-
+
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 5f3697c..b01e426 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -3,7 +3,7 @@ * Navigation sidebar with menu items. * No longer fixed - sits inside the flex layout below the title bar. */ -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { NavLink, useLocation, useNavigate } from 'react-router-dom'; import { Network, @@ -52,7 +52,7 @@ function NavItem({ to, icon, label, badge, collapsed, onClick, testId }: NavItem data-testid={testId} className={({ isActive }) => cn( - 'flex items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-medium transition-colors', + 'sidebar-nav-text flex items-center gap-2 rounded-lg px-2.5 py-1.5 transition-colors', 'hover:bg-black/5 dark:hover:bg-white/5 text-foreground/80', isActive ? 'bg-black/5 dark:bg-white/10 text-foreground' @@ -61,23 +61,21 @@ function NavItem({ to, icon, label, badge, collapsed, onClick, testId }: NavItem ) } > - {({ isActive }) => ( - <> -
- {icon} -
- {!collapsed && ( - <> - {label} - {badge && ( - - {badge} - - )} - - )} - - )} + <> +
+ {icon} +
+ {!collapsed && ( + <> + {label} + {badge && ( + + {badge} + + )} + + )} + ); } @@ -91,9 +89,14 @@ function getAgentIdFromSessionKey(sessionKey: string): string { } export function Sidebar() { + const isMac = window.electron?.platform === 'darwin'; const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed); const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed); + const sidebarWidth = useSettingsStore((state) => state.sidebarWidth); + const setSidebarWidth = useSettingsStore((state) => state.setSidebarWidth); const devModeUnlocked = useSettingsStore((state) => state.devModeUnlocked); + const [isResizing, setIsResizing] = useState(false); + const stopResizeRef = useRef<(() => void) | null>(null); const sessions = useChatStore((s) => s.sessions); const currentSessionKey = useChatStore((s) => s.currentSessionKey); @@ -169,6 +172,45 @@ export function Sidebar() { void fetchAgents(); }, [fetchAgents]); + const stopResizing = useCallback(() => { + stopResizeRef.current?.(); + stopResizeRef.current = null; + setIsResizing(false); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }, []); + + const handleResizePointerDown = useCallback( + (event: React.PointerEvent) => { + if (sidebarCollapsed) return; + event.preventDefault(); + event.stopPropagation(); + try { + event.currentTarget.setPointerCapture(event.pointerId); + } catch { + // Window listeners below keep dragging reliable even if capture is unavailable. + } + + const onMove = (moveEvent: PointerEvent) => { + setSidebarWidth(moveEvent.clientX); + }; + const onUp = () => stopResizing(); + + stopResizeRef.current = () => { + window.removeEventListener('pointermove', onMove); + window.removeEventListener('pointerup', onUp); + }; + window.addEventListener('pointermove', onMove); + window.addEventListener('pointerup', onUp); + setIsResizing(true); + document.body.style.cursor = 'col-resize'; + document.body.style.userSelect = 'none'; + }, + [setSidebarWidth, sidebarCollapsed, stopResizing], + ); + + useEffect(() => stopResizing, [stopResizing]); + const agentNameById = useMemo( () => Object.fromEntries((agents ?? []).map((agent) => [agent.id, agent.name])), [agents], @@ -200,13 +242,13 @@ export function Sidebar() { const extraNavItems = rendererExtensionRegistry.getExtraNavItems(); const coreNavItems = [ - { to: '/models', icon: , label: t('sidebar.models'), testId: 'sidebar-nav-models' }, - { to: '/agents', icon: , label: t('sidebar.agents'), testId: 'sidebar-nav-agents' }, - { to: '/channels', icon: , label: t('sidebar.channels'), testId: 'sidebar-nav-channels' }, - { to: '/skills', icon: , label: t('sidebar.skills'), testId: 'sidebar-nav-skills' }, - { to: '/cron', icon: , label: t('sidebar.cronTasks'), testId: 'sidebar-nav-cron' }, + { to: '/models', icon: , label: t('sidebar.models'), testId: 'sidebar-nav-models' }, + { to: '/agents', icon: , label: t('sidebar.agents'), testId: 'sidebar-nav-agents' }, + { to: '/channels', icon: , label: t('sidebar.channels'), testId: 'sidebar-nav-channels' }, + { to: '/skills', icon: , label: t('sidebar.skills'), testId: 'sidebar-nav-skills' }, + { to: '/cron', icon: , label: t('sidebar.cronTasks'), testId: 'sidebar-nav-cron' }, ...(devModeUnlocked - ? [{ to: '/dreams', icon: , label: t('common:sidebar.openClawDreams'), testId: 'sidebar-nav-dreams' }] + ? [{ to: '/dreams', icon: , label: t('common:sidebar.openClawDreams'), testId: 'sidebar-nav-dreams' }] : []), ]; @@ -214,7 +256,7 @@ export function Sidebar() { ...coreNavItems.filter((item) => !hiddenRoutes.has(item.to)), ...extraNavItems.map((item) => ({ to: item.to, - icon: , + icon: , label: item.labelI18nKey ? t(item.labelI18nKey) : item.label, testId: item.testId, })), @@ -224,12 +266,19 @@ export function Sidebar() {