Skip to content

Commit

Permalink
UI: Expose --fd-tocnav-height CSS variable
Browse files Browse the repository at this point in the history
  • Loading branch information
fuma-nama committed Dec 14, 2024
1 parent eb74b66 commit 1a2597a
Show file tree
Hide file tree
Showing 17 changed files with 350 additions and 280 deletions.
6 changes: 6 additions & 0 deletions .changeset/soft-sloths-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'fumadocs-openapi': patch
'fumadocs-ui': patch
---

Expose `--fd-tocnav-height` CSS variable
4 changes: 2 additions & 2 deletions packages/openapi/src/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,13 @@ export function API({ children, ...props }: HTMLAttributes<HTMLDivElement>) {
<div
{...props}
className={cn(
'flex flex-col gap-x-6 gap-y-4 max-xl:[--fd-toc-height:46px] max-md:[--fd-toc-height:36px] xl:flex-row xl:items-start',
'flex flex-col gap-x-6 gap-y-4 xl:flex-row xl:items-start',
props.className,
)}
style={
{
'--fd-api-info-top':
'calc(var(--fd-nav-height) + var(--fd-banner-height) + var(--fd-toc-height, 0.5rem))',
'calc(var(--fd-nav-height) + var(--fd-banner-height) + var(--fd-tocnav-height, 0px))',
...props.style,
} as object
}
Expand Down
12 changes: 9 additions & 3 deletions packages/ui/src/components/layout/toc.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
PopoverTriggerProps,
} from '@radix-ui/react-popover';
import { ChevronRight, Text } from 'lucide-react';
import { usePageStyles } from '@/contexts/layout';

export interface TOCProps {
/**
Expand All @@ -32,12 +33,15 @@ export interface TOCProps {
}

export function Toc(props: HTMLAttributes<HTMLDivElement>) {
const { toc } = usePageStyles();

return (
<div
id="nd-toc"
{...props}
data-toc=""
className={cn(
'sticky top-fd-layout-top h-[var(--fd-toc-height)] flex-1 pb-2 pt-12',
'sticky top-fd-layout-top h-[var(--fd-toc-height)] pb-2 pt-12',
toc,
props.className,
)}
style={
Expand All @@ -48,7 +52,9 @@ export function Toc(props: HTMLAttributes<HTMLDivElement>) {
} as object
}
>
{props.children}
<div className="flex h-full w-[var(--fd-toc-width)] max-w-full flex-col gap-3 pe-2">
{props.children}
</div>
</div>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const contextsMap = {
'../contexts/search.tsx': 'fumadocs-ui/provider',
'../contexts/tree.tsx': 'fumadocs-ui/provider',
'../contexts/i18n.tsx': 'fumadocs-ui/provider',
'../contexts/layout.tsx': 'fumadocs-ui/provider',
};

export const registry: Registry = {
Expand Down
30 changes: 30 additions & 0 deletions packages/ui/src/contexts/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client';
import { createContext, type ReactNode, useContext } from 'react';

export interface PageStyles {
tocNav?: string;
toc?: string;
page?: string;
article?: string;
}

/**
* applied styles to different layout components in `Page` from layouts
*/
const StylesContext = createContext<PageStyles>({
tocNav: 'xl:hidden',
toc: 'max-xl:hidden',
});

export function usePageStyles() {
return useContext(StylesContext);
}

export function StylesProvider({
children,
...value
}: PageStyles & { children: ReactNode }) {
return (
<StylesContext.Provider value={value}>{children}</StylesContext.Provider>
);
}
4 changes: 2 additions & 2 deletions packages/ui/src/contexts/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
useState,
useMemo,
useRef,
type MutableRefObject,
type ReactNode,
type RefObject,
} from 'react';
import { usePathname } from 'next/navigation';
import { SidebarProvider as BaseProvider } from 'fumadocs-core/sidebar';
Expand All @@ -20,7 +20,7 @@ interface SidebarContext {
/**
* When set to false, don't close the sidebar when navigate to another page
*/
closeOnRedirect: MutableRefObject<boolean>;
closeOnRedirect: RefObject<boolean>;
}

