Skip to content

Commit 9a5ac54

Browse files
committed
Better crumbs
1 parent 8f69047 commit 9a5ac54

File tree

7 files changed

+147
-66
lines changed

7 files changed

+147
-66
lines changed

src/components/Breadcrumbs.tsx

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as React from 'react'
2+
import { Link } from '@tanstack/react-router'
3+
import { ChevronRight, ChevronDown } from 'lucide-react'
4+
import { twMerge } from 'tailwind-merge'
5+
import type { MarkdownHeading } from '~/utils/markdown/processor'
6+
7+
type BreadcrumbsProps = {
8+
/** Section label (e.g., "Getting Started", "Blog") */
9+
section: string
10+
/** Optional link for the section */
11+
sectionTo?: string
12+
headings?: MarkdownHeading[]
13+
/** Breakpoint at which the TOC toggle is hidden (default: 'lg') */
14+
tocHiddenBreakpoint?: 'md' | 'lg'
15+
}
16+
17+
export function Breadcrumbs({
18+
section,
19+
sectionTo,
20+
headings,
21+
tocHiddenBreakpoint = 'lg',
22+
}: BreadcrumbsProps) {
23+
const [isTocOpen, setIsTocOpen] = React.useState(false)
24+
25+
const showTocToggle = headings && headings.length > 1
26+
const hiddenClass = tocHiddenBreakpoint === 'md' ? 'md:hidden' : 'lg:hidden'
27+
28+
return (
29+
<div className="flex flex-col">
30+
<div className="flex items-center justify-between gap-4 text-sm text-gray-500 dark:text-gray-400">
31+
{sectionTo ? (
32+
<Link
33+
to={sectionTo}
34+
className="whitespace-nowrap hover:text-gray-700 dark:hover:text-gray-200 transition-colors"
35+
>
36+
{section}
37+
</Link>
38+
) : (
39+
<span className="whitespace-nowrap">{section}</span>
40+
)}
41+
{showTocToggle && (
42+
<button
43+
onClick={() => setIsTocOpen(!isTocOpen)}
44+
className={twMerge(
45+
hiddenClass,
46+
'whitespace-nowrap inline-flex items-center gap-1 text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors',
47+
)}
48+
aria-expanded={isTocOpen}
49+
>
50+
<span>On this page</span>
51+
<ChevronDown
52+
className={twMerge(
53+
'w-3.5 h-3.5 transition-transform duration-200',
54+
isTocOpen && 'rotate-180',
55+
)}
56+
/>
57+
</button>
58+
)}
59+
</div>
60+
{showTocToggle && (
61+
<div
62+
className={twMerge(
63+
hiddenClass,
64+
'grid transition-[grid-template-rows] duration-200 ease-out',
65+
isTocOpen ? 'grid-rows-[1fr]' : 'grid-rows-[0fr]',
66+
)}
67+
>
68+
<div className="overflow-hidden">
69+
<ul className="pt-3 pb-1 flex flex-col gap-px">
70+
{headings.map((heading) => (
71+
<li
72+
key={`breadcrumb-toc-${heading.id}`}
73+
style={{
74+
paddingLeft: `${(heading.level - 2) * 0.5}rem`,
75+
}}
76+
>
77+
<Link
78+
to="."
79+
hash={`#${heading.id}`}
80+
className="block py-1 pl-2 border-l-2 border-gray-200 dark:border-gray-700 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-400 dark:hover:border-gray-500 transition-colors"
81+
onClick={() => setIsTocOpen(false)}
82+
resetScroll={false}
83+
hashScrollIntoView={{
84+
behavior: 'smooth',
85+
}}
86+
>
87+
<span dangerouslySetInnerHTML={{ __html: heading.text }} />
88+
</Link>
89+
</li>
90+
))}
91+
</ul>
92+
</div>
93+
</div>
94+
)}
95+
</div>
96+
)
97+
}

