polish realtime canvas toolbar layout
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-08 00:37:44 +08:00
parent 356c46f480
commit d4ff127fb3

View File

@ -2254,13 +2254,13 @@ export function RealtimeCanvasDialog({
</DialogHeader>
)}
<div className="absolute left-4 right-4 top-4 z-50 pointer-events-none">
<div className="pointer-events-auto rounded-[28px] border border-black/10 bg-white/96 shadow-[0_22px_70px_rgba(15,23,42,0.14)] backdrop-blur-sm">
<div className="flex flex-wrap items-center gap-2 px-3 py-3">
<div className="flex items-center gap-3 rounded-full border border-black/10 bg-zinc-50 px-4 py-2">
<div className="pointer-events-auto rounded-[24px] border border-black/10 bg-white/98 shadow-[0_12px_36px_rgba(15,23,42,0.1)]">
<div className="flex items-center gap-2 overflow-x-auto px-2 py-2 [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
<div className="flex h-10 shrink-0 items-center gap-2 rounded-full border border-black/10 bg-zinc-50 px-3">
<span className="text-sm font-semibold text-zinc-900">Realtime Canvas</span>
<span
className={cn(
"flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium",
"flex items-center gap-1.5 rounded-full px-2 py-1 text-xs font-medium",
statusBadge.className
)}
>
@ -2281,12 +2281,12 @@ export function RealtimeCanvasDialog({
</span>
</div>
<div className="flex flex-wrap items-center gap-1 rounded-full border border-black/10 bg-white px-1.5 py-1">
<div className="flex h-10 shrink-0 items-center gap-0.5 rounded-full border border-black/10 bg-white px-1">
<Button
type="button"
size="sm"
variant="ghost"
className="h-9 rounded-full px-3 text-xs"
className="h-8 rounded-full px-2.5 text-xs"
onClick={() => void saveCurrentProject()}
>
<Save className="mr-1.5 h-3.5 w-3.5" />
@ -2296,7 +2296,7 @@ export function RealtimeCanvasDialog({
type="button"
size="sm"
variant="ghost"
className="h-9 rounded-full px-3 text-xs"
className="h-8 rounded-full px-2.5 text-xs"
onClick={() => {
refreshSavedProjects();
setProjectPanelOpen((openState) => !openState);
@ -2309,7 +2309,7 @@ export function RealtimeCanvasDialog({
type="button"
size="sm"
variant="ghost"
className="h-9 rounded-full px-3 text-xs"
className="h-8 rounded-full px-2.5 text-xs"
onClick={startBlankProject}
>
<Plus className="mr-1.5 h-3.5 w-3.5" />
@ -2320,7 +2320,7 @@ export function RealtimeCanvasDialog({
type="button"
size="icon"
variant="ghost"
className="h-9 w-9 rounded-full"
className="h-8 w-8 rounded-full"
onClick={() => onOpenChange(false)}
title="返回画布"
>
@ -2329,12 +2329,12 @@ export function RealtimeCanvasDialog({
) : null}
</div>
<div className="flex flex-wrap items-center gap-1 rounded-full border border-black/10 bg-white px-1.5 py-1">
<div className="flex h-10 shrink-0 items-center gap-0.5 rounded-full border border-black/10 bg-white px-1">
<Button
size="icon"
variant="ghost"
className={cn(
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100",
"h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100",
brushMode === "brush" && "bg-black text-white hover:bg-black"
)}
onClick={() => setBrushMode("brush")}
@ -2346,7 +2346,7 @@ export function RealtimeCanvasDialog({
size="icon"
variant="ghost"
className={cn(
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100",
"h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100",
brushMode === "eraser" && "bg-black text-white hover:bg-black"
)}
onClick={() => setBrushMode("eraser")}
@ -2358,7 +2358,7 @@ export function RealtimeCanvasDialog({
size="icon"
variant="ghost"
className={cn(
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100",
"h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100",
brushMode === "rect" && "bg-black text-white hover:bg-black"
)}
onClick={() => setBrushMode("rect")}
@ -2370,7 +2370,7 @@ export function RealtimeCanvasDialog({
size="icon"
variant="ghost"
className={cn(
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100",
"h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100",
brushMode === "circle" && "bg-black text-white hover:bg-black"
)}
onClick={() => setBrushMode("circle")}
@ -2382,7 +2382,7 @@ export function RealtimeCanvasDialog({
size="icon"
variant="ghost"
className={cn(
"h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100",
"h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100",
brushMode === "line" && "bg-black text-white hover:bg-black"
)}
onClick={() => setBrushMode("line")}
@ -2392,10 +2392,10 @@ export function RealtimeCanvasDialog({
</Button>
</div>
<div className="flex flex-wrap items-center gap-2 rounded-full border border-black/10 bg-zinc-50 px-3 py-2">
<div className="flex h-10 shrink-0 items-center gap-2 rounded-full border border-black/10 bg-zinc-50 px-3">
<button
type="button"
className="flex h-10 items-center gap-2 rounded-full border border-black/10 bg-white px-3 text-xs font-medium text-zinc-700 transition hover:bg-zinc-50"
className="flex h-8 items-center gap-2 rounded-full border border-black/10 bg-white px-3 text-xs font-medium text-zinc-700 transition hover:bg-zinc-50"
onClick={() => colorInputRef.current?.click()}
title="选择画笔颜色"
>
@ -2416,7 +2416,7 @@ export function RealtimeCanvasDialog({
<span className="min-w-[64px] text-[11px] uppercase tracking-wide text-zinc-500">
{brushColor}
</span>
<div className="flex min-w-[210px] flex-1 items-center gap-3 rounded-full border border-black/10 bg-white px-3 py-2">
<div className="flex w-[210px] items-center gap-3 rounded-full border border-black/10 bg-white px-3 py-1.5">
<span className="whitespace-nowrap text-xs font-medium text-zinc-600">
粗细 {brushSize}
</span>
@ -2429,7 +2429,7 @@ export function RealtimeCanvasDialog({
className="min-w-[140px] flex-1 [&>span:first-child]:h-2.5 [&>span:first-child]:bg-zinc-200 [&>span:first-child]:shadow-inner [&>span:first-child>span]:bg-sky-500 [&>span:last-child]:h-5 [&>span:last-child]:w-5 [&>span:last-child]:border-2 [&>span:last-child]:border-sky-500 [&>span:last-child]:bg-white"
/>
</div>
<div className="flex items-center gap-1 rounded-full border border-black/10 bg-white px-2 py-2">
<div className="flex h-8 items-center gap-1 rounded-full border border-black/10 bg-white px-2">
{CANVAS_BACKGROUND_PRESETS.map((color) => (
<button
key={color}
@ -2446,11 +2446,11 @@ export function RealtimeCanvasDialog({
</div>
</div>
<div className="flex flex-wrap items-center gap-1 rounded-full border border-black/10 bg-white px-1.5 py-1">
<div className="flex h-10 shrink-0 items-center gap-0.5 rounded-full border border-black/10 bg-white px-1">
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={() => uploadInputRef.current?.click()}
title="上传图片"
>
@ -2459,7 +2459,7 @@ export function RealtimeCanvasDialog({
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={clearSketch}
title="清空"
>
@ -2468,7 +2468,7 @@ export function RealtimeCanvasDialog({
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={undo}
disabled={!canUndo}
title="撤销"
@ -2478,7 +2478,7 @@ export function RealtimeCanvasDialog({
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={redo}
disabled={!canRedo}
title="重做"
@ -2488,7 +2488,7 @@ export function RealtimeCanvasDialog({
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={handleDownloadCanvas}
title="保存图片"
>
@ -2497,7 +2497,7 @@ export function RealtimeCanvasDialog({
<Button
size="icon"
variant="ghost"
className="h-10 w-10 rounded-full text-zinc-600 hover:bg-zinc-100"
className="h-8 w-8 rounded-full text-zinc-600 hover:bg-zinc-100"
onClick={forceReconnect}
title="重连"
>
@ -2505,48 +2505,11 @@ export function RealtimeCanvasDialog({
</Button>
</div>
<div className="flex min-w-[280px] flex-1 flex-wrap items-center gap-2 rounded-[22px] border border-black/10 bg-zinc-50 px-2 py-2">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-primary">
<Sparkles className="h-4 w-4" />
</div>
<Input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onBlur={() => scheduleFlush(true, false)}
placeholder="输入指令,基于当前画布实时生成"
className="h-10 min-w-[180px] flex-1 border-0 bg-transparent px-2 text-sm shadow-none focus-visible:ring-0"
/>
<Button
size="sm"
className="h-10 rounded-xl px-4 font-medium"
onClick={triggerGenerate}
disabled={status === "connecting"}
>
{status === "connecting" ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"生成"
)}
</Button>
<Button
size="sm"
variant="outline"
className="h-10 rounded-xl px-4 font-medium"
onClick={handleApplyToCanvas}
disabled={!previewUrl}
>
<Send className="mr-1.5 h-4 w-4" />
发送到画布
</Button>
<span className="ml-auto text-xs text-zinc-500">
{statusMessage}
</span>
</div>
</div>
</div>
</div>
{projectPanelOpen ? (
<div className="absolute left-4 top-28 z-40 w-[22rem] max-w-[calc(100vw-2rem)] rounded-[24px] border border-black/10 bg-white/95 p-4 shadow-2xl backdrop-blur-xl">
<div className="absolute left-4 top-20 z-40 w-[22rem] max-w-[calc(100vw-2rem)] rounded-[24px] border border-black/10 bg-white/95 p-4 shadow-2xl backdrop-blur-xl">
<div className="flex items-center justify-between">
<div>
<div className="text-sm font-semibold text-zinc-900">项目列表</div>
@ -2900,6 +2863,45 @@ export function RealtimeCanvasDialog({
className="hidden"
onChange={handleSketchUpload}
/>
<div className="absolute bottom-5 left-1/2 z-20 w-[min(760px,calc(100vw-2rem))] -translate-x-1/2 pointer-events-none">
<div className="pointer-events-auto flex items-center gap-2 rounded-[22px] border border-black/10 bg-white/96 px-2 py-2 shadow-[0_12px_40px_rgba(15,23,42,0.12)]">
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-primary/10 text-primary">
<Sparkles className="h-4 w-4" />
</div>
<Input
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onBlur={() => scheduleFlush(true, false)}
placeholder="输入指令,基于当前画布实时生成"
className="h-9 flex-1 border-0 bg-transparent px-1 text-sm shadow-none focus-visible:ring-0"
/>
<span className="hidden whitespace-nowrap text-xs text-zinc-500 xl:inline">
{statusMessage}
</span>
<Button
size="sm"
className="h-9 shrink-0 rounded-full px-4 font-medium"
onClick={triggerGenerate}
disabled={status === "connecting"}
>
{status === "connecting" ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"生成"
)}
</Button>
<Button
size="sm"
variant="outline"
className="h-9 shrink-0 rounded-full px-4 font-medium"
onClick={handleApplyToCanvas}
disabled={!previewUrl}
>
<Send className="mr-1.5 h-4 w-4" />
发送到画布
</Button>
</div>
</div>
</>
);