const SidebarContext = createContext<SidebarContext | undefined>(undefined);
Expand Down
12 changes: 2 additions & 10 deletions packages/ui/src/contexts/tree.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
'use client';
import type { PageTree } from 'fumadocs-core/server';
import { usePathname } from 'next/navigation';
import {
createContext,
useContext,
type ReactNode,
useMemo,
useRef,
} from 'react';
import { createContext, useContext, type ReactNode, useMemo } from 'react';
import { searchPath } from 'fumadocs-core/breadcrumb';

interface TreeContextType {
root: PageTree.Root | PageTree.Folder;
}

const TreeContext = createContext<TreeContextType | undefined>(undefined);
const TreeContext = createContext<TreeContextType | null>(null);
const PathContext = createContext<PageTree.Node[]>([]);

export function TreeContextProvider({
Expand All @@ -32,8 +26,6 @@ export function TreeContextProvider({

const root = (path.findLast((item) => item.type === 'folder' && item.root) ??
tree) as PageTree.Root;
const pathnameRef = useRef(pathname);
pathnameRef.current = pathname;

return (
<TreeContext.Provider value={useMemo(() => ({ root }), [root])}>
Expand Down
63 changes: 44 additions & 19 deletions packages/ui/src/layouts/docs.client.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { ChevronDown } from 'lucide-react';
import { ChevronDown, Menu, X } from 'lucide-react';
import {
type ButtonHTMLAttributes,
type HTMLAttributes,
Expand All @@ -23,23 +23,8 @@ import {
import { cva } from 'class-variance-authority';
import { buttonVariants } from '@/components/ui/button';
import { useSidebar } from '@/contexts/sidebar';

export function LayoutBody(props: HTMLAttributes<HTMLElement>) {
const { collapsed } = useSidebar();

return (
<main
{...props}
className={cn(
!collapsed &&
'[&_#nd-page]:max-w-[calc(min(100vw,var(--fd-layout-width))-var(--fd-sidebar-width)-var(--fd-toc-width))]',
props.className,
)}
>
{props.children}
</main>
);
}
import { useNav } from '@/components/layout/nav';
import { SidebarTrigger } from 'fumadocs-core/sidebar';

const itemVariants = cva(
'flex flex-row items-center gap-2 rounded-md px-3 py-2.5 text-fd-muted-foreground transition-colors duration-100 [overflow-wrap:anywhere] hover:bg-fd-accent/50 hover:text-fd-accent-foreground/80 hover:transition-none md:px-2 md:py-1.5 [&_svg]:size-4',
Expand Down Expand Up @@ -69,11 +54,51 @@ export function LinksMenu({ items, ...props }: LinksMenuProps) {
);
}

export function Navbar(props: HTMLAttributes<HTMLElement>) {
const { open } = useSidebar();
const { isTransparent } = useNav();

return (
<header
id="nd-subnav"
{...props}
className={cn(
'sticky top-[var(--fd-banner-height)] z-30 flex h-14 flex-row items-center border-b border-fd-foreground/10 px-4 backdrop-blur-lg transition-colors',
(!isTransparent || open) && 'bg-fd-background/80',
props.className,
)}
>
{props.children}
</header>
);
}

export function NavbarSidebarTrigger(
props: ButtonHTMLAttributes<HTMLButtonElement>,
) {
const { open } = useSidebar();

return (
<SidebarTrigger
{...props}
className={cn(
buttonVariants({
color: 'ghost',
size: 'icon',
}),
props.className,
)}
>
{open ? <X /> : <Menu />}
</SidebarTrigger>
);
}

interface MenuItemProps extends HTMLAttributes<HTMLElement> {
item: LinkItemType;
}

export function MenuItem({ item, ...props }: MenuItemProps) {
function MenuItem({ item, ...props }: MenuItemProps) {
if (item.type === 'custom')
return (
<div {...props} className={cn('grid', props.className)}>
Expand Down
39 changes: 28 additions & 11 deletions packages/ui/src/layouts/docs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,22 @@ import {
LanguageToggle,
LanguageToggleText,
} from '@/components/layout/language-toggle';
import { LayoutBody, LinksMenu } from '@/layouts/docs.client';
import { LinksMenu, Navbar, NavbarSidebarTrigger } from '@/layouts/docs.client';
import { TreeContextProvider } from '@/contexts/tree';
import { NavProvider, Title } from '@/components/layout/nav';
import { ThemeToggle } from '@/components/layout/theme-toggle';
import { Navbar, NavbarSidebarTrigger } from '@/layouts/docs/navbar';
import {
LargeSearchToggle,
SearchToggle,
} from '@/components/layout/search-toggle';
import { SearchOnly } from '@/contexts/search';
import {
getSidebarTabsFromOptions,
layoutVariables,
SidebarLinkItem,
type SidebarOptions,
} from '@/layouts/docs/shared';
import { type PageStyles, StylesProvider } from '@/contexts/layout';

export interface DocsLayoutProps extends BaseLayoutProps {
tree: PageTree.Root;
Expand Down Expand Up @@ -76,13 +77,24 @@ export function DocsLayout({
if (props.tree === undefined) notFound();

const tabs = getSidebarTabsFromOptions(tabOptions, props.tree) ?? [];
const variables = cn(
'[--fd-tocnav-height:36px] md:[--fd-sidebar-width:260px] xl:[--fd-toc-width:260px] xl:[--fd-tocnav-height:0px]',
!navReplace && navEnabled
? '[--fd-nav-height:3.5rem] md:[--fd-nav-height:0px]'
: undefined,
);

const pageStyles: PageStyles = {
tocNav: cn('xl:hidden'),
toc: cn('max-xl:hidden'),
};

return (
<TreeContextProvider tree={props.tree}>
<NavProvider transparentMode={transparentMode}>
{replaceOrDefault(
{ enabled: navEnabled, component: navReplace },
<Navbar id="nd-subnav" className="h-14 md:hidden">
<Navbar className="md:hidden">
<Title url={nav.url} title={nav.title} />
<div className="flex flex-1 flex-row items-center gap-1">
{nav.children}
Expand All @@ -94,16 +106,18 @@ export function DocsLayout({
</Navbar>,
nav,
)}
<LayoutBody
<main
id="nd-docs-layout"
{...props.containerProps}
className={cn(
'flex flex-1 flex-row md:[--fd-sidebar-width:260px] xl:[--fd-toc-width:260px] [&_#nd-toc]:max-xl:hidden [&_#nd-tocnav]:xl:hidden',
!navReplace && navEnabled
? '[--fd-nav-height:3.5rem] md:[--fd-nav-height:0px]'
: null,
'flex flex-1 flex-row pe-[var(--fd-layout-offset)]',
variables,
props.containerProps?.className,
)}
style={{
...layoutVariables,
...props.containerProps?.style,
}}
>
{collapsible ? (
<SidebarCollapseTrigger className="fixed bottom-3 start-2 z-40 transition-opacity data-[collapsed=false]:pointer-events-none data-[collapsed=false]:opacity-0 max-md:hidden" />
Expand All @@ -112,7 +126,10 @@ export function DocsLayout({
{ enabled: sidebarEnabled, component: sidebarReplace },
<Aside
{...sidebar}
className="md:flex-1 md:data-[collapsed=true]:flex-initial"
className={cn(
'md:ps-[var(--fd-layout-offset)]',
sidebar.className,
)}
>
<SidebarHeader>
<SidebarHeaderItems {...nav} links={links} />
Expand Down Expand Up @@ -151,8 +168,8 @@ export function DocsLayout({
tabs,
},
)}
{props.children}
</LayoutBody>
<StylesProvider {...pageStyles}>{props.children}</StylesProvider>
</main>
</NavProvider>
</TreeContextProvider>
);
Expand Down
47 changes: 0 additions & 47 deletions packages/ui/src/layouts/docs/navbar.tsx

This file was deleted.

4 changes: 4 additions & 0 deletions packages/ui/src/layouts/docs/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import { getSidebarTabs, type TabOptions } from '@/utils/get-sidebar-tabs';
import type { FC, ReactNode } from 'react';
import type { Option } from '@/components/layout/root-toggle';

export const layoutVariables = {
'--fd-layout-offset': 'max(calc(50vw - var(--fd-layout-width) / 2), 0px)',
};

export interface SidebarOptions extends SidebarProps {
enabled: boolean;
component: ReactNode;
Expand Down
Loading

0 comments on commit 1a2597a

Please sign in to comment.