feat: move navigation to right sidebar
Switch top navigation to a vertical right rail with icon+label and adjust page layouts to reserve sidebar space.
This commit is contained in:
parent
33f2a729d6
commit
28d0f445c5
@ -43,7 +43,7 @@ export default async function AdminLayout({
|
|||||||
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
||||||
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1 pt-16">{children}</div>
|
<div className="flex-1 pr-20">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,9 +63,9 @@ async function SidebarWrapper({ children }: { children: React.ReactNode }) {
|
|||||||
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
||||||
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
||||||
/>
|
/>
|
||||||
<div className="pt-14 flex h-full w-full overflow-hidden"> {/* Add padding-top for fixed navbar */}
|
<div className="flex h-svh w-full overflow-hidden pr-20">
|
||||||
<AppSidebar user={session?.user} />
|
<AppSidebar user={session?.user} />
|
||||||
<SidebarInset className="h-[calc(100vh-56px)]">{children}</SidebarInset> {/* Adjust height */}
|
<SidebarInset className="h-full">{children}</SidebarInset>
|
||||||
</div>
|
</div>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -59,7 +59,7 @@ async function CanvasLayoutWrapper({ children }: { children: React.ReactNode })
|
|||||||
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
tutorialEnabled={siteSettings.tutorialEnabled ?? false}
|
||||||
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
rankingEnabled={siteSettings.rankingEnabled ?? true}
|
||||||
/>
|
/>
|
||||||
<div className="pt-12 h-full">
|
<div className="h-full pr-20">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export function AppSidebar({ user }: { user: User | undefined }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Sidebar className="group-data-[side=left]:border-r-0 !top-14 !h-[calc(100svh-3.5rem)]">
|
<Sidebar className="group-data-[side=left]:border-r-0">
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<div className="flex flex-row items-center justify-between w-full">
|
<div className="flex flex-row items-center justify-between w-full">
|
||||||
|
|||||||
@ -124,6 +124,25 @@ export function TopNavbar({
|
|||||||
// 决定显示哪个 LOGO
|
// 决定显示哪个 LOGO
|
||||||
const displayLogo = (siteLogoDark && siteLogoDark.trim() !== "") ? siteLogoDark : siteLogo;
|
const displayLogo = (siteLogoDark && siteLogoDark.trim() !== "") ? siteLogoDark : siteLogo;
|
||||||
|
|
||||||
|
const getShortLabel = (label: string) => {
|
||||||
|
const trimmed = label.trim();
|
||||||
|
if (!trimmed) return "";
|
||||||
|
if (trimmed === "AI精选") return "精选";
|
||||||
|
if (trimmed === "功能教程") return "教程";
|
||||||
|
if (trimmed === "精选榜") return "榜单";
|
||||||
|
if (trimmed === "图片编辑") return "编辑";
|
||||||
|
if (trimmed === "无限画布") return "画布";
|
||||||
|
if (trimmed === "我的图册") return "图册";
|
||||||
|
if (trimmed === "系统设置") return "设置";
|
||||||
|
if (trimmed === "工具箱") return "工具";
|
||||||
|
if (trimmed === "工具箱2") return "工具";
|
||||||
|
return Array.from(trimmed).slice(0, 2).join("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const navItemBaseClass =
|
||||||
|
"flex h-14 w-14 flex-col items-center justify-center gap-1 rounded-xl text-white hover:bg-white/10 transition-colors";
|
||||||
|
const navLabelClass = "text-[10px] leading-none font-medium";
|
||||||
|
|
||||||
// 打开图片编辑器 - 直接打开,不触发上传
|
// 打开图片编辑器 - 直接打开,不触发上传
|
||||||
const handleOpenImageEditor = () => {
|
const handleOpenImageEditor = () => {
|
||||||
setEditorImageUrl(""); // 确保初始为空
|
setEditorImageUrl(""); // 确保初始为空
|
||||||
@ -182,11 +201,11 @@ export function TopNavbar({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const navContent = (
|
const navContent = (
|
||||||
<nav className="fixed top-0 left-0 right-0 z-[100] flex h-12 items-center justify-between bg-black/90 backdrop-blur-sm px-4 text-white border-b border-white/10 shadow-sm">
|
<nav className="fixed right-0 top-0 bottom-0 z-[100] flex w-20 flex-col items-center bg-black/90 backdrop-blur-sm text-white border-l border-white/10 shadow-sm">
|
||||||
{/* 左侧:LOGO + 网站名称 */}
|
{/* 顶部:LOGO + 网站名称 */}
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
className="flex items-center gap-2 transition-opacity hover:opacity-80"
|
className="mt-3 flex flex-col items-center gap-1 transition-opacity hover:opacity-80"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
@ -196,14 +215,14 @@ export function TopNavbar({
|
|||||||
<img
|
<img
|
||||||
src={displayLogo}
|
src={displayLogo}
|
||||||
alt={siteName}
|
alt={siteName}
|
||||||
className="h-6 w-6 object-contain"
|
className="h-7 w-7 object-contain"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className="text-sm font-semibold">{siteName}</span>
|
<span className="max-w-[4.5rem] text-center text-[10px] leading-tight line-clamp-2">{siteName}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{/* 右侧区域:自定义链接 + 系统设置 */}
|
{/* 侧边功能区 */}
|
||||||
<div className="flex items-center gap-2">
|
<div className="mt-4 flex w-full flex-1 flex-col items-center gap-3 overflow-y-auto px-2 pb-4">
|
||||||
{/* 工具箱1 (如果启用且有链接) */}
|
{/* 工具箱1 (如果启用且有链接) */}
|
||||||
{toolbox1Enabled && customLinks.filter(l => (l.toolboxGroup || 1) === 1).length > 0 && (
|
{toolbox1Enabled && customLinks.filter(l => (l.toolboxGroup || 1) === 1).length > 0 && (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -211,31 +230,33 @@ export function TopNavbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
className={`${navItemBaseClass} p-0`}
|
||||||
|
title={toolbox1Name}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Briefcase className="h-4 w-4" />
|
<Briefcase className="h-5 w-5" />
|
||||||
<span className="text-sm font-medium">{toolbox1Name}</span>
|
<span className={navLabelClass}>{getShortLabel(toolbox1Name)}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[320px] p-2">
|
<DropdownMenuContent side="left" align="center" className="w-[320px] p-2">
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{customLinks
|
{customLinks
|
||||||
.filter(l => (l.toolboxGroup || 1) === 1)
|
.filter(l => (l.toolboxGroup || 1) === 1)
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
.map((link) => (
|
.map((link) => (
|
||||||
<DropdownMenuItem key={link.id} asChild className="cursor-pointer">
|
<DropdownMenuItem key={link.id} asChild className="cursor-pointer">
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex flex-col items-center justify-center gap-2 rounded-md bg-muted/50 p-4 hover:bg-accent hover:text-accent-foreground text-center h-20 transition-colors"
|
className="flex flex-col items-center justify-center gap-2 rounded-md bg-muted/50 p-4 hover:bg-accent hover:text-accent-foreground text-center h-20 transition-colors"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-5 w-5 opacity-70" />
|
<ExternalLink className="h-5 w-5 opacity-70" />
|
||||||
<span className="text-xs font-medium line-clamp-2">{link.name}</span>
|
<span className="text-xs font-medium line-clamp-2">{link.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
@ -247,31 +268,33 @@ export function TopNavbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
className={`${navItemBaseClass} p-0`}
|
||||||
|
title={toolbox2Name}
|
||||||
|
type="button"
|
||||||
>
|
>
|
||||||
<Briefcase className="h-4 w-4" />
|
<Briefcase className="h-5 w-5" />
|
||||||
<span className="text-sm font-medium">{toolbox2Name}</span>
|
<span className={navLabelClass}>{getShortLabel(toolbox2Name)}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[320px] p-2">
|
<DropdownMenuContent side="left" align="center" className="w-[320px] p-2">
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{customLinks
|
{customLinks
|
||||||
.filter(l => l.toolboxGroup === 2)
|
.filter(l => l.toolboxGroup === 2)
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
.map((link) => (
|
.map((link) => (
|
||||||
<DropdownMenuItem key={link.id} asChild className="cursor-pointer">
|
<DropdownMenuItem key={link.id} asChild className="cursor-pointer">
|
||||||
<a
|
<a
|
||||||
href={link.url}
|
href={link.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex flex-col items-center justify-center gap-2 rounded-md bg-muted/50 p-4 hover:bg-accent hover:text-accent-foreground text-center h-20 transition-colors"
|
className="flex flex-col items-center justify-center gap-2 rounded-md bg-muted/50 p-4 hover:bg-accent hover:text-accent-foreground text-center h-20 transition-colors"
|
||||||
>
|
>
|
||||||
<ExternalLink className="h-5 w-5 opacity-70" />
|
<ExternalLink className="h-5 w-5 opacity-70" />
|
||||||
<span className="text-xs font-medium line-clamp-2">{link.name}</span>
|
<span className="text-xs font-medium line-clamp-2">{link.name}</span>
|
||||||
</a>
|
</a>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
)}
|
)}
|
||||||
@ -279,15 +302,15 @@ export function TopNavbar({
|
|||||||
{/* AI智能精选 */}
|
{/* AI智能精选 */}
|
||||||
<a
|
<a
|
||||||
href="/featured"
|
href="/featured"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
className={navItemBaseClass}
|
||||||
title="AI智能精选"
|
title="AI精选"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = "/featured";
|
window.location.href = "/featured";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Sparkles className="h-4 w-4 text-yellow-400" />
|
<Sparkles className="h-5 w-5 text-yellow-400" />
|
||||||
<span>AI精选</span>
|
<span className={navLabelClass}>{getShortLabel("AI精选")}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{/* 功能教程 */}
|
{/* 功能教程 */}
|
||||||
@ -297,14 +320,15 @@ export function TopNavbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
className={`${navItemBaseClass} p-0`}
|
||||||
type="button"
|
type="button"
|
||||||
|
title="功能教程"
|
||||||
>
|
>
|
||||||
<PlayCircle className="h-4 w-4 text-green-400" />
|
<PlayCircle className="h-5 w-5 text-green-400" />
|
||||||
<span className="text-sm font-medium">功能教程</span>
|
<span className={navLabelClass}>{getShortLabel("功能教程")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[320px] p-2">
|
<DropdownMenuContent side="left" align="center" className="w-[320px] p-2">
|
||||||
<div className="space-y-1 max-h-[400px] overflow-y-auto">
|
<div className="space-y-1 max-h-[400px] overflow-y-auto">
|
||||||
{tutorialVideos
|
{tutorialVideos
|
||||||
.sort((a, b) => a.order - b.order)
|
.sort((a, b) => a.order - b.order)
|
||||||
@ -355,14 +379,15 @@ export function TopNavbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
className={`${navItemBaseClass} p-0`}
|
||||||
type="button"
|
type="button"
|
||||||
|
title="精选榜单"
|
||||||
>
|
>
|
||||||
<Trophy className="h-4 w-4 text-orange-400" />
|
<Trophy className="h-5 w-5 text-orange-400" />
|
||||||
<span className="text-sm font-medium">精选榜</span>
|
<span className={navLabelClass}>{getShortLabel("精选榜")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-[280px] p-3">
|
<DropdownMenuContent side="left" align="center" className="w-[280px] p-3">
|
||||||
<div className="flex items-center gap-2 mb-3 pb-2 border-b">
|
<div className="flex items-center gap-2 mb-3 pb-2 border-b">
|
||||||
<Trophy className="h-5 w-5 text-orange-500" />
|
<Trophy className="h-5 w-5 text-orange-500" />
|
||||||
<span className="font-semibold">精选榜单 TOP5</span>
|
<span className="font-semibold">精选榜单 TOP5</span>
|
||||||
@ -409,18 +434,18 @@ export function TopNavbar({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors"
|
className={`${navItemBaseClass} p-0`}
|
||||||
onClick={handleOpenImageEditor}
|
onClick={handleOpenImageEditor}
|
||||||
type="button"
|
type="button"
|
||||||
title="图片编辑"
|
title="图片编辑"
|
||||||
>
|
>
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-5 w-5" />
|
||||||
<span className="text-sm font-medium">图片编辑</span>
|
<span className={navLabelClass}>{getShortLabel("图片编辑")}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* 无限画布 */}
|
{/* 无限画布 */}
|
||||||
<button
|
<button
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
className={navItemBaseClass}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newId = crypto.randomUUID();
|
const newId = crypto.randomUUID();
|
||||||
window.location.href = `/canvas/${newId}`;
|
window.location.href = `/canvas/${newId}`;
|
||||||
@ -428,44 +453,42 @@ export function TopNavbar({
|
|||||||
type="button"
|
type="button"
|
||||||
title="无限画布"
|
title="无限画布"
|
||||||
>
|
>
|
||||||
<LayoutGrid className="h-4 w-4 text-purple-400" />
|
<LayoutGrid className="h-5 w-5 text-purple-400" />
|
||||||
<span>无限画布</span>
|
<span className={navLabelClass}>{getShortLabel("无限画布")}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* 我的图册 */}
|
{/* 我的图册 */}
|
||||||
<a
|
<a
|
||||||
href="/gallery"
|
href="/gallery"
|
||||||
className="flex items-center gap-1 h-8 px-3 text-white hover:bg-white/15 hover:text-white rounded-full transition-colors text-sm font-medium"
|
className={navItemBaseClass}
|
||||||
title="我的图册"
|
title="我的图册"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = "/gallery";
|
window.location.href = "/gallery";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Images className="h-4 w-4" />
|
<Images className="h-5 w-5" />
|
||||||
<span>我的图册</span>
|
<span className={navLabelClass}>{getShortLabel("我的图册")}</span>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* 分隔线 (如果有链接或图册按钮,且是管理员) */}
|
{/* 系统设置(仅管理员可见)*/}
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<div className="hidden h-4 w-px bg-white/20 mx-1 sm:block" />
|
<div className="pb-4">
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 系统设置(仅管理员可见)*/}
|
|
||||||
{isAdmin && (
|
|
||||||
<a
|
<a
|
||||||
href="/settings"
|
href="/settings"
|
||||||
className="flex items-center justify-center h-8 w-8 text-white hover:bg-white/15 hover:text-white rounded-full"
|
className={navItemBaseClass}
|
||||||
title="系统设置"
|
title="系统设置"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.location.href = "/settings";
|
window.location.href = "/settings";
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-5 w-5" />
|
||||||
|
<span className={navLabelClass}>{getShortLabel("系统设置")}</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{/* 图片编辑器 */}
|
{/* 图片编辑器 */}
|
||||||
{showImageEditor && (
|
{showImageEditor && (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user