Updated the Sidebar component to include smoother transitions for width, padding, and background color changes. Adjusted layout styles for better responsiveness and visual appeal, ensuring a more cohesive user experience when toggling the sidebar. Refactored navigation button styles for consistency and added transition effects to icon and label visibility based on the collapsed state.
203 lines
6.3 KiB
TypeScript
203 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Bot,
|
|
Boxes,
|
|
ChevronLeft,
|
|
Clock3,
|
|
FileText,
|
|
Home,
|
|
PlayCircle,
|
|
User,
|
|
Video,
|
|
Workflow,
|
|
} from "lucide-react";
|
|
import type { NavKey } from "./AppShell";
|
|
|
|
type SidebarProps = {
|
|
active: NavKey;
|
|
collapsed: boolean;
|
|
onNavigate: (key: NavKey) => void;
|
|
onToggle: () => void;
|
|
};
|
|
|
|
const mainItems: Array<{
|
|
key: NavKey;
|
|
label: string;
|
|
icon: React.ComponentType<{ size?: number }>;
|
|
}> = [
|
|
{ key: "home", label: "控制台", icon: Home },
|
|
{ key: "components", label: "组件库", icon: Boxes },
|
|
{ key: "history", label: "历史记录", icon: Clock3 },
|
|
{ key: "test", label: "测试助手", icon: PlayCircle },
|
|
{ key: "workflow", label: "工作流", icon: Workflow },
|
|
{ key: "profile", label: "个人中心", icon: User },
|
|
];
|
|
|
|
const assistantSubItems: Array<{
|
|
key: NavKey;
|
|
label: string;
|
|
icon: React.ComponentType<{ size?: number }>;
|
|
}> = [
|
|
{ key: "assistant-prompt", label: "提示词模式", icon: FileText },
|
|
{ key: "assistant-workflow", label: "工作流模式", icon: Workflow },
|
|
];
|
|
|
|
export function Sidebar({
|
|
active,
|
|
collapsed,
|
|
onNavigate,
|
|
onToggle,
|
|
}: SidebarProps) {
|
|
const assistantActive =
|
|
active === "assistant-prompt" || active === "assistant-workflow";
|
|
|
|
return (
|
|
<aside
|
|
className={[
|
|
"flex shrink-0 flex-col overflow-hidden border-r border-sidebar-border bg-sidebar transition-[width] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[width]",
|
|
collapsed ? "w-[76px]" : "w-[252px]",
|
|
].join(" ")}
|
|
>
|
|
<div className="flex h-[81px] items-center gap-3 border-b border-sidebar-border px-5 transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]">
|
|
<div
|
|
className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl text-on-primary shadow-sm"
|
|
style={{
|
|
backgroundColor: "var(--primary)",
|
|
backgroundImage:
|
|
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%), radial-gradient(circle at 80% 90%, color-mix(in srgb, var(--gradient-lavender) 65%, transparent), transparent 55%)",
|
|
}}
|
|
>
|
|
<Video size={22} style={{ color: "var(--primary-foreground)" }} />
|
|
</div>
|
|
|
|
<div
|
|
className={[
|
|
"min-w-0 overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 opacity-0 -translate-x-2" : "w-[140px] opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
<div className="truncate font-display text-base text-foreground">
|
|
AI 视频助手
|
|
</div>
|
|
<div className="truncate caption-label text-muted-soft">管理台</div>
|
|
</div>
|
|
</div>
|
|
|
|
<nav className="flex-1 space-y-1 px-3 py-5">
|
|
<NavButton
|
|
active={active === "home"}
|
|
collapsed={collapsed}
|
|
icon={Home}
|
|
label="控制台"
|
|
onClick={() => onNavigate("home")}
|
|
/>
|
|
|
|
<div className="pt-2">
|
|
<div
|
|
className={[
|
|
"flex h-11 w-full items-center gap-3 rounded-full px-3 text-sm",
|
|
assistantActive ? "text-foreground" : "text-muted-foreground",
|
|
collapsed ? "justify-center" : "",
|
|
].join(" ")}
|
|
>
|
|
<Bot size={18} />
|
|
{!collapsed && <span className="font-medium">创建助手</span>}
|
|
</div>
|
|
|
|
<div
|
|
className={[
|
|
"mt-1 space-y-1 transition-[padding] duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "pl-0" : "pl-5",
|
|
].join(" ")}
|
|
>
|
|
{assistantSubItems.map((item) => (
|
|
<NavButton
|
|
key={item.key}
|
|
active={active === item.key}
|
|
collapsed={collapsed}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
onClick={() => onNavigate(item.key)}
|
|
small
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
{mainItems.slice(1).map((item) => (
|
|
<NavButton
|
|
key={item.key}
|
|
active={active === item.key}
|
|
collapsed={collapsed}
|
|
icon={item.icon}
|
|
label={item.label}
|
|
onClick={() => onNavigate(item.key)}
|
|
/>
|
|
))}
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="border-t border-sidebar-border p-3">
|
|
<button
|
|
onClick={onToggle}
|
|
className="flex h-10 w-full items-center justify-center rounded-full border border-hairline-strong text-muted-foreground transition-[background-color,color,border-color,transform] duration-200 hover:bg-surface-strong hover:text-foreground active:scale-95"
|
|
>
|
|
<ChevronLeft
|
|
size={18}
|
|
className={[
|
|
"transition-transform duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "rotate-180" : "rotate-0",
|
|
].join(" ")}
|
|
/>
|
|
</button>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|
|
|
|
function NavButton({
|
|
active,
|
|
collapsed,
|
|
icon: Icon,
|
|
label,
|
|
onClick,
|
|
small = false,
|
|
}: {
|
|
active: boolean;
|
|
collapsed: boolean;
|
|
icon: React.ComponentType<{ size?: number }>;
|
|
label: string;
|
|
onClick: () => void;
|
|
small?: boolean;
|
|
}) {
|
|
return (
|
|
<button
|
|
onClick={onClick}
|
|
title={collapsed ? label : undefined}
|
|
className={[
|
|
"group mt-1 flex w-full items-center gap-3 overflow-hidden rounded-full px-3 text-sm transition-[background-color,color,transform] duration-200 hover:translate-x-0.5 active:scale-[0.98]",
|
|
small ? "h-10" : "h-11",
|
|
active
|
|
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
|
|
: "text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground",
|
|
collapsed ? "justify-center" : "",
|
|
].join(" ")}
|
|
>
|
|
<Icon
|
|
size={small ? 16 : 18}
|
|
className="shrink-0 transition-transform duration-200 group-hover:scale-105"
|
|
/>
|
|
<span
|
|
className={[
|
|
"min-w-0 truncate transition-all duration-300 ease-[cubic-bezier(0.22,1,0.36,1)]",
|
|
collapsed ? "w-0 opacity-0 -translate-x-2" : "w-[150px] opacity-100 translate-x-0",
|
|
].join(" ")}
|
|
>
|
|
{label}
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|