src/components/CopyPageDropdown.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,39 +248,39 @@ export function CopyPageDropdown({
248248
<button
249249
onClick={handleCopyPage}
250250
className={twMerge(
251-
'inline-flex items-center justify-center gap-2',
252-
'py-1.5 pl-3 pr-3',
253-
'text-sm font-medium',
251+
'inline-flex items-center justify-center gap-1.5',
252+
'py-1 pl-2 pr-2',
253+
'text-xs font-medium',
254254
'text-gray-700 dark:text-gray-300',
255255
'hover:bg-gray-50 dark:hover:bg-gray-700',
256256
'transition-colors duration-200',
257257
)}
258258
>
259259
{copied ? (
260260
<>
261-
<Check className="w-3.5 h-3.5" />
261+
<Check className="w-3 h-3" />
262262
Copied!
263263
</>
264264
) : (
265265
<>
266-
<Copy className="w-3.5 h-3.5" />
266+
<Copy className="w-3 h-3" />
267267
Copy page
268268
</>
269269
)}
270270
</button>
271-
<div className="w-px h-5 bg-gray-200 dark:bg-gray-700" />
271+
<div className="w-px h-4 bg-gray-200 dark:bg-gray-700" />
272272
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
273273
<DropdownMenu.Trigger asChild>
274274
<button
275275
className={twMerge(
276276
'inline-flex items-center justify-center',
277-
'px-2 py-1.5',
277+
'px-1.5 py-1',
278278
'text-gray-500 dark:text-gray-400',
279279
'hover:bg-gray-50 dark:hover:bg-gray-700',
280280
'transition-colors duration-200',
281281
)}
282282
>
283-
<ChevronDown className="w-3.5 h-3.5" />
283+
<ChevronDown className="w-3 h-3" />
284284
</button>
285285
</DropdownMenu.Trigger>
286286
<DropdownMenu.Portal>

src/components/Doc.tsx

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { useWidthToggle } from '~/components/DocsLayout'
55
import { AdGate } from '~/contexts/AdsContext'
66
import { GamHeader } from './Gam'
77
import { Toc } from './Toc'
8-
import { TocMobile } from './TocMobile'
98
import { renderMarkdown } from '~/utils/markdown'
109
import { DocBreadcrumb } from './DocBreadcrumb'
1110
import { MarkdownContent } from './MarkdownContent'
@@ -111,7 +110,6 @@ export function Doc({
111110

112111
return (
113112
<div className="flex-1 min-h-0 flex flex-col">
114-
{shouldRenderToc ? <TocMobile headings={headings} /> : null}
115113
<AdGate>
116114
<div className="py-2 pb-6 lg:py-4 lg:pb-8 xl:py-6 xl:pb-10 max-w-full">
117115
<GamHeader />
@@ -123,15 +121,13 @@ export function Doc({
123121
isTocVisible && 'max-w-full',
124122
)}
125123
>
126-
<div
127-
className={twMerge(
128-
'flex overflow-auto flex-col w-full sm:pr-2 lg:pr-4 xl:pr-6',
129-
isTocVisible && 'pr-0!',
130-
)}
131-
>
132-
{config && title && (
133-
<div className="mb-3 pr-2 lg:pr-4">
134-
<DocBreadcrumb config={config} title={title} />
124+
<div className="flex overflow-auto flex-col w-full">
125+
{config && (
126+
<div className="mb-3">
127+
<DocBreadcrumb
128+
config={config}
129+
headings={isTocVisible ? headings : undefined}
130+
/>
135131
</div>
136132
)}
137133
<MarkdownContent
@@ -141,9 +137,6 @@ export function Doc({
141137
filePath={filePath}
142138
htmlMarkup={markup}
143139
containerRef={markdownContainerRef}
144-
proseClassName={
145-
isTocVisible ? 'sm:pr-2 lg:pr-4 xl:pr-6' : undefined
146-
}
147140
libraryId={libraryId}
148141
libraryVersion={libraryVersion}
149142
pagePath={pagePath}
@@ -166,7 +159,7 @@ export function Doc({
166159
</div>
167160

168161
{isTocVisible && (
169-
<div className="pl-2 xl:pl-6 2xl:pl-8 max-w-32 lg:max-w-36 xl:max-w-44 2xl:max-w-56 3xl:max-w-64 w-full hidden lg:block transition-all">
162+
<div className="pl-4 max-w-32 lg:max-w-36 xl:max-w-44 2xl:max-w-56 3xl:max-w-64 w-full hidden lg:block transition-all">
170163
<Toc
171164
headings={headings}
172165
activeHeadings={activeHeadings}

src/components/DocBreadcrumb.tsx

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useParams } from '@tanstack/react-router'
2-
import { ChevronRight } from 'lucide-react'
2+
import { Breadcrumbs } from './Breadcrumbs'
33
import type { ConfigSchema } from '~/utils/config'
4+
import type { MarkdownHeading } from '~/utils/markdown/processor'
45

56
function findSectionForDoc(
67
config: ConfigSchema,
@@ -31,10 +32,10 @@ function findSectionForDoc(
3132

3233
export function DocBreadcrumb({
3334
config,
34-
title,
35+
headings,
3536
}: {
3637
config: ConfigSchema
37-
title: string
38+
headings?: MarkdownHeading[]
3839
}) {
3940
const { _splat, framework } = useParams({ strict: false })
4041

@@ -51,15 +52,10 @@ export function DocBreadcrumb({
5152
}
5253

5354
return (
54-
<nav
55-
aria-label="Breadcrumb"
56-
className="flex items-center gap-1.5 text-sm text-gray-500 dark:text-gray-400"
57-
>
58-
<span>{section}</span>
59-
<ChevronRight className="w-3.5 h-3.5 opacity-50 shrink-0" />
60-
<span className="text-gray-700 dark:text-gray-200 font-medium truncate max-w-[300px]">
61-
{title}
62-
</span>
63-
</nav>
55+
<Breadcrumbs
56+
section={section}
57+
headings={headings}
58+
tocHiddenBreakpoint="lg"
59+
/>
6460
)
6561
}

src/components/DocsLayout.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react'
2-
import { TextAlignStart, ArrowLeft, ArrowRight, Menu, Tag } from 'lucide-react'
2+
import { TextAlignStart, ArrowLeft, ArrowRight, Menu } from 'lucide-react'
33
import { GithubIcon } from '~/components/icons/GithubIcon'
44
import { DiscordIcon } from '~/components/icons/DiscordIcon'
55
import { Link, useMatches, useParams } from '@tanstack/react-router'
@@ -27,6 +27,7 @@ function DocsMenuStrip({
2727
colorFrom,
2828
colorTo,
2929
frameworkLogo,
30+
version,
3031
onHover,
3132
onClick,
3233
}: {
@@ -36,6 +37,7 @@ function DocsMenuStrip({
3637
colorFrom: string
3738
colorTo: string
3839
frameworkLogo: string | undefined
40+
version: string
3941
onHover: () => void
4042
onClick: () => void
4143
}) {
@@ -104,7 +106,7 @@ function DocsMenuStrip({
104106
{/* FrameworkSelect + VersionSelect icons */}
105107
<div className="flex flex-col gap-2 shrink-0">
106108
<div className="flex items-center justify-center">
107-
<span className="flex items-center justify-center w-6 h-6 rounded border border-gray-500/20">
109+
<span className="flex items-center justify-center w-6 h-6">
108110
{frameworkLogo ? (
109111
<img src={frameworkLogo} alt="" className="w-4 h-4" />
110112
) : (
@@ -113,8 +115,8 @@ function DocsMenuStrip({
113115
</span>
114116
</div>
115117
<div className="flex items-center justify-center">
116-
<span className="flex items-center justify-center w-6 h-6 rounded border border-gray-500/20">
117-
<Tag className="w-3.5 h-3.5 opacity-60" />
118+
<span className="flex items-center justify-center px-1 py-0.5 text-[9px] font-medium opacity-60 border border-gray-500/30 rounded">
119+
{version}
118120
</span>
119121
</div>
120122
</div>
@@ -280,7 +282,7 @@ export function DocsLayout({
280282
repo,
281283
children,
282284
}: DocsLayoutProps) {
283-
const { libraryId } = useParams({
285+
const { libraryId, version } = useParams({
284286
from: '/$libraryId/$version/docs',
285287
})
286288
const { _splat } = useParams({ strict: false })
@@ -471,6 +473,7 @@ export function DocsLayout({
471473
colorFrom={colorFrom}
472474
colorTo={colorTo}
473475
frameworkLogo={currentFrameworkOption?.logo}
476+
version={version}
474477
onHover={() => {
475478
if (window.innerWidth < 1280) {
476479
// Only auto-show on lg screens, not xl+
@@ -544,7 +547,7 @@ export function DocsLayout({
544547
>
545548
{smallMenu}
546549
{largeMenu}
547-
<div className="flex flex-col max-w-full min-w-0 w-full min-h-0 relative px-4 sm:px-0 sm:pl-8">
550+
<div className="flex flex-col max-w-full min-w-0 flex-1 min-h-0 relative px-4 sm:px-8">
548551
<div
549552
className={twMerge(
550553
`max-w-full min-w-0 flex justify-center w-full min-h-[88dvh] sm:min-h-0`,
@@ -605,8 +608,8 @@ export function DocsLayout({
605608
sm:top-[var(--navbar-height)]
606609
"
607610
>
608-
<div className="sm:sticky sm:top-[var(--navbar-height)] ml-auto flex flex-wrap flex-row justify-center sm:flex-col gap-4 pl-4 pb-4">
609-
<div className="flex flex-wrap items-stretch border-l border-gray-500/20 rounded-bl-lg overflow-hidden -mr-px">
611+
<div className="sm:sticky sm:top-[var(--navbar-height)] ml-auto flex flex-wrap flex-row justify-center sm:flex-col gap-4 pb-4 max-w-full overflow-hidden">
612+
<div className="flex flex-wrap items-stretch border-l border-gray-500/20 rounded-bl-lg overflow-hidden w-full">
610613
<div className="w-full flex gap-2 justify-between border-b border-gray-500/20 px-3 py-2">
611614
<Link
612615
className="font-medium opacity-60 hover:opacity-100 text-xs"

src/components/Toc.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function Toc({
2828
activeHeadings,
2929
}: TocProps) {
3030
return (
31-
<nav className="flex flex-col sticky top-[var(--navbar-height)] max-h-[calc(100dvh-var(--navbar-height))]">
31+
<nav className="flex flex-col sticky top-[var(--navbar-height)] max-h-[calc(100dvh-var(--navbar-height))] overflow-hidden">
3232
<div className="py-1">
3333
<h3 className="text-[.8em] lg:text-[.825em] xl:text-[.875em] 2xl:text-[.9em] font-bold">
3434
On this page
@@ -60,7 +60,10 @@ export function Toc({
6060
behavior: 'smooth',
6161
}}
6262
>
63-
<span dangerouslySetInnerHTML={{ __html: heading.text }} />
63+
<span
64+
className="truncate block"
65+
dangerouslySetInnerHTML={{ __html: heading.text }}
66+
/>
6467
</Link>
6568
</li>
6669
))}

0 commit comments

Comments
 (0)