mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
UI: unify sidebar tabs matching
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-ui': patch
|
||||
---
|
||||
|
||||
unify sidebar tabs matching
|
||||
@@ -4,26 +4,12 @@ import { type ComponentProps, type ReactNode, useMemo, useState } from 'react';
|
||||
import Link from 'fumadocs-core/link';
|
||||
import { usePathname } from 'fumadocs-core/framework';
|
||||
import { cn } from '@/utils/cn';
|
||||
import { isActive } from '@/utils/is-active';
|
||||
import { isTabActive } from '@/utils/is-active';
|
||||
import { useSidebar } from '@/contexts/sidebar';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import type { SidebarTab } from '@/utils/get-sidebar-tabs';
|
||||
|
||||
export interface Option {
|
||||
/**
|
||||
* Redirect URL of the folder, usually the index page
|
||||
*/
|
||||
url: string;
|
||||
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
unlisted?: boolean;
|
||||
|
||||
/**
|
||||
* Detect from a list of urls
|
||||
*/
|
||||
urls?: Set<string>;
|
||||
|
||||
export interface Option extends SidebarTab {
|
||||
props?: ComponentProps<'a'>;
|
||||
}
|
||||
|
||||
@@ -40,13 +26,7 @@ export function RootToggle({
|
||||
const pathname = usePathname();
|
||||
|
||||
const selected = useMemo(() => {
|
||||
const lookup = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
|
||||
return options.findLast((item) => {
|
||||
if (item.urls) return item.urls.has(lookup);
|
||||
|
||||
return isActive(item.url, pathname, true);
|
||||
});
|
||||
return options.findLast((item) => isTabActive(item, pathname));
|
||||
}, [options, pathname]);
|
||||
|
||||
const onClick = () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { buttonVariants } from '@/components/ui/button';
|
||||
import { Sidebar as SidebarIcon } from 'lucide-react';
|
||||
import Link from 'fumadocs-core/link';
|
||||
import { usePathname } from 'fumadocs-core/framework';
|
||||
import { isActive } from '@/utils/is-active';
|
||||
import { isTabActive } from '@/utils/is-active';
|
||||
import type { Option } from '@/components/layout/root-toggle';
|
||||
|
||||
export function Navbar({
|
||||
@@ -90,15 +90,7 @@ export function LayoutTabs({
|
||||
}) {
|
||||
const pathname = usePathname();
|
||||
const selected = useMemo(() => {
|
||||
const url = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||
|
||||
return options.findLast((option) => {
|
||||
if (option.urls) {
|
||||
return option.urls.has(url);
|
||||
}
|
||||
|
||||
return isActive(option.url, pathname, true);
|
||||
});
|
||||
return options.findLast((option) => isTabActive(option, pathname));
|
||||
}, [options, pathname]);
|
||||
|
||||
return (
|
||||
@@ -121,7 +113,7 @@ export function LayoutTabs({
|
||||
}
|
||||
|
||||
function LayoutTab({
|
||||
option,
|
||||
option: { title, url, unlisted, props },
|
||||
selected = false,
|
||||
}: {
|
||||
option: Option;
|
||||
@@ -129,14 +121,16 @@ function LayoutTab({
|
||||
}) {
|
||||
return (
|
||||
<Link
|
||||
href={url}
|
||||
{...props}
|
||||
className={cn(
|
||||
'inline-flex border-b-2 border-transparent transition-colors items-center pb-1.5 font-medium gap-2 text-fd-muted-foreground text-sm text-nowrap hover:text-fd-accent-foreground',
|
||||
option.unlisted && !selected && 'hidden',
|
||||
unlisted && !selected && 'hidden',
|
||||
selected && 'border-fd-primary text-fd-primary',
|
||||
props?.className,
|
||||
)}
|
||||
href={option.url}
|
||||
>
|
||||
{option.title}
|
||||
{title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,25 @@
|
||||
import type { PageTree } from 'fumadocs-core/server';
|
||||
import type { Option } from '@/components/layout/root-toggle';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export interface SidebarTab {
|
||||
/**
|
||||
* Redirect URL of the folder, usually the index page
|
||||
*/
|
||||
url: string;
|
||||
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
description?: ReactNode;
|
||||
|
||||
/**
|
||||
* Detect from a list of urls
|
||||
*/
|
||||
urls?: Set<string>;
|
||||
unlisted?: boolean;
|
||||
}
|
||||
|
||||
export interface GetSidebarTabsOptions {
|
||||
transform?: (option: Option, node: PageTree.Folder) => Option | null;
|
||||
transform?: (option: SidebarTab, node: PageTree.Folder) => SidebarTab | null;
|
||||
}
|
||||
|
||||
const defaultTransform: GetSidebarTabsOptions['transform'] = (option, node) => {
|
||||
@@ -21,18 +38,18 @@ const defaultTransform: GetSidebarTabsOptions['transform'] = (option, node) => {
|
||||
export function getSidebarTabs(
|
||||
tree: PageTree.Root,
|
||||
{ transform = defaultTransform }: GetSidebarTabsOptions = {},
|
||||
): Option[] {
|
||||
function findOptions(
|
||||
): SidebarTab[] {
|
||||
const results: SidebarTab[] = [];
|
||||
|
||||
function scanOptions(
|
||||
node: PageTree.Root | PageTree.Folder,
|
||||
unlisted?: boolean,
|
||||
): Option[] {
|
||||
const results: Option[] = [];
|
||||
|
||||
) {
|
||||
if ('root' in node && node.root) {
|
||||
const urls = getFolderUrls(node);
|
||||
|
||||
if (urls.size > 0) {
|
||||
const option: Option = {
|
||||
const option: SidebarTab = {
|
||||
url: urls.values().next().value ?? '',
|
||||
title: node.name,
|
||||
icon: node.icon,
|
||||
@@ -47,19 +64,14 @@ export function getSidebarTabs(
|
||||
}
|
||||
|
||||
for (const child of node.children) {
|
||||
if (child.type === 'folder')
|
||||
results.push(...findOptions(child, unlisted));
|
||||
if (child.type === 'folder') scanOptions(child, unlisted);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
const options = findOptions(tree);
|
||||
if (tree.fallback) {
|
||||
options.push(...findOptions(tree.fallback, true));
|
||||
}
|
||||
scanOptions(tree);
|
||||
if (tree.fallback) scanOptions(tree.fallback, true);
|
||||
|
||||
return options;
|
||||
return results;
|
||||
}
|
||||
|
||||
function getFolderUrls(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { SidebarTab } from '@/utils/get-sidebar-tabs';
|
||||
|
||||
function normalize(url: string) {
|
||||
if (url.length > 1 && url.endsWith('/')) return url.slice(0, -1);
|
||||
return url;
|
||||
@@ -13,3 +15,9 @@ export function isActive(
|
||||
|
||||
return url === pathname || (nested && pathname.startsWith(`${url}/`));
|
||||
}
|
||||
|
||||
export function isTabActive(tab: SidebarTab, pathname: string) {
|
||||
if (tab.urls) return tab.urls.has(normalize(pathname));
|
||||
|
||||
return isActive(tab.url, pathname, true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user