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> </DialogHeader>
)} )}
<div className="absolute left-4 right-4 top-4 z-50 pointer-events-none"> <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="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 flex-wrap items-center gap-2 px-3 py-3"> <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 items-center gap-3 rounded-full border border-black/10 bg-zinc-50 px-4 py-2"> <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="text-sm font-semibold text-zinc-900">Realtime Canvas</span>
<span <span
className={cn( 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 statusBadge.className
)} )}
> >
@ -2281,12 +2281,12 @@ export function RealtimeCanvasDialog({
</span> </span>
</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 <Button
type="button" type="button"
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-9 rounded-full px-3 text-xs" className="h-8 rounded-full px-2.5 text-xs"
onClick={() => void saveCurrentProject()} onClick={() => void saveCurrentProject()}
> >
<Save className="mr-1.5 h-3.5 w-3.5" /> <Save className="mr-1.5 h-3.5 w-3.5" />
@ -2296,7 +2296,7 @@ export function RealtimeCanvasDialog({
type="button" type="button"
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-9 rounded-full px-3 text-xs" className="h-8 rounded-full px-2.5 text-xs"
onClick={() => { onClick={() => {
refreshSavedProjects(); refreshSavedProjects();
setProjectPanelOpen((openState) => !openState); setProjectPanelOpen((openState) => !openState);
@ -2309,7 +2309,7 @@ export function RealtimeCanvasDialog({
type="button" type="button"
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-9 rounded-full px-3 text-xs" className="h-8 rounded-full px-2.5 text-xs"
onClick={startBlankProject} onClick={startBlankProject}
> >
<Plus className="mr-1.5 h-3.5 w-3.5" /> <Plus className="mr-1.5 h-3.5 w-3.5" />
@ -2320,7 +2320,7 @@ export function RealtimeCanvasDialog({
type="button" type="button"
size="icon" size="icon"
variant="ghost" variant="ghost"
className="h-9 w-9 rounded-full" className="h-8 w-8 rounded-full"
onClick={() => onOpenChange(false)} onClick={() => onOpenChange(false)}
title="返回画布" title="返回画布"
> >
@ -2329,12 +2329,12 @@ export function RealtimeCanvasDialog({
) : null} ) : null}
</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 <Button
size="icon" size="icon"
variant="ghost" variant="ghost"
className={cn( 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" brushMode === "brush" && "bg-black text-white hover:bg-black"
)} )}
onClick={() => setBrushMode("brush")} onClick={() => setBrushMode("brush")}
@ -2346,7 +2346,7 @@ export function RealtimeCanvasDialog({
size="icon" size="icon"
variant="ghost" variant="ghost"
className={cn( 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" brushMode === "eraser" && "bg-black text-white hover:bg-black"
)} )}
onClick={() => setBrushMode("eraser")} onClick={() => setBrushMode("eraser")}
@ -2358,7 +2358,7 @@ export function RealtimeCanvasDialog({
size="icon" size="icon"
variant="ghost" variant="ghost"
className={cn( 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" brushMode === "rect" && "bg-black text-white hover:bg-black"
)} )}
onClick={() => setBrushMode("rect")} onClick={() => setBrushMode("rect")}
@ -2370,7 +2370,7 @@ export function RealtimeCanvasDialog({
size="icon" size="icon"
variant="ghost" variant="ghost"
className={cn( 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" brushMode === "circle" && "bg-black text-white hover:bg-black"
)} )}
onClick={() => setBrushMode("circle")} onClick={() => setBrushMode("circle")}
@ -2382,7 +2382,7 @@ export function RealtimeCanvasDialog({
size="icon" size="icon"
variant="ghost" variant="ghost"
className={cn( 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" brushMode === "line" && "bg-black text-white hover:bg-black"
)} )}
onClick={() => setBrushMode("line")} onClick={() => setBrushMode("line")}
@ -2392,10 +2392,10 @@ export function RealtimeCanvasDialog({
</Button> </Button>
</div> </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 <button
type="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()} onClick={() => colorInputRef.current?.click()}
title="选择画笔颜色" title="选择画笔颜色"
> >
@ -2416,7 +2416,7 @@ export function RealtimeCanvasDialog({
<span className="min-w-[64px] text-[11px] uppercase tracking-wide text-zinc-500"> <span className="min-w-[64px] text-[11px] uppercase tracking-wide text-zinc-500">
{brushColor} {brushColor}
</span> </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"> <span className="whitespace-nowrap text-xs font-medium text-zinc-600">
粗细 {brushSize} 粗细 {brushSize}
</span> </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" 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>
<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) => ( {CANVAS_BACKGROUND_PRESETS.map((color) => (
<button <button
key={color} key={color}
@ -2446,11 +2446,11 @@ export function RealtimeCanvasDialog({
</div> </div>
</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 <Button
size="icon" size="icon"
variant="ghost" 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()} onClick={() => uploadInputRef.current?.click()}
title="上传图片" title="上传图片"
> >
@ -2459,7 +2459,7 @@ export function RealtimeCanvasDialog({
<Button <Button
size="icon" size="icon"
variant="ghost" 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} onClick={clearSketch}
title="清空" title="清空"
> >
@ -2468,7 +2468,7 @@ export function RealtimeCanvasDialog({
<Button <Button
size="icon" size="icon"
variant="ghost" 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} onClick={undo}
disabled={!canUndo} disabled={!canUndo}
title="撤销" title="撤销"
@ -2478,7 +2478,7 @@ export function RealtimeCanvasDialog({
<Button <Button
size="icon" size="icon"
variant="ghost" 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} onClick={redo}
disabled={!canRedo} disabled={!canRedo}
title="重做" title="重做"
@ -2488,7 +2488,7 @@ export function RealtimeCanvasDialog({
<Button <Button
size="icon" size="icon"
variant="ghost" 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} onClick={handleDownloadCanvas}
title="保存图片" title="保存图片"
> >
@ -2497,7 +2497,7 @@ export function RealtimeCanvasDialog({
<Button <Button
size="icon" size="icon"
variant="ghost" 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} onClick={forceReconnect}
title="重连" title="重连"
> >
@ -2505,48 +2505,11 @@ export function RealtimeCanvasDialog({
</Button> </Button>
</div> </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> </div>
</div> </div>
{projectPanelOpen ? ( {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 className="flex items-center justify-between">
<div> <div>
<div className="text-sm font-semibold text-zinc-900">项目列表</div> <div className="text-sm font-semibold text-zinc-900">项目列表</div>
@ -2900,6 +2863,45 @@ export function RealtimeCanvasDialog({
className="hidden" className="hidden"
onChange={handleSketchUpload} 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>
</> </>
); );