refactor(wavelet): 侧栏导航改为 openflareSidebarNav 动态配置

用 kind: item | group 的单一数组描述侧栏顺序,移除 slice(0, 4) 硬编码切分。
This commit is contained in:
ryan
2026-06-18 19:54:08 +08:00
parent 29176da35f
commit b56b4c93b6
4 changed files with 113 additions and 92 deletions
@@ -14,98 +14,103 @@ import {
SidebarMenuSubButton,
SidebarMenuSubItem,
} from '@/components/ui/sidebar';
import type {OpenFlareNavGroup} from '@/lib/navigation/openflare-nav';
import {
matchesNavPath,
openflareNavItems,
openflareWebsiteNavGroup,
openflareSidebarNav,
isNavGroupActive,
} from '@/lib/navigation/openflare-nav';
function SidebarNavGroupMenuItem({
group,
pathname,
onNavigate,
}: {
group: OpenFlareNavGroup;
pathname: string;
onNavigate?: () => void;
}) {
const groupActive = isNavGroupActive(pathname, group);
const [open, setOpen] = useState(groupActive);
useEffect(() => {
if (groupActive) {
setOpen(true);
}
}, [groupActive]);
return (
<Collapsible
asChild
open={open}
onOpenChange={setOpen}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip={group.title} isActive={groupActive}>
<group.icon />
<span>{group.title}</span>
<ChevronRight className="ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
{group.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>
);
}
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>
))}
{openflareSidebarNav.map((entry) => {
if (entry.kind === 'group') {
return (
<SidebarNavGroupMenuItem
key={entry.title}
group={entry}
pathname={pathname}
onNavigate={onNavigate}
/>
);
}
<Collapsible
asChild
open={websiteGroupOpen}
onOpenChange={setWebsiteGroupOpen}
className="group/collapsible"
>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
return (
<SidebarMenuItem key={entry.title}>
<SidebarMenuButton
tooltip={openflareWebsiteNavGroup.title}
isActive={websiteGroupActive}
tooltip={entry.title}
isActive={matchesNavPath(pathname, entry.url, entry.childUrls)}
asChild
>
<openflareWebsiteNavGroup.icon />
<span>{openflareWebsiteNavGroup.title}</span>
<ChevronRight className="ml-auto size-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
<Link href={entry.url} onClick={onNavigate}>
<entry.icon />
<span>{entry.title}</span>
</Link>
</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>
))}
</SidebarMenuItem>
);
})}
</SidebarMenu>
);
}
@@ -64,7 +64,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"
import {openflareSidebarNav} from "@/lib/navigation/openflare-nav"
/* 导航数据 */
const data = {
@@ -295,7 +295,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</DropdownMenu>
</SidebarHeader>
<SidebarContent className="group-data-[collapsible=icon]">
{openflareNavItems.length > 0 && (
{openflareSidebarNav.length > 0 && (
<SidebarGroup className="py-0">
<SidebarGroupContent className="py-1">
<OpenFlareSidebarMenu onNavigate={handleCloseSidebar} />