Refine video canvas sidebar UI
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-04-07 00:20:07 +08:00
parent ca0fbc5fd4
commit be8cb630f9

View File

@ -513,6 +513,53 @@ const VIDEO_CANVAS_SIDEBAR_GROUPS: VideoCanvasSidebarGroup[] = [
},
];
const VIDEO_CANVAS_SIDEBAR_GROUP_STYLES: Record<
VideoCanvasSidebarGroup["id"],
{
iconBg: string;
iconBorder: string;
iconText: string;
rail: string;
}
> = {
input: {
iconBg: "#eef3ff",
iconBorder: "#dbe4ff",
iconText: "#5b76fe",
rail: "#dde5ff",
},
analysis: {
iconBg: "#effaf4",
iconBorder: "#d9efe3",
iconText: "#46b078",
rail: "#dcefe5",
},
storyboard: {
iconBg: "#fff7e8",
iconBorder: "#f7e7c8",
iconText: "#ca8f2d",
rail: "#f0dfbc",
},
generation: {
iconBg: "#fff1f4",
iconBorder: "#f6dbe2",
iconText: "#dd5b7b",
rail: "#f1d9e0",
},
tool: {
iconBg: "#f3f4f8",
iconBorder: "#e3e6ef",
iconText: "#63708a",
rail: "#e2e5ed",
},
output: {
iconBg: "#eef7ff",
iconBorder: "#dbe9fb",
iconText: "#4d8ed8",
rail: "#d8e6f8",
},
};
function isPlainObject(value: unknown): value is Record<string, unknown> {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
@ -1356,8 +1403,8 @@ export default function CanvasEditorPage({ params }: PageProps) {
: null;
const videoModuleSidebarWidth = isVideoMode
? isVideoModuleSidebarCollapsed
? 60
: 264
? 58
: 236
: 0;
const selectedWorkflowPanelLeft = isVideoMode
? videoModuleSidebarWidth + 24
@ -8249,34 +8296,44 @@ export default function CanvasEditorPage({ params }: PageProps) {
>
<div
className={cn(
"overflow-hidden rounded-[24px] border border-[#dbe2ee] bg-white shadow-[0_0_0_1px_rgba(219,226,238,0.42),0_24px_52px_rgba(22,24,29,0.08)] dark:border-zinc-800 dark:bg-zinc-950",
isVideoModuleSidebarCollapsed ? "w-[60px]" : "w-[264px]"
"overflow-hidden rounded-[24px] border border-black/10 bg-white shadow-[0_10px_30px_rgba(15,23,42,0.06)] dark:border-zinc-800 dark:bg-zinc-950",
isVideoModuleSidebarCollapsed ? "w-[58px]" : "w-[236px]"
)}
>
<div className="flex items-center justify-between border-b border-[#e8edf5] px-3 py-3 dark:border-zinc-800">
<div
className={cn(
"flex items-center border-b border-black/5 px-2.5 py-2.5 dark:border-zinc-800",
isVideoModuleSidebarCollapsed
? "relative justify-center"
: "justify-between"
)}
>
{isVideoModuleSidebarCollapsed ? (
<div className="flex w-full justify-center">
<span className="flex h-9 w-9 items-center justify-center rounded-[18px] border border-[#dde5f4] bg-[#f6f8ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.24)]">
<span className="flex h-9 w-9 items-center justify-center rounded-[14px] border border-black/10 bg-[#f7f8fb] text-[#5b76fe]">
<Layers className="h-4 w-4" />
</span>
</div>
) : (
<div className="flex items-center gap-2">
<span className="flex h-9 w-9 items-center justify-center rounded-[18px] border border-[#dbe3ff] bg-[#eef2ff] text-[#5b76fe] shadow-[0_0_0_1px_rgba(91,118,254,0.08)] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300">
<div className="flex items-center gap-2.5">
<span className="flex h-9 w-9 items-center justify-center rounded-[14px] border border-black/10 bg-[#f7f8fb] text-[#5b76fe]">
<Layers className="h-4 w-4" />
</span>
<div>
<div className="text-sm font-semibold text-[#16181d]">
<div className="text-[15px] font-semibold text-[#16181d]">
视频模块
</div>
<div className="text-[11px] text-[#6f7483]">
工作流分层添加
<div className="text-[12px] text-[#7a766f]">
工作流分层
</div>
</div>
</div>
)}
<Button
className="h-8 w-8 rounded-[14px] text-[#6f7483] hover:bg-[#f7f9ff] hover:text-[#16181d] dark:hover:bg-zinc-900"
className={cn(
"h-8 w-8 rounded-[12px] text-[#7a766f] hover:bg-[#f6f5f4] hover:text-[#16181d] dark:hover:bg-zinc-900",
isVideoModuleSidebarCollapsed && "absolute top-2.5 right-2.5"
)}
onClick={() =>
setIsVideoModuleSidebarCollapsed((prev) => !prev)
}
@ -8293,12 +8350,14 @@ export default function CanvasEditorPage({ params }: PageProps) {
</div>
{isVideoModuleSidebarCollapsed ? (
<div className="flex flex-col items-center gap-2 px-2 py-2.5">
<div className="flex flex-col items-center gap-1.5 px-2 py-2">
{VIDEO_CANVAS_SIDEBAR_GROUPS.map((group) => {
const GroupIcon = group.icon;
const groupStyle =
VIDEO_CANVAS_SIDEBAR_GROUP_STYLES[group.id];
return (
<button
className="flex h-10 w-10 items-center justify-center rounded-[16px] border border-[#dde5f4] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.26)] transition-colors hover:bg-[#f4f7ff] hover:text-[#3954db] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
className="flex h-10 w-10 items-center justify-center rounded-[12px] border border-transparent bg-transparent transition-colors hover:bg-[#f6f5f4] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400 dark:hover:bg-zinc-900 dark:hover:text-zinc-100"
key={group.id}
onClick={() => {
setIsVideoModuleSidebarCollapsed(false);
@ -8309,6 +8368,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
}}
title={group.label}
type="button"
style={{ color: groupStyle.iconText }}
>
<GroupIcon className="h-4 w-4" />
</button>
@ -8317,9 +8377,11 @@ export default function CanvasEditorPage({ params }: PageProps) {
</div>
) : (
<ScrollArea className="max-h-[calc(100vh-164px)]">
<div className="space-y-3 p-3">
<div className="space-y-2 p-2.5">
{VIDEO_CANVAS_SIDEBAR_GROUPS.map((group) => {
const GroupIcon = group.icon;
const groupStyle =
VIDEO_CANVAS_SIDEBAR_GROUP_STYLES[group.id];
return (
<Collapsible
key={group.id}
@ -8331,28 +8393,35 @@ export default function CanvasEditorPage({ params }: PageProps) {
}
open={videoModuleSectionOpen[group.id] !== false}
>
<div className="overflow-hidden rounded-[20px] border border-[#e6ebf3] bg-[#fcfdff] dark:border-zinc-800 dark:bg-zinc-900">
<CollapsibleTrigger className="flex w-full items-center justify-between gap-3 px-3 py-2.5 text-left">
<div className="overflow-hidden rounded-[16px] border border-black/5 bg-[#fbfbfa] dark:border-zinc-800 dark:bg-zinc-900">
<CollapsibleTrigger className="flex w-full items-center justify-between gap-3 px-2.5 py-2.5 text-left transition-colors hover:bg-black/[0.018]">
<div className="flex min-w-0 items-center gap-2.5">
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[15px] border border-[#e3e8f2] bg-white text-[#5b76fe] shadow-[0_0_0_1px_rgba(219,226,238,0.3)] dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300">
<span
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[12px] border dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-300"
style={{
backgroundColor: groupStyle.iconBg,
borderColor: groupStyle.iconBorder,
color: groupStyle.iconText,
}}
>
<GroupIcon className="h-4 w-4" />
</span>
<div className="min-w-0 flex-1">
<span className="block truncate text-sm font-medium text-[#16181d]">
<span className="block truncate text-[14px] font-semibold text-[#16181d]">
{group.label}
</span>
<span className="block truncate text-[11px] text-[#7a8395]">
<span className="block truncate text-[11px] text-[#8b867e]">
{group.description}
</span>
</div>
</div>
<div className="flex items-center gap-1.5">
<span className="rounded-full border border-[#e2e7f1] bg-white px-2 py-0.5 text-[11px] text-[#6f7483] dark:border-zinc-800 dark:bg-zinc-950">
<div className="flex items-center gap-1">
<span className="rounded-full border border-black/8 bg-white px-2 py-0.5 text-[11px] text-[#7a766f] dark:border-zinc-800 dark:bg-zinc-950">
{group.nodeTypes.length}
</span>
<ChevronDown
className={cn(
"h-4 w-4 shrink-0 text-[#6f7483] transition-transform",
"h-4 w-4 shrink-0 text-[#7a766f] transition-transform",
videoModuleSectionOpen[group.id] !== false
? "rotate-0"
: "-rotate-90"
@ -8361,8 +8430,11 @@ export default function CanvasEditorPage({ params }: PageProps) {
</div>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="border-t border-[#edf1f7] px-2.5 pb-2.5 pt-2 dark:border-zinc-800">
<div className="space-y-1">
<div className="border-t border-black/5 px-2.5 pb-2.5 pt-1.5 dark:border-zinc-800">
<div
className="ml-4 space-y-0.5 border-l pl-3"
style={{ borderColor: groupStyle.rail }}
>
{group.nodeTypes.map((type) => {
const definition =
getVideoWorkflowNodeDefinition(type);
@ -8371,7 +8443,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
getVideoWorkflowQuickConnectIcon(type);
return (
<button
className="flex w-full items-center gap-2.5 rounded-[16px] border border-transparent bg-transparent px-2.5 py-2 text-left transition-all hover:border-[#d8e2ff] hover:bg-[#f4f7ff] hover:shadow-[0_0_0_1px_rgba(91,118,254,0.08)] dark:hover:bg-zinc-950"
className="flex w-full items-center gap-2 rounded-[12px] border border-transparent bg-transparent px-2 py-1.5 text-left transition-all hover:bg-white hover:shadow-[0_1px_2px_rgba(15,23,42,0.04)] dark:hover:bg-zinc-950"
key={type}
onClick={() =>
handleAddWorkflowNode(type)
@ -8380,17 +8452,19 @@ export default function CanvasEditorPage({ params }: PageProps) {
type="button"
>
<span
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-[14px] border border-white text-slate-600 shadow-[0_0_0_1px_rgba(219,226,238,0.32)] dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300"
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[10px] border border-transparent dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-300"
style={{
color: definition.accentColor,
backgroundColor: `${definition.accentColor}18`,
color: groupStyle.iconText,
backgroundColor: groupStyle.iconBg,
}}
>
<Icon className="h-4 w-4" />
</span>
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-[#16181d]">
{definition.label}
<Icon className="h-3.5 w-3.5" />
</span>
<div className="min-w-0 flex-1">
<span className="block truncate text-[13px] font-medium text-[#23211e]">
{definition.label}
</span>
</div>
</button>
);
})}