Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { NextConfig } from 'next'
import createMDX from '@next/mdx'
import rehypeSlug from 'rehype-slug'

const nextConfig: NextConfig = {
/* config options here */
Expand All @@ -8,6 +9,9 @@ const nextConfig: NextConfig = {

const withMDX = createMDX({
extension : /\.mdx?$/,
options: {
rehypePlugins: [rehypeSlug],
},
})

export default withMDX(nextConfig)
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"radix-ui": "^1.4.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"rehype-slug": "^6.0.0",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
Expand Down
6 changes: 5 additions & 1 deletion src/components/docs-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { DocsSidebar } from '@/components/docs-sidebar'
import { SiteHeader } from '@/components/site-header'
import { TableOfContents } from '@/components/toc'

interface DocsLayoutProps {
children: React.ReactNode
Expand All @@ -12,11 +13,14 @@ export function DocsLayout({ children }: DocsLayoutProps) {
<div className='flex min-h-screen flex-col'>
<SiteHeader />
<div className='container-wrapper flex-1'>
<div className='container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-10 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-12'>
<div className='container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10 xl:grid-cols-[240px_minmax(0,1fr)_200px]'>
<DocsSidebar />
<main className='relative py-8 lg:py-12'>
<div className='mx-auto w-full max-w-4xl min-w-0'>{children}</div>
</main>
<aside className='hidden text-sm xl:block sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto pt-12 pb-8 pl-4 pr-6 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden'>
<TableOfContents />
</aside>
</div>
</div>
</div>
Expand Down
106 changes: 82 additions & 24 deletions src/components/docs-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
'use client'

import * as React from 'react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { ChevronRight } from 'lucide-react'
import { docsConfig, type NavItemWithChildren } from '@/config/docs'
import { cn } from '@/lib/utils'

export function DocsSidebar() {
const pathname = usePathname()

return (
<aside className='border-border/40 fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto border-r md:sticky md:block'>
<aside className='border-border/40 fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 overflow-y-auto border-r md:sticky md:block [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden'>
<div className='py-6 pr-6 lg:py-8'>
<div className='w-full'>
{docsConfig.sidebarNav.map((section, index) => (
Expand All @@ -35,46 +37,102 @@ interface DocsSidebarNavItemsProps {

function DocsSidebarNavItems({ items, pathname }: DocsSidebarNavItemsProps) {
return items.length ? (
<div className='flex flex-col space-y-1 border-l border-zinc-200 ml-3 pl-2 text-sm dark:border-zinc-800'>
{items.map((item, index) =>
item.href ? (
<div className='flex flex-col space-y-1 text-sm'>
{items.map((item, index) => (
<SidebarNavItem key={index} item={item} pathname={pathname} />
))}
</div>
) : null
}

function SidebarNavItem({ item, pathname }: { item: NavItemWithChildren; pathname: string | null }) {
const hasChildren = item.items && item.items.length > 0

// Recursively check if a child is active
const isChildActive = React.useMemo(() => {
const checkActive = (navItem: NavItemWithChildren): boolean => {
if (navItem.href && pathname?.startsWith(navItem.href) && navItem.href !== '/') return true
if (navItem.items && navItem.items.length > 0) {
return navItem.items.some(checkActive)
}
return false
}
return checkActive(item)
}, [item, pathname])

const [isOpen, setIsOpen] = React.useState(isChildActive || item.defaultOpen)
const isActive = pathname === item.href

return (
<div className="flex flex-col space-y-1 relative">
<div className="group relative flex w-full items-center">
{item.href ? (
<Link
key={index}
href={item.href}
className={cn(
'group flex w-full items-center rounded-md border border-transparent px-2 py-1.5 transition-colors',
'flex w-full items-center rounded-md border border-transparent px-2 py-1.5 pr-8 transition-colors',
item.disabled && 'cursor-not-allowed opacity-60',
pathname === item.href
isActive
? 'bg-zinc-100 font-medium text-zinc-900 dark:bg-zinc-800/50 dark:text-zinc-50'
: 'text-zinc-600 hover:bg-zinc-100 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800/50 dark:hover:text-zinc-50',
)}
target={item.external ? '_blank' : ''}
rel={item.external ? 'noreferrer' : ''}
>
{item.title}
{item.label ? (
<span className='ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline'>
{item.label}
</span>
) : null}
<span className="flex items-center">
{item.title}
{item.label ? (
<span className='ml-2 rounded-md bg-[#adfa1d] px-1.5 py-0.5 text-xs leading-none text-[#000000] no-underline group-hover:no-underline'>
{item.label}
</span>
) : null}
</span>
</Link>
) : (
<span
key={index}
className={cn(
'flex w-full cursor-not-allowed items-center rounded-md px-2 py-1.5 text-zinc-600 hover:underline dark:text-zinc-400',
item.disabled && 'cursor-not-allowed opacity-60',
'flex w-full items-center rounded-md px-2 py-1.5 pr-8 text-zinc-600 transition-colors dark:text-zinc-400',
item.disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800/50 dark:hover:text-zinc-50',
)}
onClick={!item.disabled && hasChildren ? () => setIsOpen(!isOpen) : undefined}
>
{item.title}
{item.label ? (
<span className='ml-2 rounded-md bg-muted px-1.5 py-0.5 text-xs leading-none text-muted-foreground no-underline group-hover:no-underline'>
{item.label}
</span>
) : null}
<span className="flex items-center">
{item.title}
{item.label ? (
<span className='ml-2 rounded-md bg-muted px-1.5 py-0.5 text-xs leading-none text-muted-foreground no-underline group-hover:no-underline'>
{item.label}
</span>
) : null}
</span>
</span>
),
)}

{hasChildren && (
<button
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setIsOpen(!isOpen)
}}
className="absolute right-1 top-1 p-1 rounded-sm text-zinc-400 hover:bg-zinc-200 hover:text-zinc-900 dark:text-zinc-500 dark:hover:bg-zinc-700 dark:hover:text-zinc-100"
>
<ChevronRight
className={cn(
"h-4 w-4 shrink-0 transition-transform duration-200",
isOpen && "rotate-90"
)}
/>
</button>
)}
</div>

{hasChildren && isOpen && (
<div className="ml-3 border-l border-zinc-200 pl-2 dark:border-zinc-800 mt-1 flex flex-col space-y-1">
{item.items.map((child, index) => (
<SidebarNavItem key={index} item={child} pathname={pathname} />
))}
</div>
)}
</div>
) : null
)
}
Loading
Loading