frontend: FE-029 Finder式工作台外壳,毛玻璃侧栏+透明工具栏+折叠过渡动画+动作图标化

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
fenghp
2026-06-11 20:14:46 +08:00
co-authored by Cursor
parent eec2cc85a3
commit c0f613fcb6
7 changed files with 72 additions and 27 deletions
+18 -8
View File
@@ -1,4 +1,4 @@
import { PanelBottom, PanelLeft, PanelRight } from "lucide-react"
import { FolderCog, PanelBottom, PanelLeft, PanelRight, Unplug } from "lucide-react"
import { Button } from "~/components/ui/button"
import { Separator } from "~/components/ui/separator"
@@ -36,7 +36,7 @@ export function TopBar({
return (
<header
className={cn(
"app-drag flex h-12 shrink-0 items-center justify-between gap-3 border-b bg-background px-3",
"app-drag flex h-12 shrink-0 items-center justify-between gap-3 border-b px-3",
macInset && "pl-20",
)}
>
@@ -80,12 +80,22 @@ export function TopBar({
<PanelBottom />
</PanelToggle>
<Separator orientation="vertical" className="mx-1 h-4!" />
<Button size="sm" variant="outline" onClick={onManageWorkspace}>
工作区
</Button>
<Button size="sm" variant="outline" onClick={onReconnect}>
重新连接
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon-sm" variant="ghost" onClick={onManageWorkspace} aria-label="工作区与映射管理">
<FolderCog />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">工作区与映射管理</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon-sm" variant="ghost" onClick={onReconnect} aria-label="重新连接">
<Unplug />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom">重新连接</TooltipContent>
</Tooltip>
</div>
</header>
)
@@ -73,7 +73,8 @@ export function WorkspaceShell({
return (
<TooltipProvider>
<div className="flex h-svh flex-col overflow-hidden bg-background">
{/* Finder 式布局:根容器不铺底色,vibrancy 下毛玻璃从顶栏与左侧栏透出 */}
<div className="flex h-svh flex-col overflow-hidden">
<TopBar
session={session}
panels={panels}
@@ -99,7 +100,13 @@ export function WorkspaceShell({
)}
<div className="flex min-h-0 flex-1">
{panels.tree && (
{/* 左右面板保持挂载,通过宽度过渡实现折叠 / 展开动画 */}
<div
className={cn(
"flex min-h-0 shrink-0 overflow-hidden transition-[width] duration-250 ease-out-quart",
panels.tree ? "w-[280px]" : "w-0",
)}
>
<SourceTreePanel
collection={session.collection}
mappings={session.mappings}
@@ -107,7 +114,7 @@ export function WorkspaceShell({
onNavigate={onNavigate}
onFileAction={actions.handleFileAction}
/>
)}
</div>
<FolderItemsPanel
session={session}
selectedServerPath={selectedServerPath}
@@ -117,7 +124,12 @@ export function WorkspaceShell({
onFileAction={actions.handleFileAction}
onRefresh={refreshItems}
/>
{panels.changes && (
<div
className={cn(
"flex min-h-0 shrink-0 justify-end overflow-hidden transition-[width] duration-250 ease-out-quart",
panels.changes ? "w-[340px]" : "w-0",
)}
>
<ChangesPanel
mappings={session.mappings}
pendingChanges={pending.pendingChanges}
@@ -130,16 +142,22 @@ export function WorkspaceShell({
onFileAction={actions.handleFileAction}
onRefresh={() => void pending.refresh()}
/>
)}
</div>
</div>
{panels.console && (
{/* 底部操作台通过高度过渡实现滑入滑出 */}
<div
className={cn(
"shrink-0 overflow-hidden transition-[height] duration-250 ease-out-quart",
panels.console ? "h-[180px]" : "h-0",
)}
>
<ConsolePanel
refreshToken={logsRefreshToken}
busy={actions.actionBusy || actions.checkinBusy}
busyText={actions.notice?.kind === "info" ? actions.notice.text : null}
/>
)}
</div>
<WorkspaceDialogs
dialog={actions.dialog}
@@ -129,7 +129,9 @@ export function SourceTreePanel({
return (
<aside className="flex h-full w-[280px] shrink-0 flex-col border-r bg-sidebar">
<div className="flex h-9 shrink-0 items-center gap-2 border-b px-3">
<span className="text-xs font-medium text-muted-foreground">源码目录</span>
<span className="text-[11px] font-medium tracking-wider text-muted-foreground uppercase">
源码目录
</span>
<span className="ml-auto max-w-28 truncate text-xs text-muted-foreground">{collection}</span>
<Button
variant="ghost"
@@ -66,9 +66,11 @@ export function ChangesPanel({
}, [onCheckin, included, comment])
return (
<aside className="flex h-full w-[340px] shrink-0 flex-col border-l bg-sidebar">
<aside className="flex h-full w-[340px] shrink-0 flex-col border-l bg-background">
<div className="flex h-9 shrink-0 items-center gap-2 border-b px-3">
<span className="text-xs font-medium text-muted-foreground">挂起更改</span>
<span className="text-[11px] font-medium tracking-wider text-muted-foreground uppercase">
挂起更改
</span>
{pendingChanges.length > 0 && (
<span className="ml-auto text-xs text-muted-foreground">{pendingChanges.length} 项</span>
)}
@@ -60,9 +60,11 @@ export function ConsolePanel({
)
return (
<footer className="flex h-[180px] shrink-0 flex-col border-t bg-background">
<footer className="flex h-full flex-col border-t bg-background">
<div className="flex h-8 shrink-0 items-center gap-2 border-b px-3">
<span className="text-xs font-medium text-muted-foreground">操作日志</span>
<span className="text-[11px] font-medium tracking-wider text-muted-foreground uppercase">
操作日志
</span>
{busy && (
<span className="flex items-center gap-1.5 text-xs text-primary">
<Loader2 className="size-3 animate-spin" />