feat: integrate pixel teacher presence and resilient classroom preview

This commit is contained in:
鲨鱼辣椒 committed 2026-09-23 18:23:53 +08:00
1 parent 12d800ec51
commit 7951cca700
60 files changed
+3313 -180

No files matched your search

+12 -4
View File
@@ -40,6 +40,8 @@ import type { SidebarPeekSource } from './sidebar-peek';
import { ModuleSwitcher } from './ModuleSwitcher';
import { ImageWorkspaceSidebar } from './ImageWorkspaceSidebar';
import { SidebarUpdateButton } from './SidebarUpdateButton';
import { ClassroomSidebarResizer } from './ClassroomSidebarResizer';
import { normalizeClassroomSidebarWidth } from '@/lib/classroom-layout';
import { UserAvatar } from '@/components/profile/UserAvatar';
import { getAccountInitial } from '@/components/profile/user-avatar-utils';
import { UserProfileDialog } from '@/components/profile/UserProfileDialog';
@@ -86,6 +88,7 @@ export function Sidebar({
}: SidebarProps = {}) {
const { t } = useTranslation('common');
const storedSidebarCollapsed = useSettingsStore((state) => state.sidebarCollapsed);
const sidebarWidth = useSettingsStore((state) => normalizeClassroomSidebarWidth(state.classroomSidebarWidth));
const sidebarPinnedCollapsed = !forceExpanded && storedSidebarCollapsed;
const sidebarCollapsed = sidebarPinnedCollapsed && !sidebarPeekOpen;
const projects = useCodingWorkspaceStore((state) => state.projects);
@@ -374,26 +377,30 @@ export function Sidebar({
return (
<aside
id={workspaceLayout ? 'classroom-sidebar' : undefined}
data-testid="sidebar"
data-classroom={workspaceLayout}
aria-hidden={sidebarCollapsed}
onPointerEnter={() => onSidebarPeekChange?.(true, 'sidebar')}
onPointerLeave={() => onSidebarPeekChange?.(false, 'sidebar')}
style={workspaceLayout && !sidebarCollapsed ? { width: sidebarWidth, minWidth: sidebarWidth, flexBasis: sidebarWidth } : undefined}
className={cn(
'sidebar-glass-surface flex min-h-0 shrink-0 flex-col overflow-hidden border-r border-border/80 text-foreground',
sidebarCollapsed
? 'pointer-events-none w-0 border-r-0 opacity-0'
: workspaceLayout
? 'w-[256px] min-w-[256px] basis-[256px] opacity-100'
? 'opacity-100'
: 'w-64 opacity-100',
// Keep the collapsed preview in the same fixed layer while it opens
// and closes. This makes the hover state cover the titlebar and the
// full window height without reflowing the page underneath it.
sidebarPinnedCollapsed && 'fixed inset-y-0 left-0 z-[350]',
workspaceLayout && !sidebarPinnedCollapsed && 'relative',
sidebarPeekPreview && 'sidebar-peek-surface pt-10 shadow-float',
hideOnCompact && 'hidden lg:flex',
)}
>
{workspaceLayout && !sidebarPinnedCollapsed && <ClassroomSidebarResizer />}
<div
className={cn(
'bg-transparent px-3 text-foreground',
@@ -402,8 +409,8 @@ export function Sidebar({
)}
>
<div className="min-w-0 w-full">
{workspaceLayout ? <button type="button" aria-label="返回首页" title="返回首页" onClick={() => navigate('/module-select')} className="flex w-full items-center gap-2.5 rounded-lg px-3 pb-4 pt-3 text-left hover:bg-black/[0.025]">
<img src={classroomMark} alt="" className="h-7 w-7" /><span className="text-lg font-semibold tracking-tight">麦洛<span className="ml-2 text-xs font-normal text-muted-foreground">Makelore</span></span>
{workspaceLayout ? <button type="button" aria-label="返回首页" title="返回首页" onClick={() => navigate('/module-select')} className="flex w-full items-center gap-2 rounded-lg px-1 pb-4 pt-3 text-left hover:bg-black/[0.025]">
<img src={classroomMark} alt="" className="h-7 w-7" /><span className="text-base font-semibold tracking-tight">麦洛</span>
</button> : <ModuleSwitcher sidebarCollapsed={sidebarCollapsed} compact={!sidebarCollapsed} />}
</div>
</div>
@@ -459,6 +466,7 @@ export function Sidebar({
type="button"
data-testid={`sidebar-course-project-${project.id}`}
aria-label={`进入项目 ${project.name}`}
title={project.name}
aria-current={active ? 'page' : undefined}
onClick={() => void openProject(project)}
className={cn(
@@ -471,7 +479,7 @@ export function Sidebar({
<FolderKanban className="h-3.5 w-3.5 shrink-0 text-muted-foreground" /><span className="min-w-0 flex-1 truncate text-[13px] font-medium">{project.name}</span>
<ChevronRight aria-hidden="true" className="h-3.5 w-3.5 shrink-0 -translate-x-0.5 text-muted-foreground opacity-50 transition-[opacity,transform,color] duration-200 ease-out group-hover/project:translate-x-0 group-hover/project:text-foreground group-hover/project:opacity-100 group-focus-visible/project:translate-x-0 group-focus-visible/project:text-foreground group-focus-visible/project:opacity-100" />
</button>
{active && <CodingProjectConversations key={project.id} projectId={project.id} />}
{active && <CodingProjectConversations key={project.id} projectId={project.id} compact={workspaceLayout} />}
</div>
);
})}