Skip to content

Commit dad6095

Browse files
committed
redesign checkpoint again
1 parent 122d5c9 commit dad6095

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+975
-696
lines changed
191 KB
Loading

src/blog/tanstack-ai-the-ai-function-postmortem.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ authors:
55
- Alem Tuzlak
66
---
77

8+
![The ai() Function That Almost Was](/blog-assets/tanstack-ai-the-ai-function-postmortem/header.jpg)
9+
810
We spent eight days building an API we had to kill. Here's what happened.
911

1012
## The Dream

src/components/BottomCTA.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function BottomCTA({
2626
<Link
2727
{...linkProps}
2828
className={twMerge(
29-
'inline-block py-2 px-4 rounded uppercase font-extrabold transition-colors',
29+
'inline-block py-2 px-4 rounded-lg font-black transition-all shadow-xs hover:shadow-sm',
3030
className,
3131
)}
3232
>

src/components/Card.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react'
2+
import { twMerge } from 'tailwind-merge'
3+
4+
type CardProps<T extends React.ElementType = 'div'> = {
5+
as?: T
6+
children: React.ReactNode
7+
className?: string
8+
} & Omit<React.ComponentPropsWithoutRef<T>, 'as' | 'className' | 'children'>
9+
10+
export function Card<T extends React.ElementType = 'div'>({
11+
as,
12+
children,
13+
className,
14+
...props
15+
}: CardProps<T>) {
16+
const Component = as || 'div'
17+
return (
18+
<Component
19+
className={twMerge(
20+
'bg-white dark:bg-gray-900 rounded-lg shadow-md border border-gray-200 dark:border-gray-800',
21+
className,
22+
)}
23+
{...props}
24+
>
25+
{children}
26+
</Component>
27+
)
28+
}

src/components/CopyPageDropdown.tsx

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -47,46 +47,93 @@ function ChatGPTIcon({ className }: { className?: string }) {
4747
)
4848
}
4949

50-
export function CopyPageDropdown() {
50+
// Cache for fetched markdown content
51+
const markdownCache = new Map<string, string>()
52+
53+
type CopyPageDropdownProps = {
54+
/** GitHub repo (e.g., 'tanstack/tanstack.com'). If provided, fetches from GitHub. */
55+
repo?: string
56+
/** GitHub branch (e.g., 'main'). Required if repo is provided. */
57+
branch?: string
58+
/** File path in the repo (e.g., 'src/blog/my-post.md'). Required if repo is provided. */
59+
filePath?: string
60+
}
61+
62+
export function CopyPageDropdown({
63+
repo,
64+
branch,
65+
filePath,
66+
}: CopyPageDropdownProps = {}) {
5167
const [open, setOpen] = React.useState(false)
5268
const [copied, setCopied] = React.useState(false)
5369
const { notify } = useToast()
5470

71+
// Determine if we should fetch from GitHub or use the page URL
72+
const useGitHub = repo && branch && filePath
73+
const gitHubUrl = useGitHub
74+
? `https://raw.githubusercontent.com/${repo}/${branch}/${filePath}`
75+
: null
76+
const pageMarkdownUrl = `${typeof window !== 'undefined' ? window.location.origin : ''}${typeof window !== 'undefined' ? window.location.pathname.replace(/\/$/, '') : ''}.md`
77+
5578
const handleCopyPage = async () => {
56-
const url = `${window.location.origin}${window.location.pathname.replace(/\/$/, '')}.md`
57-
try {
58-
const response = await fetch(url)
59-
if (!response.ok) throw new Error('Failed to fetch')
60-
const content = await response.text()
79+
const urlToFetch = gitHubUrl || pageMarkdownUrl
80+
const cached = markdownCache.get(urlToFetch)
81+
82+
const copyContent = async (content: string, source: string) => {
6183
await navigator.clipboard.writeText(content)
6284
setCopied(true)
6385
setTimeout(() => setCopied(false), 2000)
6486
notify(
6587
<div>
6688
<div className="font-medium">Copied to clipboard</div>
6789
<div className="text-xs text-gray-500 dark:text-gray-400">
68-
Markdown content copied
90+
{source}
6991
</div>
7092
</div>,
7193
)
72-
} catch {
73-
// Fallback to copying the URL
74-
await navigator.clipboard.writeText(window.location.href)
75-
setCopied(true)
76-
setTimeout(() => setCopied(false), 2000)
77-
notify(
78-
<div>
79-
<div className="font-medium">Copied to clipboard</div>
80-
<div className="text-xs text-gray-500 dark:text-gray-400">
81-
Page URL copied
82-
</div>
83-
</div>,
94+
}
95+
96+
if (cached) {
97+
await copyContent(cached, 'Markdown content copied from cache')
98+
return
99+
}
100+
101+
try {
102+
const response = await fetch(urlToFetch)
103+
if (!response.ok) throw new Error('Failed to fetch')
104+
const content = await response.text()
105+
markdownCache.set(urlToFetch, content)
106+
await copyContent(
107+
content,
108+
gitHubUrl
109+
? 'Markdown content copied from GitHub'
110+
: 'Markdown content copied',
84111
)
112+
} catch {
113+
// Fallback: try to copy current page content if available
114+
const pageContent =
115+
document.querySelector('.styled-markdown-content')?.textContent || ''
116+
if (pageContent) {
117+
await copyContent(pageContent, 'Copied rendered page content')
118+
} else {
119+
// Last resort: copy the URL
120+
await navigator.clipboard.writeText(window.location.href)
121+
setCopied(true)
122+
setTimeout(() => setCopied(false), 2000)
123+
notify(
124+
<div>
125+
<div className="font-medium">Copied to clipboard</div>
126+
<div className="text-xs text-gray-500 dark:text-gray-400">
127+
Page URL copied
128+
</div>
129+
</div>,
130+
)
131+
}
85132
}
86133
}
87134

88135
const handleViewMarkdown = () => {
89-
const url = `${window.location.origin}${window.location.pathname.replace(/\/$/, '')}.md`
136+
const url = gitHubUrl || pageMarkdownUrl
90137
window.open(url, '_blank')
91138
}
92139

src/components/Doc.tsx

Lines changed: 31 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import * as React from 'react'
2-
import { FoldHorizontal, SquarePen, UnfoldHorizontal } from 'lucide-react'
2+
import { FoldHorizontal, UnfoldHorizontal } from 'lucide-react'
33
import { twMerge } from 'tailwind-merge'
44
import { useWidthToggle } from '~/components/DocsLayout'
5-
import { DocTitle } from '~/components/DocTitle'
6-
import { Markdown } from '~/components/Markdown'
75
import { AdGate } from '~/contexts/AdsContext'
8-
import { CopyPageDropdown } from './CopyPageDropdown'
96
import { GamHeader } from './Gam'
107
import { Toc } from './Toc'
118
import { TocMobile } from './TocMobile'
12-
import { DocFeedbackProvider } from './DocFeedbackProvider'
139
import { renderMarkdown } from '~/utils/markdown'
1410
import { DocBreadcrumb } from './DocBreadcrumb'
11+
import { MarkdownContent } from './MarkdownContent'
1512
import type { ConfigSchema } from '~/utils/config'
1613

1714
type DocProps = {
@@ -137,63 +134,35 @@ export function Doc({
137134
<DocBreadcrumb config={config} title={title} />
138135
</div>
139136
)}
140-
{title ? (
141-
<div className="flex flex-wrap items-center justify-between gap-2 pr-2 lg:pr-4">
142-
<DocTitle>{title}</DocTitle>
143-
<div className="flex items-center gap-2 shrink-0">
144-
<CopyPageDropdown />
145-
146-
{setIsFullWidth && (
147-
<button
148-
onClick={() => setIsFullWidth(!isFullWidth)}
149-
className="p-2 mr-4 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shrink-0 hidden [@media(min-width:1535px)]:inline-flex"
150-
title={isFullWidth ? 'Constrain width' : 'Expand width'}
151-
>
152-
{isFullWidth ? (
153-
<FoldHorizontal className="w-4 h-4" />
154-
) : (
155-
<UnfoldHorizontal className="w-4 h-4" />
156-
)}
157-
</button>
158-
)}
159-
</div>
160-
</div>
161-
) : null}
162-
<div className="h-4" />
163-
<div className="h-px bg-gray-500 opacity-20" />
164-
<div className="h-4" />
165-
<div
166-
ref={markdownContainerRef}
167-
className={twMerge(
168-
'prose prose-gray dark:prose-invert max-w-none',
169-
'[font-size:14px]',
170-
isTocVisible && 'sm:pr-2 lg:pr-4 xl:pr-6',
171-
'styled-markdown-content',
172-
)}
173-
>
174-
{libraryId && libraryVersion && pagePath ? (
175-
<DocFeedbackProvider
176-
pagePath={pagePath}
177-
libraryId={libraryId}
178-
libraryVersion={libraryVersion}
179-
>
180-
<Markdown htmlMarkup={markup} />
181-
</DocFeedbackProvider>
182-
) : (
183-
<Markdown htmlMarkup={markup} />
184-
)}
185-
</div>
186-
<div className="h-12" />
187-
<div className="w-full h-px bg-gray-500 opacity-30" />
188-
<div className="flex py-4 opacity-70">
189-
<a
190-
href={`https://github.com/${repo}/edit/${branch}/${filePath}`}
191-
className="flex items-center gap-2"
192-
>
193-
<SquarePen /> Edit on GitHub
194-
</a>
195-
</div>
196-
<div className="h-24" />
137+
<MarkdownContent
138+
title={title}
139+
repo={repo}
140+
branch={branch}
141+
filePath={filePath}
142+
htmlMarkup={markup}
143+
containerRef={markdownContainerRef}
144+
proseClassName={
145+
isTocVisible ? 'sm:pr-2 lg:pr-4 xl:pr-6' : undefined
146+
}
147+
libraryId={libraryId}
148+
libraryVersion={libraryVersion}
149+
pagePath={pagePath}
150+
titleBarActions={
151+
setIsFullWidth ? (
152+
<button
153+
onClick={() => setIsFullWidth(!isFullWidth)}
154+
className="p-2 mr-4 text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shrink-0 hidden [@media(min-width:1535px)]:inline-flex"
155+
title={isFullWidth ? 'Constrain width' : 'Expand width'}
156+
>
157+
{isFullWidth ? (
158+
<FoldHorizontal className="w-4 h-4" />
159+
) : (
160+
<UnfoldHorizontal className="w-4 h-4" />
161+
)}
162+
</button>
163+
) : null
164+
}
165+
/>
197166
</div>
198167

199168
{isTocVisible && (

src/components/DocsLayout.tsx

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { VersionSelect } from './VersionSelect'
2222
function DocsMenuStrip({
2323
menuConfig,
2424
activeItem,
25+
fullPathname,
2526
colorFrom,
2627
colorTo,
2728
frameworkLogo,
@@ -30,6 +31,7 @@ function DocsMenuStrip({
3031
}: {
3132
menuConfig: MenuItem[]
3233
activeItem: string | undefined
34+
fullPathname: string
3335
colorFrom: string
3436
colorTo: string
3537
frameworkLogo: string | undefined
@@ -53,6 +55,42 @@ function DocsMenuStrip({
5355
})
5456
})
5557

58+
// Check if a menu item path matches the current location
59+
const isItemActive = (itemTo: string | undefined): boolean => {
60+
if (!itemTo) return false
61+
62+
// External links are never active
63+
if (itemTo.startsWith('http')) return false
64+
65+
// Standard relative path comparison
66+
if (itemTo === activeItem) return true
67+
68+
// Handle special menu items with different path formats
69+
// ".." means we're on the library home page (no /docs suffix in pathname)
70+
if (itemTo === '..') {
71+
// Active when on the library version index (e.g., /query/latest but not /query/latest/docs/...)
72+
return fullPathname.match(/^\/[^/]+\/[^/]+\/?$/) !== null
73+
}
74+
75+
// "./framework" means we're on the frameworks index page
76+
if (itemTo === './framework') {
77+
return (
78+
fullPathname.includes('/docs/framework') &&
79+
!fullPathname.match(/\/docs\/framework\/[^/]+/)
80+
)
81+
}
82+
83+
// Handle absolute paths like "/$libraryId/$version/docs/contributors"
84+
if (itemTo.includes('/$libraryId')) {
85+
const pathSuffix = itemTo.split('/docs/')[1]
86+
if (pathSuffix && fullPathname.includes(`/docs/${pathSuffix}`)) {
87+
return true
88+
}
89+
}
90+
91+
return false
92+
}
93+
5694
return (
5795
<div
5896
className="flex flex-col gap-2 py-2 px-2 cursor-pointer h-full w-full"
@@ -80,7 +118,7 @@ function DocsMenuStrip({
80118
{/* Minimap: flex-height boxes filling remaining space */}
81119
<div className="flex-1 flex flex-col gap-1 min-h-0">
82120
{itemsWithSections.map((item, index) => {
83-
const isActive = !item.isSection && item.to === activeItem
121+
const isActive = !item.isSection && isItemActive(item.to)
84122

85123
return (
86124
<div
@@ -256,16 +294,22 @@ export function DocsLayout({
256294
[menuConfig],
257295
)
258296

297+
// Filter out external links for prev/next navigation
298+
const internalFlatMenu = React.useMemo(
299+
() => flatMenu.filter((d) => d && !d.to.startsWith('http')),
300+
[flatMenu],
301+
)
302+
259303
const docsMatch = matches.find((d) => d.pathname.includes('/docs'))
260304

261305
const relativePathname = lastMatch.pathname.replace(
262306
docsMatch!.pathname + '/',
263307
'',
264308
)
265309

266-
const index = flatMenu.findIndex((d) => d?.to === relativePathname)
267-
const prevItem = flatMenu[index - 1]
268-
const nextItem = flatMenu[index + 1]
310+
const index = internalFlatMenu.findIndex((d) => d?.to === relativePathname)
311+
const prevItem = internalFlatMenu[index - 1]
312+
const nextItem = internalFlatMenu[index + 1]
269313

270314
// Get current framework's logo for the preview strip
271315
const currentFramework = useCurrentFramework(frameworks)
@@ -410,6 +454,7 @@ export function DocsLayout({
410454
<DocsMenuStrip
411455
menuConfig={menuConfig}
412456
activeItem={relativePathname}
457+
fullPathname={lastMatch.pathname}
413458
colorFrom={colorFrom}
414459
colorTo={colorTo}
415460
frameworkLogo={currentFrameworkOption?.logo}

0 commit comments

Comments
 (0)