Align Code navigation and branding with workspace header rows
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
||||
FolderKanban,
|
||||
Gamepad2,
|
||||
LogOut,
|
||||
PanelLeftClose,
|
||||
Plus,
|
||||
RadioTower,
|
||||
Settings as SettingsIcon,
|
||||
@@ -91,7 +90,6 @@ export function Sidebar({
|
||||
}: SidebarProps = {}) {
|
||||
const { t } = useTranslation('common');
|
||||
const storedSidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||
const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth));
|
||||
const sidebarPinnedCollapsed = !forceExpanded && storedSidebarCollapsed;
|
||||
const sidebarCollapsed = sidebarPinnedCollapsed && !sidebarPeekOpen;
|
||||
@@ -415,17 +413,8 @@ export function Sidebar({
|
||||
)}
|
||||
>
|
||||
<div className="min-w-0 w-full">
|
||||
{workspaceLayout && <div className="no-drag flex h-10 min-w-0 items-center gap-1" data-testid="classroom-sidebar-navigation">
|
||||
{workspaceLayout && !['darwin', 'win32'].includes(window.electron?.platform ?? '') && <div className="no-drag flex h-10 min-w-0 items-center gap-1" data-testid="classroom-sidebar-navigation">
|
||||
<ModuleSwitcher sidebarCollapsed={sidebarCollapsed} toolbar iconOnly />
|
||||
{!sidebarPinnedCollapsed && ['darwin', 'win32'].includes(window.electron?.platform ?? '') && <button
|
||||
type="button"
|
||||
data-testid="titlebar-sidebar-toggle"
|
||||
aria-label="折叠侧栏"
|
||||
title="折叠侧栏"
|
||||
aria-expanded="true"
|
||||
onClick={() => setSidebarCollapsed(true)}
|
||||
className="no-drag motion-press flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-surface-subtle hover:text-foreground"
|
||||
><PanelLeftClose className="h-4 w-4" /></button>}
|
||||
</div>}
|
||||
{workspaceLayout && <button type="button" aria-label="返回首页" title="返回首页" onClick={() => navigate('/module-select')} className="classroom-brand flex w-full items-center gap-2 rounded-lg text-left">
|
||||
<span className="classroom-brand-mark"><img src={classroomMark} alt="" className="h-7 w-7" /></span>
|
||||
|
||||
@@ -15,7 +15,8 @@ import { useSettingsStore } from '@/stores/settings';
|
||||
import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout';
|
||||
import logoWordmarkSource from '@/assets/makelore-wordmark-source.png';
|
||||
import type { SidebarPeekSource } from './sidebar-peek';
|
||||
import { TITLEBAR_LOGO_WIDTH, WINDOWS_TITLEBAR_CONTROLS_WIDTH } from './titlebar-metrics';
|
||||
import { ModuleSwitcher } from './ModuleSwitcher';
|
||||
import { getClassroomTitlebarWidth, TITLEBAR_LOGO_WIDTH, WINDOWS_TITLEBAR_CONTROLS_WIDTH } from './titlebar-metrics';
|
||||
|
||||
type SidebarPeekChange = (open: boolean, source: SidebarPeekSource) => void;
|
||||
|
||||
@@ -69,6 +70,7 @@ function ProductTitleBar({
|
||||
}) {
|
||||
const sidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
|
||||
const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth));
|
||||
const classroomTitlebarWidth = getClassroomTitlebarWidth(nativeTrafficLights ? 'darwin' : 'win32', sidebarCollapsed, sidebarWidth);
|
||||
const activeProject = useCodingWorkspaceStore((state) => state.activeProject);
|
||||
const toolbarButtonClass = 'no-drag motion-press flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-surface-subtle hover:text-foreground';
|
||||
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
|
||||
@@ -94,13 +96,13 @@ function ProductTitleBar({
|
||||
className={cn(
|
||||
toolbarButtonClass,
|
||||
'pointer-events-auto absolute top-1 z-[401]',
|
||||
nativeTrafficLights ? 'left-[88px]' : 'left-2',
|
||||
workspaceLayout ? 'right-2' : nativeTrafficLights ? 'left-[88px]' : 'left-2',
|
||||
)}
|
||||
onClick={toggleSidebar}
|
||||
onPointerEnter={() => handleSidebarPeekEnter('toggle')}
|
||||
onPointerLeave={() => handleSidebarPeekLeave('toggle')}
|
||||
onFocus={() => handleSidebarPeekEnter('focus')}
|
||||
onBlur={() => handleSidebarPeekLeave('focus')}
|
||||
onFocus={!workspaceLayout ? () => handleSidebarPeekEnter('focus') : undefined}
|
||||
onBlur={!workspaceLayout ? () => handleSidebarPeekLeave('focus') : undefined}
|
||||
>
|
||||
{sidebarCollapsed ? <PanelLeft className="h-4 w-4" /> : <PanelLeftClose className="h-4 w-4" />}
|
||||
</button>
|
||||
@@ -110,10 +112,23 @@ function ProductTitleBar({
|
||||
data-testid="titlebar-sidebar-toggle-layer"
|
||||
className={cn(
|
||||
'no-drag pointer-events-auto fixed left-0 top-0 z-[400] h-10',
|
||||
nativeTrafficLights ? 'w-[132px]' : 'w-[52px]',
|
||||
!workspaceLayout && (nativeTrafficLights ? 'w-[132px]' : 'w-[52px]'),
|
||||
)}
|
||||
style={workspaceLayout ? { width: classroomTitlebarWidth } : undefined}
|
||||
onPointerEnter={workspaceLayout ? () => handleSidebarPeekEnter('titlebar') : undefined}
|
||||
onPointerLeave={workspaceLayout ? () => handleSidebarPeekLeave('titlebar') : undefined}
|
||||
onFocusCapture={workspaceLayout ? () => handleSidebarPeekEnter('focus') : undefined}
|
||||
onBlurCapture={workspaceLayout ? (event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget as Node | null)) handleSidebarPeekLeave('focus');
|
||||
} : undefined}
|
||||
>
|
||||
{sidebarToggle}
|
||||
{workspaceLayout ? <div
|
||||
data-testid="classroom-titlebar-navigation"
|
||||
className={cn('flex h-full items-center gap-2 pr-12', nativeTrafficLights ? 'pl-[88px]' : 'pl-2')}
|
||||
>
|
||||
<ModuleSwitcher sidebarCollapsed={false} toolbar iconOnly />
|
||||
{sidebarToggle}
|
||||
</div> : sidebarToggle}
|
||||
</div>
|
||||
);
|
||||
const titlebarLogo = (
|
||||
@@ -155,16 +170,16 @@ function ProductTitleBar({
|
||||
<>
|
||||
<div
|
||||
data-testid="titlebar-sidebar-surface"
|
||||
style={workspaceLayout && !sidebarTitlebarHidden ? { width: sidebarWidth, minWidth: sidebarWidth, flexBasis: sidebarWidth } : undefined}
|
||||
style={workspaceLayout ? { width: classroomTitlebarWidth, minWidth: classroomTitlebarWidth, flexBasis: classroomTitlebarWidth } : undefined}
|
||||
className={cn(
|
||||
'flex h-full shrink-0 items-center gap-1 px-2',
|
||||
sidebarTitlebarHidden
|
||||
workspaceLayout
|
||||
? ''
|
||||
: sidebarTitlebarHidden
|
||||
? nativeTrafficLights
|
||||
? 'w-[132px]'
|
||||
: 'w-[52px]'
|
||||
: workspaceLayout
|
||||
? ''
|
||||
: 'w-64',
|
||||
: 'w-64',
|
||||
overlay
|
||||
? 'pointer-events-none border-r-0 bg-transparent'
|
||||
: sidebarTitlebarHidden
|
||||
@@ -178,7 +193,7 @@ function ProductTitleBar({
|
||||
onPointerEnter={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekEnter('titlebar') : undefined}
|
||||
onPointerLeave={sidebarPreviewTitlebarVisible ? () => handleSidebarPeekLeave('titlebar') : undefined}
|
||||
/>
|
||||
{(!workspaceLayout || sidebarCollapsed) && (typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal)}
|
||||
{typeof document !== 'undefined' ? createPortal(sidebarTogglePortal, document.body) : sidebarTogglePortal}
|
||||
</>
|
||||
) : null}
|
||||
{integrated ? (
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout';
|
||||
|
||||
export const TITLEBAR_LOGO_WIDTH = 96;
|
||||
|
||||
// Three 44px buttons plus the control container's 8px padding on each side.
|
||||
export const WINDOWS_TITLEBAR_CONTROLS_WIDTH = 148;
|
||||
|
||||
/** Keep the Code navigation and conversation header outside each other's hit areas. */
|
||||
export function getClassroomTitlebarWidth(platform: string | undefined, sidebarCollapsed: boolean, sidebarWidth: number): number {
|
||||
// Two 32px controls, an 8px gap/right inset, and the native traffic-light safe area.
|
||||
const minimum = platform === 'darwin' ? 168 : 88;
|
||||
return sidebarCollapsed ? minimum : Math.max(minimum, normalizeClassroomSidebarWidth(sidebarWidth));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { CodingConversationRenameDialog } from './CodingConversationRenameDialog
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSettingsStore } from '@/stores/settings';
|
||||
import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout';
|
||||
import { WINDOWS_TITLEBAR_CONTROLS_WIDTH } from '@/components/layout/titlebar-metrics';
|
||||
import { getClassroomTitlebarWidth, WINDOWS_TITLEBAR_CONTROLS_WIDTH } from '@/components/layout/titlebar-metrics';
|
||||
import type { ConversationSnapshot } from '@/types/coding-conversation';
|
||||
import type { CodingConversationMetadata } from '@/types/coding-project';
|
||||
|
||||
@@ -189,7 +189,7 @@ export function CodingConversationHeader({
|
||||
className="pointer-events-none fixed right-0 top-0 z-[80] h-10"
|
||||
data-testid="coding-conversation-header-titlebar"
|
||||
style={{
|
||||
left: `${sidebarCollapsed ? (platform === 'darwin' ? 132 : 52) : Math.max(sidebarWidth, platform === 'darwin' ? 132 : 0)}px`,
|
||||
left: `${getClassroomTitlebarWidth(platform, sidebarCollapsed, sidebarWidth)}px`,
|
||||
// Keep both actions and their hit area outside the product chrome.
|
||||
right: platform === 'win32' ? WINDOWS_TITLEBAR_CONTROLS_WIDTH : 0,
|
||||
}}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
[data-testid="main-layout"][data-classroom="true"] [data-testid="titlebar-sidebar-surface"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; }
|
||||
[data-testid="sidebar"][data-classroom="true"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; color: #26384d; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header { padding-top: 0; padding-bottom: 0; }
|
||||
[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: flex-start; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-brand { gap: 10px; padding: 0 2px 8px; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 36px; margin-top: 10px; gap: 4px; padding: 6px 4px; border-color: #cfddc3; border-radius: 11px; background: #e8f0df; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #dfeacf; border-color: #b7c6a4; }
|
||||
[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: center; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-brand { height: 46px; gap: 10px; padding: 0 2px; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"] { min-height: 41px; margin-top: 10px; gap: 9px; padding: 8px 11px; border: 1px solid #d5e1ed; border-radius: 11px; background: #f8faf5; box-shadow: 0 3px 0 #e3eaf3; font-size: 14px; font-weight: 500; line-height: 20px; white-space: normal; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #eef3e7; border-color: #b7c6a4; }
|
||||
[data-testid="sidebar"][data-classroom="true"] .classroom-brand:hover { background: #eaf1f8; }
|
||||
.classroom-brand-mark { display: grid; place-items: center; width: 40px; height: 42px; flex-shrink: 0; border: 1px solid #f1d1aa; border-radius: 12px; background: #fff0dd; box-shadow: 0 3px 0 #ecdbc5; }
|
||||
.classroom-brand-caption { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: #64758a; }
|
||||
|
||||
Reference in New Issue
Block a user