259 lines
9.3 KiB
TypeScript
259 lines
9.3 KiB
TypeScript
import { ArrowRight, PlayCircle } from "lucide-react";
|
|
import type { ReactNode } from "react";
|
|
import { FloatingSidebarRail } from "@/components/home/floating-sidebar-shell";
|
|
import {
|
|
HoverCard,
|
|
HoverCardContent,
|
|
HoverCardTrigger,
|
|
} from "@/components/ui/hover-card";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
import type { LovartHomeSidebarItemKey } from "@/lib/home/lovart-sidebar-config";
|
|
import type { NavLink, TutorialVideo } from "./types";
|
|
|
|
type SidebarItem = {
|
|
iconUrl?: string | null;
|
|
label: string;
|
|
};
|
|
|
|
type LovartSidebarProps = {
|
|
agentEnabled: boolean;
|
|
getItem: (key: LovartHomeSidebarItemKey) => SidebarItem;
|
|
isEnabled: (key: LovartHomeSidebarItemKey) => boolean;
|
|
isProjectsOnlyView: boolean;
|
|
onAllProjects: () => void;
|
|
onGallery: () => void;
|
|
onHome: () => void;
|
|
onImageEditor: () => void;
|
|
onNavLink: (url: string) => void;
|
|
onNewCanvas: () => void;
|
|
onProfile: () => void;
|
|
onRoute: (url: string) => void;
|
|
onTutorial: (video: TutorialVideo) => void;
|
|
productPlanningEnabled: boolean;
|
|
renderIcon: (
|
|
key: LovartHomeSidebarItemKey,
|
|
options?: {
|
|
active?: boolean;
|
|
filled?: boolean;
|
|
sizeClassName?: string;
|
|
}
|
|
) => ReactNode;
|
|
settingsHref: string | null;
|
|
toolbox1Enabled: boolean;
|
|
toolbox1Links: NavLink[];
|
|
toolbox2Enabled: boolean;
|
|
toolbox2Links: NavLink[];
|
|
tutorialEnabled: boolean;
|
|
tutorialVideos: TutorialVideo[];
|
|
videoCanvasEnabled: boolean;
|
|
};
|
|
|
|
export function LovartSidebar({
|
|
agentEnabled,
|
|
getItem,
|
|
isEnabled,
|
|
isProjectsOnlyView,
|
|
onAllProjects,
|
|
onGallery,
|
|
onHome,
|
|
onImageEditor,
|
|
onNavLink,
|
|
onNewCanvas,
|
|
onProfile,
|
|
onRoute,
|
|
onTutorial,
|
|
productPlanningEnabled,
|
|
renderIcon,
|
|
settingsHref,
|
|
toolbox1Enabled,
|
|
toolbox1Links,
|
|
toolbox2Enabled,
|
|
toolbox2Links,
|
|
tutorialEnabled,
|
|
tutorialVideos,
|
|
videoCanvasEnabled,
|
|
}: LovartSidebarProps) {
|
|
const sidebarPopupBaseClass =
|
|
"rounded-[24px] border border-black/8 bg-white/96 p-2.5 text-[#111827] shadow-[0_24px_64px_rgba(15,23,42,0.12)] backdrop-blur-xl";
|
|
const sidebarButtonClass = (active = false) =>
|
|
[
|
|
"group flex h-10 w-10 items-center justify-center rounded-xl text-[#6b7280] transition-all duration-200 hover:bg-black/[0.04] hover:text-[#111827]",
|
|
active
|
|
? "bg-[#111827] text-white shadow-[0_10px_22px_rgba(15,23,42,0.18)]"
|
|
: "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ");
|
|
|
|
const tooltipButton = (
|
|
key: LovartHomeSidebarItemKey,
|
|
onClick: () => void,
|
|
options?: { active?: boolean }
|
|
) =>
|
|
isEnabled(key) ? (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
type="button"
|
|
className={sidebarButtonClass(options?.active)}
|
|
onClick={onClick}
|
|
title={getItem(key).label}
|
|
>
|
|
{renderIcon(key, { active: options?.active })}
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">{getItem(key).label}</TooltipContent>
|
|
</Tooltip>
|
|
) : null;
|
|
|
|
const toolbox = (
|
|
key: "toolbox1" | "toolbox2",
|
|
links: NavLink[],
|
|
enabled: boolean
|
|
) =>
|
|
isEnabled(key) && enabled && links.length > 0 ? (
|
|
<HoverCard openDelay={80} closeDelay={80}>
|
|
<HoverCardTrigger asChild>
|
|
<button
|
|
type="button"
|
|
className={sidebarButtonClass()}
|
|
title={getItem(key).label}
|
|
>
|
|
{renderIcon(key)}
|
|
</button>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent
|
|
side="right"
|
|
align="start"
|
|
className={`w-[248px] ${sidebarPopupBaseClass}`}
|
|
>
|
|
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/40">
|
|
{getItem(key).label}
|
|
</div>
|
|
<div className="space-y-1">
|
|
{links.map((link) => (
|
|
<button
|
|
key={link.id}
|
|
type="button"
|
|
className="flex w-full items-center justify-between rounded-2xl px-3 py-2.5 text-left text-[13px] text-black/80 transition hover:bg-black/[0.04]"
|
|
onClick={() => onNavLink(link.url)}
|
|
>
|
|
<span className="line-clamp-1">{link.name}</span>
|
|
<ArrowRight className="h-3.5 w-3.5 text-black/30" />
|
|
</button>
|
|
))}
|
|
</div>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
) : null;
|
|
|
|
return (
|
|
<aside className="fixed left-5 top-[88px] z-30 hidden flex-col items-center gap-3 lg:flex">
|
|
<TooltipProvider delayDuration={120}>
|
|
{isEnabled("newProject") ? (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<button
|
|
type="button"
|
|
onClick={onNewCanvas}
|
|
className="flex h-[56px] w-[56px] shrink-0 items-center justify-center rounded-[20px] border border-black/8 bg-white/96 shadow-[0_20px_46px_rgba(15,23,42,0.12)] backdrop-blur-xl transition-transform duration-200 hover:-translate-y-0.5 hover:bg-white"
|
|
title={getItem("newProject").label}
|
|
>
|
|
<div className="flex h-9 w-9 items-center justify-center rounded-[12px] bg-[#111827] text-white shadow-[0_10px_20px_rgba(15,23,42,0.18)]">
|
|
{renderIcon("newProject", {
|
|
filled: true,
|
|
sizeClassName: "h-4 w-4",
|
|
})}
|
|
</div>
|
|
</button>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="right">{getItem("newProject").label}</TooltipContent>
|
|
</Tooltip>
|
|
) : null}
|
|
<FloatingSidebarRail className="max-h-[calc(100vh-172px)]" tone="light">
|
|
{tooltipButton("home", onHome, { active: !isProjectsOnlyView })}
|
|
{tooltipButton("projects", onAllProjects, { active: isProjectsOnlyView })}
|
|
{tooltipButton("profile", onProfile)}
|
|
{tooltipButton("gallery", onGallery)}
|
|
{isEnabled("tutorial") && tutorialEnabled ? (
|
|
<HoverCard openDelay={80} closeDelay={100}>
|
|
<HoverCardTrigger asChild>
|
|
<button
|
|
type="button"
|
|
className={sidebarButtonClass()}
|
|
title={getItem("tutorial").label}
|
|
>
|
|
{renderIcon("tutorial")}
|
|
</button>
|
|
</HoverCardTrigger>
|
|
<HoverCardContent
|
|
side="right"
|
|
align="start"
|
|
className={`w-[300px] ${sidebarPopupBaseClass}`}
|
|
>
|
|
<div className="px-2 pb-1 pt-1 text-xs font-medium text-black/40">
|
|
{getItem("tutorial").label}
|
|
</div>
|
|
<div className="max-h-[380px] space-y-1 overflow-y-auto">
|
|
{tutorialVideos.length > 0 ? (
|
|
tutorialVideos.map((video) => (
|
|
<button
|
|
key={video.id}
|
|
type="button"
|
|
className="flex w-full items-center gap-2.5 rounded-2xl p-2.5 text-left transition hover:bg-black/[0.04]"
|
|
onClick={() => onTutorial(video)}
|
|
>
|
|
<div className="h-10 w-16 flex-shrink-0 overflow-hidden rounded-xl bg-black/[0.04]">
|
|
{video.coverUrl ? (
|
|
<img
|
|
src={video.coverUrl}
|
|
alt={video.title}
|
|
className="h-full w-full object-cover"
|
|
/>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<PlayCircle className="h-4 w-4 text-black/35" />
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="truncate text-[13px] font-medium text-black/85">
|
|
{video.title}
|
|
</div>
|
|
{video.description ? (
|
|
<div className="truncate text-[11px] text-black/42">
|
|
{video.description}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</button>
|
|
))
|
|
) : (
|
|
<div className="px-2 py-3 text-[12px] text-black/42">暂无教程视频</div>
|
|
)}
|
|
</div>
|
|
</HoverCardContent>
|
|
</HoverCard>
|
|
) : null}
|
|
{tooltipButton("imageEditor", onImageEditor)}
|
|
{tooltipButton("aiCad", () => onRoute("/ai-cad"))}
|
|
{agentEnabled ? tooltipButton("agent", () => onRoute("/canvas?mode=agent")) : null}
|
|
{videoCanvasEnabled ? tooltipButton("videoCanvas", () => onRoute("/video-canvas")) : null}
|
|
{tooltipButton("flowBoard", () => onRoute("/flow-board"))}
|
|
{productPlanningEnabled
|
|
? tooltipButton("productPlanning", () => onRoute("/product-planning"))
|
|
: null}
|
|
{toolbox("toolbox1", toolbox1Links, toolbox1Enabled)}
|
|
{toolbox("toolbox2", toolbox2Links, toolbox2Enabled)}
|
|
{settingsHref ? tooltipButton("settings", () => onRoute(settingsHref)) : null}
|
|
</FloatingSidebarRail>
|
|
</TooltipProvider>
|
|
</aside>
|
|
);
|
|
}
|