Documentation Updates (#79)

* Docs: Added Shadcn sidebar; added algorithm to automatically infer parents without needing to specify it.
* Extracted Markdoc compilation in a separate file
* Site Navigation: simplify nav by removing the border
* Docs Navigation: added TOC; improved layout on mobile
This commit is contained in:
Giancarlo Buomprisco
2024-10-30 13:49:44 +01:00
committed by GitHub
parent 6490102e9f
commit 9615d1a4bb
20 changed files with 551 additions and 266 deletions

View File

@@ -0,0 +1,26 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { SidebarMenuButton, SidebarMenuItem } from '@kit/ui/shadcn-sidebar';
import { cn, isRouteActive } from '@kit/ui/utils';
export function DocsNavLink({ label, url }: { label: string; url: string }) {
const currentPath = usePathname();
const isCurrent = isRouteActive(url, currentPath, true);
return (
<SidebarMenuItem>
<SidebarMenuButton
asChild
isActive={isCurrent}
className={cn('border-l-3 transition-background !font-normal')}
>
<Link href={url}>
<span className="block max-w-full truncate">{label}</span>
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
);
}

View File

@@ -1,192 +1,68 @@
'use client';
import { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { Menu } from 'lucide-react';
import { Cms } from '@kit/cms';
import { isBrowser } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
import { cn, isRouteActive } from '@kit/ui/utils';
import {
Sidebar,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuSub,
} from '@kit/ui/shadcn-sidebar';
function DocsNavLink({
label,
url,
level,
activePath,
}: {
label: string;
url: string;
level: number;
activePath: string;
}) {
const isCurrent = isRouteActive(url, activePath, true);
const isFirstLevel = level === 0;
import { DocsNavLink } from '~/(marketing)/docs/_components/docs-nav-link';
return (
<Button
className={cn('w-full shadow-none', {
['font-normal']: !isFirstLevel,
})}
variant={isCurrent ? 'secondary' : 'ghost'}
>
<Link
className="flex h-full max-w-full grow items-center space-x-2"
href={url}
>
<span className="block max-w-full truncate">{label}</span>
</Link>
</Button>
);
}
import { FloatingDocumentationNavigation } from './floating-docs-navigation';
function Node({
node,
level,
activePath,
}: {
node: Cms.ContentItem;
level: number;
activePath: string;
}) {
function Node({ node, level }: { node: Cms.ContentItem; level: number }) {
const pathPrefix = `/docs`;
const url = `${pathPrefix}/${node.slug}`;
return (
<>
<DocsNavLink
label={node.title}
url={url}
level={level}
activePath={activePath}
/>
<DocsNavLink label={node.title} url={url} />
{(node.children ?? []).length > 0 && (
<Tree
pages={node.children ?? []}
level={level + 1}
activePath={activePath}
/>
<Tree pages={node.children ?? []} level={level + 1} />
)}
</>
);
}
function Tree({
pages,
level,
activePath,
}: {
pages: Cms.ContentItem[];
level: number;
activePath: string;
}) {
function Tree({ pages, level }: { pages: Cms.ContentItem[]; level: number }) {
if (level === 0) {
return pages.map((treeNode, index) => (
<SidebarGroup key={index}>
<SidebarGroupContent>
<SidebarMenu>
<Node key={index} node={treeNode} level={level} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
));
}
return (
<div
className={cn('w-full space-y-1', {
['pl-3']: level > 0,
})}
>
<SidebarMenuSub>
{pages.map((treeNode, index) => (
<Node
key={index}
node={treeNode}
level={level}
activePath={activePath}
/>
<Node key={index} node={treeNode} level={level} />
))}
</div>
</SidebarMenuSub>
);
}
export function DocsNavigation({ pages }: { pages: Cms.ContentItem[] }) {
const activePath = usePathname();
return (
<>
<aside
style={{
height: `calc(100vh - 64px)`,
}}
className="sticky top-2 hidden w-80 shrink-0 overflow-y-auto border-r p-4 lg:flex"
<Sidebar
variant={'ghost'}
className={'sticky'}
>
<Tree pages={pages} level={0} activePath={activePath} />
</aside>
<Tree pages={pages} level={0} />
</Sidebar>
<div className={'lg:hidden'}>
<FloatingDocumentationNavigation
pages={pages}
activePath={activePath}
/>
<FloatingDocumentationNavigation>
<Tree pages={pages} level={0} />
</FloatingDocumentationNavigation>
</div>
</>
);
}
function FloatingDocumentationNavigation({
pages,
activePath,
}: React.PropsWithChildren<{
pages: Cms.ContentItem[];
activePath: string;
}>) {
const body = useMemo(() => {
return isBrowser() ? document.body : null;
}, []);
const [isVisible, setIsVisible] = useState(false);
const enableScrolling = (element: HTMLElement) =>
(element.style.overflowY = '');
const disableScrolling = (element: HTMLElement) =>
(element.style.overflowY = 'hidden');
// enable/disable body scrolling when the docs are toggled
useEffect(() => {
if (!body) {
return;
}
if (isVisible) {
disableScrolling(body);
} else {
enableScrolling(body);
}
}, [isVisible, body]);
// hide docs when navigating to another page
useEffect(() => {
setIsVisible(false);
}, [activePath]);
const onClick = () => {
setIsVisible(!isVisible);
};
return (
<>
<If condition={isVisible}>
<div
className={
'fixed left-0 top-0 z-10 h-screen w-full p-4' +
' flex flex-col space-y-4 overflow-auto bg-white dark:bg-background'
}
>
<Tree pages={pages} level={0} activePath={activePath} />
</div>
</If>
<Button
className={'fixed bottom-5 right-5 z-10 h-16 w-16 rounded-full'}
onClick={onClick}
>
<Menu className={'h-8'} />
</Button>
</>
);
}

View File

@@ -0,0 +1,51 @@
'use client';
import Link from 'next/link';
interface NavItem {
text: string;
level: number;
href: string;
children: NavItem[];
}
export function DocsTableOfContents(props: { data: NavItem[] }) {
const navData = props.data;
return (
<div className="lg:block hidden sticky z-10 bg-background min-w-[14em] border-l p-4 inset-y-0 h-svh">
<ol
role="list"
className="relative text-sm text-gray-600 dark:text-gray-400"
>
{navData.map((item) => (
<li key={item.href} className="group/item relative mt-3 first:mt-0">
<a
href={item.href}
className="block transition-colors hover:text-gray-950 dark:hover:text-white [&_*]:[font:inherit]"
>
{item.text}
</a>
{item.children && (
<ol role="list" className="relative mt-3 pl-4">
{item.children.map((child) => (
<li
key={child.href}
className="group/subitem relative mt-3 first:mt-0"
>
<Link
href={child.href}
className="block transition-colors hover:text-gray-950 dark:hover:text-white [&_*]:[font:inherit]"
>
{child.text}
</Link>
</li>
))}
</ol>
)}
</li>
))}
</ol>
</div>
);
}

View File

@@ -0,0 +1,73 @@
'use client';
import { useEffect, useMemo, useState } from 'react';
import { usePathname } from 'next/navigation';
import { Menu } from 'lucide-react';
import { isBrowser } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
export function FloatingDocumentationNavigation(
props: React.PropsWithChildren,
) {
const activePath = usePathname();
const body = useMemo(() => {
return isBrowser() ? document.body : null;
}, []);
const [isVisible, setIsVisible] = useState(false);
const enableScrolling = (element: HTMLElement) =>
(element.style.overflowY = '');
const disableScrolling = (element: HTMLElement) =>
(element.style.overflowY = 'hidden');
// enable/disable body scrolling when the docs are toggled
useEffect(() => {
if (!body) {
return;
}
if (isVisible) {
disableScrolling(body);
} else {
enableScrolling(body);
}
}, [isVisible, body]);
// hide docs when navigating to another page
useEffect(() => {
setIsVisible(false);
}, [activePath]);
const onClick = () => {
setIsVisible(!isVisible);
};
return (
<>
<If condition={isVisible}>
<div
className={
'fixed left-0 top-0 z-10 h-screen w-full p-4' +
' flex flex-col space-y-4 overflow-auto bg-white dark:bg-background'
}
>
{props.children}
</div>
</If>
<Button
className={'fixed bottom-5 right-5 z-10 h-16 w-16 rounded-full'}
onClick={onClick}
>
<Menu className={'h-8'} />
</Button>
</>
);
}