fumadocs-ui
Version:
The Radix UI version of Fumadocs UI
195 lines (194 loc) • 8.28 kB
JavaScript
"use client";
import { cn } from "../../../utils/cn.js";
import { buttonVariants } from "../../../components/ui/button.js";
import { Popover, PopoverContent, PopoverTrigger } from "../../../components/ui/popover.js";
import { LinkItem } from "../../shared/client.js";
import { isLayoutTabActive } from "../../shared/index.js";
import { useNotebookLayout } from "../client.js";
import Link from "fumadocs-core/link";
import { usePathname } from "fumadocs-core/framework";
import { jsx, jsxs } from "react/jsx-runtime";
import { ChevronDown, Languages, Sidebar } from "lucide-react";
import { Fragment as Fragment$1, useMemo, useRef, useState } from "react";
//#region src/layouts/notebook/slots/header.tsx
function Header(props) {
const { slots, navItems, isNavTransparent, props: { tabMode, nav, tabs, sidebar } } = useNotebookLayout();
const { open } = slots.sidebar?.useSidebar?.() ?? {};
const navMode = nav?.mode ?? "auto";
const sidebarCollapsible = sidebar.collapsible ?? true;
const showLayoutTabs = tabMode === "navbar" && tabs.length > 0;
if (nav?.component) return nav.component;
return /* @__PURE__ */ jsxs("header", {
id: "nd-subnav",
"data-transparent": isNavTransparent && !open,
...props,
className: cn("sticky [grid-area:header] flex flex-col top-(--fd-docs-row-1) z-10 backdrop-blur-sm transition-colors data-[transparent=false]:bg-fd-background/80 layout:[--fd-header-height:--spacing(14)]", showLayoutTabs && "lg:layout:[--fd-header-height:--spacing(24)]", props.className),
children: [/* @__PURE__ */ jsxs("div", {
"data-header-body": "",
className: "flex border-b px-4 gap-2 h-14 md:px-6",
children: [
/* @__PURE__ */ jsxs("div", {
className: cn("items-center", navMode === "top" && "flex flex-1", navMode === "auto" && "hidden has-data-[collapsed=true]:md:flex max-md:flex"),
children: [
sidebarCollapsible && slots.sidebar && navMode === "auto" && /* @__PURE__ */ jsx(slots.sidebar.collapseTrigger, {
className: cn(buttonVariants({
color: "ghost",
size: "icon-sm"
}), "-ms-1.5 text-fd-muted-foreground data-[collapsed=false]:hidden max-md:hidden"),
children: /* @__PURE__ */ jsx(Sidebar, {})
}),
slots.navTitle && /* @__PURE__ */ jsx(slots.navTitle, { className: cn("inline-flex items-center gap-2.5 font-semibold", navMode === "auto" && "md:hidden") }),
nav?.children
]
}),
slots.searchTrigger && /* @__PURE__ */ jsx(slots.searchTrigger.full, {
hideIfDisabled: true,
className: cn("w-full my-auto max-md:hidden", navMode === "top" ? "ps-2.5 rounded-xl max-w-sm" : "max-w-[240px]")
}),
/* @__PURE__ */ jsxs("div", {
className: "flex flex-1 items-center justify-end md:gap-2",
children: [
/* @__PURE__ */ jsx("div", {
className: "flex items-center gap-6 empty:hidden max-lg:hidden",
children: navItems.filter((item) => item.type !== "icon").map((item, i) => /* @__PURE__ */ jsx(NavbarLinkItem, { item }, i))
}),
navItems.filter((item) => item.type === "icon").map((item, i) => /* @__PURE__ */ jsx(LinkItem, {
item,
className: cn(buttonVariants({
size: "icon-sm",
color: "ghost"
}), "text-fd-muted-foreground max-lg:hidden"),
"aria-label": item.label,
children: item.icon
}, i)),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center md:hidden",
children: [slots.searchTrigger && /* @__PURE__ */ jsx(slots.searchTrigger.sm, {
hideIfDisabled: true,
className: "p-2"
}), slots.sidebar && /* @__PURE__ */ jsx(slots.sidebar.trigger, {
className: cn(buttonVariants({
color: "ghost",
size: "icon-sm",
className: "p-2 -me-1.5"
})),
children: /* @__PURE__ */ jsx(Sidebar, {})
})]
}),
/* @__PURE__ */ jsxs("div", {
className: "flex items-center gap-2 max-md:hidden",
children: [
slots.languageSelect && /* @__PURE__ */ jsx(slots.languageSelect.root, { children: /* @__PURE__ */ jsx(Languages, { className: "size-4.5 text-fd-muted-foreground" }) }),
slots.themeSwitch && /* @__PURE__ */ jsx(slots.themeSwitch, {}),
sidebarCollapsible && slots.sidebar && navMode === "top" && /* @__PURE__ */ jsx(slots.sidebar.collapseTrigger, {
className: cn(buttonVariants({
color: "secondary",
size: "icon-sm"
}), "text-fd-muted-foreground rounded-full -me-1.5"),
children: /* @__PURE__ */ jsx(Sidebar, {})
})
]
})
]
})
]
}), showLayoutTabs && /* @__PURE__ */ jsx(LayoutHeaderTabs, {
"data-header-tabs": "",
className: "overflow-x-auto border-b px-6 h-10 max-lg:hidden",
tabs
})]
});
}
function LayoutHeaderTabs({ tabs, className, ...props }) {
const pathname = usePathname();
const selectedIdx = useMemo(() => {
return tabs.findLastIndex((option) => isLayoutTabActive(option, pathname));
}, [tabs, pathname]);
return /* @__PURE__ */ jsx("div", {
className: cn("flex flex-row items-end gap-6", className),
...props,
children: tabs.map((option, i) => {
const { title, url, unlisted, props: { className, ...rest } = {} } = option;
const isSelected = selectedIdx === i;
return /* @__PURE__ */ jsx(Link, {
href: url,
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", unlisted && !isSelected && "hidden", isSelected && "border-fd-primary text-fd-primary", className),
...rest,
children: title
}, i);
})
});
}
function NavbarLinkItem({ item, className, ...props }) {
if (item.type === "custom") return item.children;
if (item.type === "menu") return /* @__PURE__ */ jsx(NavbarLinkItemMenu, {
item,
className,
...props
});
return /* @__PURE__ */ jsx(LinkItem, {
item,
className: cn("text-sm text-fd-muted-foreground transition-colors hover:text-fd-accent-foreground data-[active=true]:text-fd-primary", className),
...props,
children: item.text
});
}
function NavbarLinkItemMenu({ item, hoverDelay = 50, className, ...props }) {
const [open, setOpen] = useState(false);
const timeoutRef = useRef(null);
const freezeUntil = useRef(null);
const delaySetOpen = (value) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
timeoutRef.current = window.setTimeout(() => {
setOpen(value);
freezeUntil.current = Date.now() + 300;
}, hoverDelay);
};
const onPointerEnter = (e) => {
if (e.pointerType === "touch") return;
delaySetOpen(true);
};
const onPointerLeave = (e) => {
if (e.pointerType === "touch") return;
delaySetOpen(false);
};
function isTouchDevice() {
return "ontouchstart" in window || navigator.maxTouchPoints > 0;
}
return /* @__PURE__ */ jsxs(Popover, {
open,
onOpenChange: (value) => {
if (freezeUntil.current === null || Date.now() >= freezeUntil.current) setOpen(value);
},
children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
className: cn("inline-flex items-center gap-1.5 p-1 text-sm text-fd-muted-foreground transition-colors has-data-[active=true]:text-fd-primary data-[state=open]:text-fd-accent-foreground focus-visible:outline-none", className),
onPointerEnter,
onPointerLeave,
...props,
children: [item.url ? /* @__PURE__ */ jsx(LinkItem, {
item,
children: item.text
}) : item.text, /* @__PURE__ */ jsx(ChevronDown, { className: "size-3" })]
}), /* @__PURE__ */ jsx(PopoverContent, {
className: "flex flex-col p-1 text-fd-muted-foreground text-start",
onPointerEnter,
onPointerLeave,
children: item.items.map((child, i) => {
if (child.type === "custom") return /* @__PURE__ */ jsx(Fragment$1, { children: child.children }, i);
return /* @__PURE__ */ jsxs(LinkItem, {
item: child,
className: "inline-flex items-center gap-2 rounded-md p-2 transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground data-[active=true]:text-fd-primary [&_svg]:size-4",
onClick: () => {
if (isTouchDevice()) setOpen(false);
},
children: [child.icon, child.text]
}, i);
})
})]
});
}
//#endregion
export { Header };