Skip to content

Commit 5f28e78

Browse files
committed
feat: add YouTube channel links and promotion across the site
1 parent a1efd26 commit 5f28e78

File tree

10 files changed

+281
-25
lines changed

10 files changed

+281
-25
lines changed

src/components/DocsLayout.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react'
22
import { ChevronLeft, ChevronRight, Menu } from 'lucide-react'
33
import { GithubIcon } from '~/components/icons/GithubIcon'
44
import { DiscordIcon } from '~/components/icons/DiscordIcon'
5+
import { YouTubeIcon } from '~/components/icons/YouTubeIcon'
56
import { Link, useMatches, useParams } from '@tanstack/react-router'
67
import { useLocalStorage } from '~/utils/useLocalStorage'
78
import { useClickOutside } from '~/hooks/useClickOutside'
@@ -408,6 +409,14 @@ const useMenuConfig = ({
408409
),
409410
to: `https://github.com/${repo}`,
410411
},
412+
{
413+
label: (
414+
<div className="flex items-center gap-2">
415+
YouTube <YouTubeIcon className="text-lg opacity-20" />
416+
</div>
417+
),
418+
to: 'https://youtube.com/@tan_stack',
419+
},
411420
{
412421
label: (
413422
<div className="flex items-center gap-2">

src/components/Footer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const footerLinks = [
1010
},
1111
{ label: 'GitHub', to: 'https://github.com/tanstack' },
1212
{
13-
label: 'Youtube',
14-
to: 'https://www.youtube.com/user/tannerlinsley',
13+
label: 'YouTube',
14+
to: 'https://youtube.com/@tan_stack',
1515
},
1616
{
1717
label: 'Nozzle.io - Keyword Rank Tracker',

src/components/Navbar.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { DiscordIcon } from '~/components/icons/DiscordIcon'
4949
import { InstagramIcon } from '~/components/icons/InstagramIcon'
5050
import { BSkyIcon } from '~/components/icons/BSkyIcon'
5151
import { BrandXIcon } from '~/components/icons/BrandXIcon'
52+
import { YouTubeIcon } from '~/components/icons/YouTubeIcon'
5253
const LazyAuthenticatedUserMenu = React.lazy(() =>
5354
import('~/components/AuthenticatedUserMenu').then((m) => ({
5455
default: m.AuthenticatedUserMenu,
@@ -278,6 +279,12 @@ export function Navbar({ children }: { children: React.ReactNode }) {
278279
>
279280
<InstagramIcon />
280281
</a>
282+
<a
283+
href="https://youtube.com/@tan_stack"
284+
aria-label="Subscribe to TanStack on YouTube"
285+
>
286+
<YouTubeIcon />
287+
</a>
281288
<a href="https://tlinz.com/discord" aria-label="Join TanStack Discord">
282289
<DiscordIcon />
283290
</a>
@@ -634,6 +641,12 @@ export function Navbar({ children }: { children: React.ReactNode }) {
634641
icon: TrendingUp,
635642
to: '/stats/npm',
636643
},
644+
{
645+
label: 'YouTube',
646+
icon: YouTubeIcon,
647+
to: 'https://youtube.com/@tan_stack',
648+
target: '_blank',
649+
},
637650
{
638651
label: 'Discord',
639652
icon: DiscordIcon,
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from 'react'
2+
3+
export function YouTubeIcon({
4+
className,
5+
width = '1em',
6+
height = '1em',
7+
...props
8+
}: React.SVGProps<SVGSVGElement>) {
9+
return (
10+
<svg
11+
viewBox="0 0 461 461"
12+
fill="currentColor"
13+
width={width}
14+
height={height}
15+
role="img"
16+
aria-hidden={props['aria-label'] ? undefined : true}
17+
className={className}
18+
{...props}
19+
xmlns="http://www.w3.org/2000/svg"
20+
>
21+
<path d="M365.257,67.393H95.744C42.866,67.393,0,110.259,0,163.137v134.728 c0,52.878,42.866,95.744,95.744,95.744h269.513c52.878,0,95.744-42.866,95.744-95.744V163.137 C461.001,110.259,418.135,67.393,365.257,67.393z M300.506,237.056l-126.06,60.123c-3.359,1.602-7.239-0.847-7.239-4.568V168.607 c0-3.774,3.982-6.22,7.348-4.514l126.06,63.881C304.363,229.873,304.298,235.248,300.506,237.056z" />
22+
</svg>
23+
)
24+
}

src/components/landing/IntentLanding.tsx

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -148,17 +148,36 @@ function IntentRegistryPreview() {
148148
params={{ packageName: pkg.name.replace('/', '__') }}
149149
className="group flex flex-col gap-2 rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900/50 p-4 hover:border-sky-300 dark:hover:border-sky-700 transition-colors"
150150
>
151-
<div className="flex items-center justify-between gap-2">
151+
<div className="flex items-start justify-between gap-2 mb-1">
152152
<span className="font-mono text-sm font-semibold text-gray-900 dark:text-gray-100 group-hover:text-sky-600 dark:group-hover:text-sky-400 transition-colors truncate">
153153
{pkg.name}
154154
</span>
155+
<div className="shrink-0 w-20">
156+
{skillHistory[pkg.name] &&
157+
skillHistory[pkg.name].length > 0 ? (
158+
<SkillSparkline
159+
history={skillHistory[pkg.name]}
160+
height={24}
161+
maxSlots={maxSlots}
162+
onVersionClick={(entry: SkillHistoryEntry) => {
163+
navigate({
164+
to: '/intent/registry/$packageName',
165+
params: {
166+
packageName: pkg.name.replace('/', '__'),
167+
},
168+
search: { version: entry.version },
169+
})
170+
}}
171+
/>
172+
) : null}
173+
</div>
174+
</div>
175+
{/* Stats row */}
176+
<div className="flex items-center gap-3 text-[11px] text-gray-400 dark:text-gray-500">
155177
<span className="shrink-0 text-xs font-medium text-sky-600 dark:text-sky-400 tabular-nums">
156178
{pkg.skillNames.length}{' '}
157179
{pkg.skillNames.length === 1 ? 'skill' : 'skills'}
158180
</span>
159-
</div>
160-
{/* Stats row */}
161-
<div className="flex items-center gap-3 text-[11px] text-gray-400 dark:text-gray-500">
162181
{pkg.monthlyDownloads > 0 && (
163182
<span className="tabular-nums">
164183
{pkg.monthlyDownloads >= 1_000_000
@@ -179,23 +198,6 @@ function IntentRegistryPreview() {
179198
{pkg.description}
180199
</p>
181200
)}
182-
{skillHistory[pkg.name] &&
183-
skillHistory[pkg.name].length > 0 && (
184-
<SkillSparkline
185-
history={skillHistory[pkg.name]}
186-
height={28}
187-
maxSlots={maxSlots}
188-
onVersionClick={(entry: SkillHistoryEntry) => {
189-
navigate({
190-
to: '/intent/registry/$packageName',
191-
params: {
192-
packageName: pkg.name.replace('/', '__'),
193-
},
194-
search: { version: entry.version },
195-
})
196-
}}
197-
/>
198-
)}
199201
</Link>
200202
))}
201203
</div>

src/routes/feed.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Card } from '~/components/Card'
44
import { seo } from '~/utils/seo'
55
import { Rss, BookOpen, Github } from 'lucide-react'
66
import { BrandXIcon } from '~/components/icons/BrandXIcon'
7+
import { YouTubeIcon } from '~/components/icons/YouTubeIcon'
78

89
export const Route = createFileRoute('/feed')({
910
component: RouteComp,
@@ -50,6 +51,17 @@ const alternatives = [
5051
label: 'Follow on X',
5152
internal: false,
5253
},
54+
{
55+
title: 'YouTube',
56+
icon: YouTubeIcon,
57+
gradient: 'from-red-500 to-red-700',
58+
borderColor: 'hover:border-red-500/50',
59+
description:
60+
'Tutorials, deep dives, and release walkthroughs on the official TanStack channel.',
61+
href: 'https://youtube.com/@tan_stack',
62+
label: 'Subscribe on YouTube',
63+
internal: false,
64+
},
5365
]
5466

5567
function RouteComp() {

src/routes/index.tsx

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import { setResponseHeaders } from '@tanstack/react-start/server'
2424
import { AdGate } from '~/contexts/AdsContext'
2525
import { GamHeader } from '~/components/Gam'
2626
import { TrustedByMarquee } from '~/components/TrustedByMarquee'
27-
import { ArrowRight, Code2, Layers, Shield, Zap } from 'lucide-react'
27+
import { ArrowRight, Code2, Layers, Shield, Zap, Play } from 'lucide-react'
28+
import { YouTubeIcon } from '~/components/icons/YouTubeIcon'
2829
import { Card } from '~/components/Card'
2930
import LibraryCard from '~/components/LibraryCard'
3031
import { FeaturedShowcases } from '~/components/ShowcaseSection'
@@ -590,6 +591,51 @@ function Index() {
590591
</div>
591592
</div>
592593
</div>
594+
595+
<div className="px-4 mx-auto max-w-(--breakpoint-lg)">
596+
<div
597+
className={`
598+
rounded-md p-4 grid gap-6
599+
bg-gradient-to-br from-red-500 to-red-700 text-white overflow-hidden relative
600+
shadow-xl shadow-red-700/30
601+
sm:p-8 sm:grid-cols-3 items-center`}
602+
>
603+
<div
604+
className={`absolute transform opacity-10 z-0
605+
right-0 top-0 -translate-y-1/3 translate-x-1/3
606+
sm:opacity-20`}
607+
>
608+
<YouTubeIcon width={300} height={300} />
609+
</div>
610+
<div className={`sm:col-span-2`}>
611+
<h3 id="youtube" className="text-3xl font-bold scroll-mt-24">
612+
<a
613+
href="#youtube"
614+
className="hover:underline decoration-white/50"
615+
>
616+
TanStack on YouTube
617+
</a>
618+
</h3>
619+
<p className={`mt-4`}>
620+
The official TanStack YouTube channel. Tutorials, deep dives,
621+
release walkthroughs, and more — free for everyone!
622+
</p>
623+
</div>
624+
<div className={`flex items-center justify-center`}>
625+
<Button
626+
as="a"
627+
href="https://youtube.com/@tan_stack"
628+
target="_blank"
629+
rel="noreferrer"
630+
className="w-full mt-4 bg-white border-white hover:bg-gray-100 text-red-600 justify-center shadow-lg text-sm"
631+
>
632+
<Play className="w-4 h-4" />
633+
Subscribe on YouTube
634+
</Button>
635+
</div>
636+
</div>
637+
</div>
638+
593639
<div className="h-4" />
594640
<div className="px-4 mx-auto max-w-(--breakpoint-lg) relative">
595641
<Card className="rounded-md p-8 md:p-14">

src/routes/intent/registry/index.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,48 @@ function IntentRegistryPage() {
156156
</span>
157157
</div>
158158
</div>
159+
160+
<div className="mt-6">
161+
<Link
162+
to="/$libraryId/$version/docs/$"
163+
params={
164+
{
165+
libraryId: 'intent',
166+
version: 'latest',
167+
_splat: 'registry',
168+
} as never
169+
}
170+
className="inline-flex items-center gap-1.5 text-sm text-sky-600 dark:text-sky-400 hover:text-sky-700 dark:hover:text-sky-300 transition-colors"
171+
>
172+
<svg
173+
className="w-4 h-4"
174+
fill="none"
175+
viewBox="0 0 24 24"
176+
stroke="currentColor"
177+
strokeWidth={1.75}
178+
>
179+
<path
180+
strokeLinecap="round"
181+
strokeLinejoin="round"
182+
d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5m8.25 3v6.75m0 0-3-3m3 3 3-3M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"
183+
/>
184+
</svg>
185+
Ship skills with your library and get listed here
186+
<svg
187+
className="w-3.5 h-3.5"
188+
fill="none"
189+
viewBox="0 0 24 24"
190+
stroke="currentColor"
191+
strokeWidth={2}
192+
>
193+
<path
194+
strokeLinecap="round"
195+
strokeLinejoin="round"
196+
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
197+
/>
198+
</svg>
199+
</Link>
200+
</div>
159201
</div>
160202
</div>
161203

@@ -414,6 +456,62 @@ function IntentRegistryPage() {
414456
</div>
415457
</>
416458
))}
459+
460+
{/* Ship your skills CTA */}
461+
<div className="mt-16 mb-4 rounded-xl border border-gray-200 dark:border-gray-800 bg-gradient-to-br from-sky-50/50 to-white dark:from-sky-950/20 dark:to-gray-900 p-8 text-center">
462+
<div className="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-sky-100 dark:bg-sky-950/60 mb-4">
463+
<svg
464+
className="w-5 h-5 text-sky-600 dark:text-sky-400"
465+
fill="none"
466+
viewBox="0 0 24 24"
467+
stroke="currentColor"
468+
strokeWidth={1.75}
469+
>
470+
<path
471+
strokeLinecap="round"
472+
strokeLinejoin="round"
473+
d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5m8.25 3v6.75m0 0-3-3m3 3 3-3M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z"
474+
/>
475+
</svg>
476+
</div>
477+
<h3 className="text-lg font-bold text-gray-900 dark:text-gray-100 mb-2">
478+
Ship Agent Skills with your library
479+
</h3>
480+
<p className="text-sm text-gray-500 dark:text-gray-400 max-w-lg mx-auto mb-5">
481+
Add the{' '}
482+
<code className="px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-xs font-mono">
483+
tanstack-intent
484+
</code>{' '}
485+
keyword to your package, generate skills with the CLI, and your
486+
library appears here automatically.
487+
</p>
488+
<Link
489+
to="/$libraryId/$version/docs/$"
490+
params={
491+
{
492+
libraryId: 'intent',
493+
version: 'latest',
494+
_splat: 'registry',
495+
} as never
496+
}
497+
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-lg bg-sky-600 hover:bg-sky-700 dark:bg-sky-500 dark:hover:bg-sky-600 text-white text-sm font-medium transition-colors"
498+
>
499+
Get started
500+
<svg
501+
className="w-4 h-4"
502+
fill="none"
503+
viewBox="0 0 24 24"
504+
stroke="currentColor"
505+
strokeWidth={2}
506+
>
507+
<path
508+
strokeLinecap="round"
509+
strokeLinejoin="round"
510+
d="M13.5 4.5 21 12m0 0-7.5 7.5M21 12H3"
511+
/>
512+
</svg>
513+
</Link>
514+
</div>
417515
</div>
418516
</div>
419517
)

0 commit comments

Comments
 (0)