Skip to content

Commit f3663a9

Browse files
committed
checkpoint
1 parent 88ef80d commit f3663a9

File tree

9 files changed

+176
-134
lines changed

9 files changed

+176
-134
lines changed

src/components/FeedPage.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,9 @@ export function FeedPage({
102102
libraries: normalizeFilter(effectiveFilters.libraries),
103103
partners: normalizeFilter(effectiveFilters.partners),
104104
tags: normalizeFilter(effectiveFilters.tags),
105-
releaseLevels: normalizeFilter(effectiveFilters.releaseLevels),
105+
releaseLevels: normalizeFilter(effectiveFilters.releaseLevels) ?? [
106+
...FEED_DEFAULTS.releaseLevels,
107+
],
106108
includePrerelease: effectiveFilters.includePrerelease,
107109
featured: effectiveFilters.featured,
108110
search: effectiveFilters.search,
@@ -117,7 +119,9 @@ export function FeedPage({
117119
libraries: normalizeFilter(effectiveFilters.libraries),
118120
partners: normalizeFilter(effectiveFilters.partners),
119121
tags: normalizeFilter(effectiveFilters.tags),
120-
releaseLevels: normalizeFilter(effectiveFilters.releaseLevels),
122+
releaseLevels: normalizeFilter(effectiveFilters.releaseLevels) ?? [
123+
...FEED_DEFAULTS.releaseLevels,
124+
],
121125
includePrerelease: effectiveFilters.includePrerelease,
122126
featured: effectiveFilters.featured,
123127
search: effectiveFilters.search,
@@ -246,7 +250,9 @@ export function FeedPage({
246250
selectedLibraries={effectiveFilters.libraries}
247251
selectedPartners={effectiveFilters.partners}
248252
selectedTags={effectiveFilters.tags}
249-
selectedReleaseLevels={effectiveFilters.releaseLevels}
253+
selectedReleaseLevels={
254+
effectiveFilters.releaseLevels ?? [...FEED_DEFAULTS.releaseLevels]
255+
}
250256
includePrerelease={effectiveFilters.includePrerelease}
251257
featured={effectiveFilters.featured}
252258
search={effectiveFilters.search}

src/components/FeedTicker.tsx

Lines changed: 82 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -68,99 +68,104 @@ export function FeedTicker() {
6868
if (!currentEntry) return null
6969

7070
const renderEntry = (entry: typeof currentEntry) => {
71-
const isRelease = entry.entryType === 'release'
72-
73-
if (isRelease) {
74-
// Get release level badge color
75-
const releaseLevelTag = entry.tags.find((tag) =>
76-
tag.startsWith('release:'),
77-
)
78-
const isPrerelease = entry.tags.includes('release:prerelease')
79-
const releaseLevel = releaseLevelTag?.replace('release:', '') || ''
80-
81-
const badgeColors: Record<string, string> = {
82-
major:
83-
'bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200',
84-
minor:
71+
// Entry type badge configs
72+
const badgeConfigs: Record<string, { label: string; className: string }> = {
73+
release: {
74+
label: 'Release',
75+
className:
76+
'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
77+
},
78+
blog: {
79+
label: 'Blog',
80+
className:
8581
'bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200',
86-
patch: 'bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200',
87-
}
88-
89-
const badgeColor = isPrerelease
90-
? 'bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200'
91-
: badgeColors[releaseLevel] ||
92-
'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200'
93-
94-
// Get version from metadata (strip @tanstack/ prefix)
95-
const version = (
96-
(entry.metadata as { version?: string } | null)?.version || ''
97-
).replace(/@tanstack\//gi, '')
98-
99-
return (
100-
<Link
101-
to="/feed/$id"
102-
params={{ id: entry._id }}
103-
search={{} as any}
104-
className="flex items-center gap-1.5 px-2 py-1 rounded-lg hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors group"
105-
>
106-
{/* Badge */}
107-
<span
108-
className={`px-1.5 py-0.5 rounded text-[10px] font-medium uppercase flex-shrink-0 ${badgeColor}`}
109-
>
110-
{isPrerelease ? 'Pre' : releaseLevel || 'Release'}
111-
</span>
112-
113-
{/* Version */}
114-
{version && (
115-
<span className="text-[10px] font-medium text-gray-600 dark:text-gray-400 flex-shrink-0">
116-
{version}
117-
</span>
118-
)}
119-
120-
{/* Time ago */}
121-
<span className="text-[10px] text-gray-500 dark:text-gray-400 flex-shrink-0">
122-
{formatDistanceToNow(new Date(entry.publishedAt), {
123-
addSuffix: true,
124-
})}
125-
</span>
82+
},
83+
announcement: {
84+
label: 'News',
85+
className:
86+
'bg-orange-100 dark:bg-orange-900 text-orange-800 dark:text-orange-200',
87+
},
88+
}
12689

127-
{/* Library name */}
128-
{entry.libraryIds[0] &&
129-
(() => {
130-
const library = libraries.find(
131-
(lib) => lib.id === entry.libraryIds[0],
132-
)
133-
return library ? (
134-
<span className="text-[10px] font-medium text-gray-700 dark:text-gray-300 flex-shrink-0">
135-
{library.name}
136-
</span>
137-
) : null
138-
})()}
139-
140-
{/* Excerpt */}
141-
{entry.excerpt && (
142-
<span className="text-[10px] text-gray-500 dark:text-gray-400 truncate flex-1 min-w-0">
143-
{entry.excerpt}
144-
</span>
145-
)}
146-
</Link>
147-
)
90+
const badge = badgeConfigs[entry.entryType] || {
91+
label: entry.entryType,
92+
className:
93+
'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200',
14894
}
14995

150-
// Non-release entries - keep original format
96+
const isRelease = entry.entryType === 'release'
97+
98+
// For releases, get release level and version
99+
const releaseLevelTag = isRelease
100+
? entry.tags.find(
101+
(tag) =>
102+
tag === 'release:major' ||
103+
tag === 'release:minor' ||
104+
tag === 'release:patch',
105+
)
106+
: null
107+
const releaseLevel = releaseLevelTag?.replace('release:', '') || ''
108+
const version = isRelease
109+
? (
110+
(entry.metadata as { version?: string } | null)?.version || ''
111+
).replace(/@tanstack\//gi, '')
112+
: ''
113+
151114
return (
152115
<Link
153116
to="/feed/$id"
154117
params={{ id: entry._id }}
155118
search={{} as any}
156-
className="flex items-center gap-2 px-2 py-1 rounded-lg hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors group"
119+
className="flex items-center gap-1.5 px-2 py-1 rounded-lg hover:bg-gray-100/50 dark:hover:bg-gray-800/50 transition-colors group"
157120
>
121+
{/* Entry type badge */}
122+
<span
123+
className={`px-1.5 py-0.5 rounded text-[10px] font-medium uppercase flex-shrink-0 ${badge.className}`}
124+
>
125+
{badge.label}
126+
</span>
127+
128+
{/* Release level (for releases only) */}
129+
{isRelease && releaseLevel && (
130+
<span className="text-[10px] font-medium text-gray-600 dark:text-gray-400 flex-shrink-0 uppercase">
131+
{releaseLevel}
132+
</span>
133+
)}
134+
135+
{/* Library name (for releases) */}
136+
{isRelease &&
137+
entry.libraryIds[0] &&
138+
(() => {
139+
const library = libraries.find(
140+
(lib) => lib.id === entry.libraryIds[0],
141+
)
142+
return library ? (
143+
<span className="text-[10px] font-semibold text-gray-700 dark:text-gray-300 flex-shrink-0">
144+
{library.name}
145+
</span>
146+
) : null
147+
})()}
148+
149+
{/* Version (for releases) */}
150+
{isRelease && version && (
151+
<span className="text-[10px] font-medium text-gray-600 dark:text-gray-400 flex-shrink-0">
152+
{version}
153+
</span>
154+
)}
155+
158156
{/* Time ago */}
159157
<span className="text-[10px] text-gray-500 dark:text-gray-400 flex-shrink-0">
160158
{formatDistanceToNow(new Date(entry.publishedAt), {
161159
addSuffix: true,
162160
})}
163161
</span>
162+
163+
{/* Title or excerpt */}
164+
{(entry.title || entry.excerpt) && (
165+
<span className="text-[10px] text-gray-500 dark:text-gray-400 truncate flex-1 min-w-0">
166+
{isRelease ? entry.excerpt : entry.title || entry.excerpt}
167+
</span>
168+
)}
164169
</Link>
165170
)
166171
}

src/components/Navbar.tsx

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,28 @@ export function Navbar({ children }: { children: React.ReactNode }) {
6767
}, [])
6868

6969
const [showMenu, setShowMenu] = React.useState(false)
70+
const smallMenuRef = React.useRef<HTMLDivElement>(null)
7071

7172
const toggleMenu = () => {
7273
setShowMenu((prev) => !prev)
7374
}
7475

76+
// Close mobile menu when clicking outside
77+
React.useEffect(() => {
78+
if (!showMenu) return
79+
80+
const handleClickOutside = (event: MouseEvent) => {
81+
const target = event.target as HTMLElement
82+
// Check if click is outside the small menu
83+
if (smallMenuRef.current && !smallMenuRef.current.contains(target)) {
84+
setShowMenu(false)
85+
}
86+
}
87+
88+
document.addEventListener('click', handleClickOutside)
89+
return () => document.removeEventListener('click', handleClickOutside)
90+
}, [showMenu])
91+
7592
const loginButton = (
7693
<>
7794
{(() => {
@@ -113,8 +130,8 @@ export function Navbar({ children }: { children: React.ReactNode }) {
113130
<Link
114131
to="/admin"
115132
className="flex items-center gap-1 rounded-md px-2 py-1.5
116-
border border-gray-200 dark:border-gray-700
117-
hover:bg-gray-100 dark:hover:bg-gray-800
133+
bg-black dark:bg-white text-white dark:text-black
134+
hover:bg-gray-800 dark:hover:bg-gray-200
118135
transition-colors duration-200 text-xs font-medium"
119136
>
120137
<Lock className="w-3.5 h-3.5" />
@@ -159,13 +176,17 @@ export function Navbar({ children }: { children: React.ReactNode }) {
159176
)
160177

161178
const navbar = (
179+
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
162180
<div
163181
className={twMerge(
164182
'w-full p-2 fixed top-0 z-[100] bg-white/90 dark:bg-black/90 backdrop-blur-lg',
165183
'flex items-center justify-between gap-4',
166184
'border-b border-gray-500/20',
167185
)}
168186
ref={containerRef}
187+
onClick={() => {
188+
setShowMenu(false)
189+
}}
169190
>
170191
<div className="flex items-center gap-4">
171192
<div className="flex items-center gap-2 font-black text-xl uppercase">
@@ -220,7 +241,7 @@ export function Navbar({ children }: { children: React.ReactNode }) {
220241
{Title ? <Title /> : null}
221242
</div>
222243
</div>
223-
<div className="hidden lg:flex flex-1 justify-end min-w-0">
244+
<div className="hidden xl:flex flex-1 justify-end min-w-0">
224245
<FeedTicker />
225246
</div>
226247
<div className="flex items-center gap-2">
@@ -564,6 +585,7 @@ export function Navbar({ children }: { children: React.ReactNode }) {
564585

565586
const smallMenu = showMenu ? (
566587
<div
588+
ref={smallMenuRef}
567589
className="lg:hidden bg-white/50 dark:bg-black/60 backdrop-blur-[20px] z-50
568590
fixed top-[var(--navbar-height)] left-0 right-0 max-h-[calc(100dvh-var(--navbar-height))] overflow-y-auto
569591
"
@@ -572,25 +594,26 @@ export function Navbar({ children }: { children: React.ReactNode }) {
572594
className="flex flex-col whitespace-nowrap overflow-y-auto
573595
border-t border-gray-500/20 text-lg bg-white/80 dark:bg-black/90"
574596
>
575-
<div className="flex items-center justify-between p-2 gap-2">
576-
<div className="flex-1">
577-
<SearchButton />
578-
</div>
579-
<div className="sm:hidden">{loginButton}</div>
580-
</div>
581597
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions */}
582598
<div
583-
className="space-y-px text-sm p-2 border-b border-gray-500/20"
584599
onClick={(event) => {
585600
const target = event.target as HTMLElement
586-
if (target.closest('a')) {
601+
if (target.closest('a') || target.closest('button')) {
587602
setShowMenu(false)
588603
}
589604
}}
590605
>
591-
{items}
606+
<div className="flex items-center justify-between p-2 gap-2">
607+
<div className="flex-1">
608+
<SearchButton />
609+
</div>
610+
<div className="xs:hidden">{loginButton}</div>
611+
</div>
612+
<div className="space-y-px text-sm p-2 border-b border-gray-500/20">
613+
{items}
614+
</div>
615+
<div className="p-4 sm:hidden">{socialLinks}</div>
592616
</div>
593-
<div className="p-4 sm:hidden">{socialLinks}</div>
594617
</div>
595618
</div>
596619
) : null

src/components/Toc.tsx

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Link } from '@tanstack/react-router'
12
import * as React from 'react'
23
import { twMerge } from 'tailwind-merge'
34
import { MarkdownHeading } from '~/utils/markdown/processor'
@@ -35,37 +36,32 @@ export function Toc({
3536
</div>
3637
<ul
3738
className={twMerge(
38-
'py-1 flex flex-col overflow-y-auto gap-0.5 text-[.6em] lg:text-[.65em] xl:text-[.7em] 2xl:text-[.75em]',
39+
'py-1 flex flex-col overflow-y-auto text-[.6em] lg:text-[.65em] xl:text-[.7em] 2xl:text-[.75em]',
3940
)}
4041
>
4142
{headings?.map((heading) => (
4243
<li
4344
key={heading.id}
44-
className={twMerge(
45-
'cursor-pointer py-1 w-full rounded hover:bg-gray-500/10',
46-
headingLevels[heading.level],
47-
)}
45+
className={twMerge('w-full', headingLevels[heading.level])}
4846
>
49-
<a
47+
<Link
48+
to="."
5049
title={heading.id}
51-
href={`#${heading.id}`}
50+
hash={`#${heading.id}`}
5251
aria-current={activeHeadings.includes(heading.id) && 'location'}
5352
className={twMerge(
54-
'flex items-start gap-1.5 transition-colors duration-200 opacity-60 hover:opacity-100',
55-
activeHeadings.includes(heading.id) &&
56-
`opacity-100 ${textColor}`,
53+
'block py-1 pl-2 border-l-2 rounded-r transition-colors duration-200 opacity-60 hover:opacity-100 hover:bg-gray-500/10',
54+
activeHeadings.includes(heading.id)
55+
? `opacity-100 border-current ${textColor}`
56+
: 'border-transparent',
5757
)}
58+
resetScroll={false}
59+
hashScrollIntoView={{
60+
behavior: 'smooth',
61+
}}
5862
>
59-
<span
60-
className={twMerge(
61-
'w-1.5 h-1.5 mt-1.5 shrink-0 rounded-full transition-opacity duration-200',
62-
activeHeadings.includes(heading.id)
63-
? `bg-current`
64-
: 'opacity-0',
65-
)}
66-
/>
6763
<span dangerouslySetInnerHTML={{ __html: heading.text }} />
68-
</a>
64+
</Link>
6965
</li>
7066
))}
7167
</ul>

0 commit comments

Comments
 (0)