feat(wavelet): 侧栏新增网站管理折叠组

使用 shadcn Collapsible 与 SidebarMenuSub 收纳网站、证书、DNS、源站入口。
This commit is contained in:
ryan
2026-06-18 19:48:30 +08:00
parent 8b3d220d73
commit 1208f7ee94
5 changed files with 193 additions and 37 deletions
@@ -0,0 +1,111 @@
'use client';
import Link from 'next/link';
import {usePathname} from 'next/navigation';
import {useEffect, useState} from 'react';
import {ChevronRight} from 'lucide-react';
import {Collapsible, CollapsibleContent, CollapsibleTrigger} from '@/components/ui/collapsible';
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
} from '@/components/ui/sidebar';
import {
matchesNavPath,
openflareNavItems,
openflareWebsiteNavGroup,
isNavGroupActive,
} from '@/lib/navigation/openflare-nav';
export function OpenFlareSidebarMenu({
onNavigate,
}: {
onNavigate?: () => void;
}) {
const pathname = usePathname();
const websiteGroupActive = isNavGroupActive(pathname, openflareWebsiteNavGroup);
const [websiteGroupOpen, setWebsiteGroupOpen] = useState(websiteGroupActive);
useEffect(() => {
if (websiteGroupActive) {
setWebsiteGroupOpen(true);
}
}, [websiteGroupActive]);
const renderFlatItemsBeforeGroup = openflareNavItems.slice(0, 4);
const renderFlatItemsAfterGroup = openflareNavItems.slice(4);
return (
<SidebarMenu className="gap-1">
{renderFlatItemsBeforeGroup.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
asChild
>
<Link href={item.url} onClick={onNavigate}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
<Collapsible
asChild
open={websiteGroupOpen}
onOpenChange={setWebsiteGroupOpen}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton
tooltip={openflareWebsiteNavGroup.title}
isActive={websiteGroupActive}
>
<openflareWebsiteNavGroup.icon />
<span>{openflareWebsiteNavGroup.title}</span>
<ChevronRight className="ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{openflareWebsiteNavGroup.items.map((item) => (
<SidebarMenuSubItem key={item.title}>
<SidebarMenuSubButton
asChild
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
>
<Link href={item.url} onClick={onNavigate}>
<span>{item.title}</span>
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
{renderFlatItemsAfterGroup.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={matchesNavPath(pathname, item.url, item.childUrls)}
asChild
>
<Link href={item.url} onClick={onNavigate}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
);
}
+2 -20
View File
@@ -63,6 +63,7 @@ import {
import {useUser} from "@/contexts/user-context"
import {usePublicConfig} from "@/hooks/use-public-config"
import {OpenFlareSidebarMenu} from "@/components/layout/openflare-sidebar-menu"
import {openflareNavItems} from "@/lib/navigation/openflare-nav"
/* 导航数据 */
@@ -297,26 +298,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
{openflareNavItems.length > 0 && (
<SidebarGroup className="py-0">
<SidebarGroupContent className="py-1">
<SidebarMenu className="gap-1">
{openflareNavItems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={
item.url === "/"
? pathname === "/"
: pathname === item.url || pathname.startsWith(`${item.url}/`)
}
asChild
>
<Link href={item.url} onClick={handleCloseSidebar}>
<item.icon />
<span>{item.title}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
<OpenFlareSidebarMenu onNavigate={handleCloseSidebar} />
</SidebarGroupContent>
</SidebarGroup>
)}
@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import {Collapsible as CollapsiblePrimitive} from "radix-ui"
function Collapsible({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
}
function CollapsibleTrigger({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Trigger>) {
return (
<CollapsiblePrimitive.Trigger data-slot="collapsible-trigger" {...props} />
)
}
function CollapsibleContent({
...props
}: React.ComponentProps<typeof CollapsiblePrimitive.Content>) {
return (
<CollapsiblePrimitive.Content data-slot="collapsible-content" {...props} />
)
}
export { Collapsible, CollapsibleTrigger, CollapsibleContent }