Skip to content

Commit 5b17ed5

Browse files
authored
feat: improved frameworks page (#556)
* feat: improved frameworks page * prettier * clean up missing types after main merge * add missing commits code * fix sidebar font-color lookup
1 parent db6d835 commit 5b17ed5

11 files changed

Lines changed: 313 additions & 41 deletions

File tree

src/components/DocsLayout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ import { VersionSelect } from './VersionSelect'
2020
const getFrameworkTextColor = (frameworkValue: string | undefined) => {
2121
if (!frameworkValue) return 'text-gray-500'
2222
const framework = frameworkOptions.find((f) => f.value === frameworkValue)
23-
if (!framework) return 'text-gray-500'
24-
// Convert bg-* to text-* color class
25-
return framework.color.replace('bg-', 'text-')
23+
24+
return framework?.fontColor ?? 'text-gray-500'
2625
}
2726

2827
// Create context for width toggle state

src/components/FrameworkCard.tsx

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { Link } from '@tanstack/react-router'
2+
import { twMerge } from 'tailwind-merge'
3+
import { FaCheck, FaCopy } from 'react-icons/fa'
4+
import { Library } from '~/libraries'
5+
import { getFrameworkOptions } from '~/libraries/frameworks'
6+
import { useCopyButton } from '~/components/CopyMarkdownButton'
7+
import { useToast } from '~/components/ToastProvider'
8+
9+
export function FrameworkCard({
10+
framework,
11+
libraryId,
12+
packageName,
13+
index,
14+
library,
15+
}: {
16+
framework: ReturnType<typeof getFrameworkOptions>[number]
17+
libraryId: string
18+
packageName: string
19+
index: number
20+
library: Library
21+
}) {
22+
const { notify } = useToast()
23+
const [copied, onCopyClick] = useCopyButton(async () => {
24+
await navigator.clipboard.writeText(packageName)
25+
notify(
26+
<div>
27+
<div className="font-medium">Copied package name</div>
28+
<div className="text-gray-500 dark:text-gray-400 text-xs">
29+
{packageName} copied to clipboard
30+
</div>
31+
</div>
32+
)
33+
})
34+
35+
const hasCustomInstallPath = !!library.installPath
36+
const installationPath = library.installPath
37+
? library.installPath
38+
.replace('$framework', framework.value)
39+
.replace('$libraryId', libraryId)
40+
: 'installation'
41+
42+
// Add framework hash fragment only for default installation pages (when installPath is not defined)
43+
// Link component adds the # automatically, so we just pass the value without #
44+
const installationHash = !hasCustomInstallPath ? framework.value : undefined
45+
46+
return (
47+
<div
48+
className={twMerge(
49+
'border-2 border-gray-200 dark:border-gray-800/50 rounded-xl',
50+
'shadow-md p-6 transition-all duration-300 ease-out',
51+
'bg-white/90 dark:bg-black/40 backdrop-blur-sm',
52+
'hover:shadow-xl hover:-translate-y-1',
53+
'flex flex-col gap-4 group',
54+
'min-h-[180px]',
55+
'relative'
56+
)}
57+
style={{
58+
zIndex: index,
59+
willChange: 'transform',
60+
}}
61+
>
62+
<Link
63+
from="/$libraryId/$version/docs"
64+
to="./$"
65+
params={{
66+
_splat: installationPath,
67+
}}
68+
hash={installationHash}
69+
className="flex flex-col flex-1 gap-4"
70+
>
71+
{/* Framework Logo */}
72+
<div className="flex-shrink-0 flex justify-center">
73+
<img
74+
src={framework.logo}
75+
alt={framework.label}
76+
className="w-16 h-16 object-contain transition-transform duration-300 group-hover:scale-110"
77+
/>
78+
</div>
79+
80+
{/* Framework Name */}
81+
<div className="text-center flex-1 flex items-center justify-center">
82+
<div className="text-lg font-semibold text-gray-900 dark:text-gray-100">
83+
{framework.label}
84+
</div>
85+
</div>
86+
</Link>
87+
88+
{/* Package Name with Copy Button - Bottom of Card */}
89+
<div className="pt-2 border-t border-gray-200 dark:border-gray-800 space-y-2">
90+
<div className="flex items-center justify-center gap-2">
91+
<code className="text-xs text-gray-600 dark:text-gray-400 font-mono">
92+
{packageName}
93+
</code>
94+
<button
95+
onClick={(e) => {
96+
e.preventDefault()
97+
e.stopPropagation()
98+
onCopyClick(e)
99+
}}
100+
className="flex-shrink-0 p-1.5 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors rounded hover:bg-gray-100 dark:hover:bg-gray-800"
101+
title="Copy package name"
102+
>
103+
{copied ? (
104+
<FaCheck className="w-3 h-3 text-green-600 dark:text-green-400" />
105+
) : (
106+
<FaCopy className="w-3 h-3" />
107+
)}
108+
</button>
109+
</div>
110+
<Link
111+
from="/$libraryId/$version/docs"
112+
to="./$"
113+
params={{
114+
_splat: installationPath,
115+
}}
116+
hash={installationHash}
117+
className="block w-full text-center text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 underline transition-colors"
118+
>
119+
Full install instructions
120+
</Link>
121+
</div>
122+
123+
{/* Accent indicator */}
124+
<div
125+
className={twMerge(
126+
'absolute bottom-0 left-0 right-0 h-1 rounded-b-xl',
127+
'transition-opacity duration-300',
128+
'opacity-0 group-hover:opacity-100',
129+
framework.color
130+
)}
131+
/>
132+
</div>
133+
)
134+
}

src/libraries/frameworks.tsx

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,78 @@
1-
import reactLogo from '../images/react-logo.svg'
2-
import vueLogo from '../images/vue-logo.svg'
31
import angularLogo from '../images/angular-logo.svg'
4-
import svelteLogo from '../images/svelte-logo.svg'
5-
import solidLogo from '../images/solid-logo.svg'
62
import jsLogo from '../images/js-logo.svg'
3+
import litLogo from '../images/lit-logo.svg'
4+
import qwikLogo from '../images/qwik-logo.svg'
5+
import preactLogo from '../images/preact-logo.svg'
6+
import reactLogo from '../images/react-logo.svg'
7+
import solidLogo from '../images/solid-logo.svg'
8+
import svelteLogo from '../images/svelte-logo.svg'
9+
import vueLogo from '../images/vue-logo.svg'
710
import type { Framework } from './types'
811

912
export const frameworkOptions = [
10-
{ label: 'React', value: 'react', logo: reactLogo, color: 'bg-blue-500' },
11-
{ label: 'Vue', value: 'vue', logo: vueLogo, color: 'bg-green-500' },
13+
{
14+
label: 'React',
15+
value: 'react',
16+
logo: reactLogo,
17+
color: 'bg-blue-500',
18+
fontColor: 'text-sky-500',
19+
},
20+
{
21+
label: 'Preact',
22+
value: 'preact',
23+
logo: preactLogo,
24+
color: 'bg-purple-500',
25+
fontColor: 'text-purple-500',
26+
},
27+
{
28+
label: 'Vue',
29+
value: 'vue',
30+
logo: vueLogo,
31+
color: 'bg-green-500',
32+
fontColor: 'text-green-500',
33+
},
1234
{
1335
label: 'Angular',
1436
value: 'angular',
1537
logo: angularLogo,
1638
color: 'bg-red-500',
39+
fontColor: 'text-fuchsia-500',
40+
},
41+
{
42+
label: 'Solid',
43+
value: 'solid',
44+
logo: solidLogo,
45+
color: 'bg-blue-600',
46+
fontColor: 'text-blue-600',
47+
},
48+
{
49+
label: 'Lit',
50+
value: 'lit',
51+
logo: litLogo,
52+
color: 'bg-emerald-500',
53+
fontColor: 'text-emerald-500',
1754
},
1855
{
1956
label: 'Svelte',
2057
value: 'svelte',
2158
logo: svelteLogo,
22-
color: 'bg-orange-500',
59+
color: 'bg-orange-600',
60+
fontColor: 'text-orange-600',
61+
},
62+
{
63+
label: 'Qwik',
64+
value: 'qwik',
65+
logo: qwikLogo,
66+
color: 'bg-indigo-500',
67+
fontColor: 'text-indigo-500',
68+
},
69+
{
70+
label: 'Vanilla',
71+
value: 'vanilla',
72+
logo: jsLogo,
73+
color: 'bg-yellow-500',
74+
fontColor: 'text-yellow-500',
2375
},
24-
{ label: 'Solid', value: 'solid', logo: solidLogo, color: 'bg-blue-600' },
25-
{ label: 'Vanilla', value: 'vanilla', logo: jsLogo, color: 'bg-yellow-500' },
2676
] as const
2777

2878
export function getFrameworkOptions(frameworkStrs: Framework[]) {

src/libraries/maintainers.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const allMaintainers: Maintainer[] = [
114114
isCoreMaintainer: true,
115115
avatar: 'https://github.com/jherr.png',
116116
github: 'jherr',
117-
creatorOf: ['create-tsrouter-app'],
117+
creatorOf: ['ai', 'create-tsrouter-app'],
118118
frameworkExpertise: ['react'],
119119
specialties: ['Templates'],
120120
workshopsAvailable: true,
@@ -168,7 +168,7 @@ export const allMaintainers: Maintainer[] = [
168168
name: 'Alem Tuzlak',
169169
avatar: 'https://github.com/AlemTuzlak.png',
170170
github: 'AlemTuzlak',
171-
creatorOf: ['devtools'],
171+
creatorOf: ['ai', 'devtools'],
172172
contributorOf: ['pacer', 'form'],
173173
frameworkExpertise: ['react'],
174174
specialties: ['DevTools', 'Routers', 'Vite Plugins'],
@@ -273,6 +273,31 @@ export const allMaintainers: Maintainer[] = [
273273
website: 'https://www.linkedin.com/in/jonghyeonko',
274274
},
275275
},
276+
{
277+
name: 'Sarah Gerrard',
278+
avatar: 'https://github.com/ladybluenotes.png',
279+
github: 'ladybluenotes',
280+
contributorOf: [
281+
'ai',
282+
'config',
283+
'db',
284+
'devtools',
285+
'form',
286+
'pacer',
287+
'query',
288+
'ranger',
289+
'router',
290+
'start',
291+
'store',
292+
'table',
293+
'virtual',
294+
],
295+
frameworkExpertise: ['react', 'solid'],
296+
specialties: ['Documentation'],
297+
social: {
298+
bluesky: 'https://bsky.app/profile/ladybluenotes.dev',
299+
},
300+
},
276301
{
277302
name: 'Riccardo Perra',
278303
avatar:

src/libraries/query.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export const queryProject = {
3232
frameworks: ['react', 'solid', 'vue', 'svelte', 'angular'],
3333
scarfId: '53afb586-3934-4624-a37a-e680c1528e17',
3434
defaultDocs: 'framework/react/overview',
35+
installPath: 'framework/$framework/installation',
3536
legacyPackages: ['react-query'],
3637
handleRedirects: (href: string) => {
3738
handleRedirects(

src/libraries/router.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const routerProject = {
3737
frameworks: ['react', 'solid'],
3838
scarfId: '3d14fff2-f326-4929-b5e1-6ecf953d24f4',
3939
defaultDocs: 'framework/react/overview',
40+
installPath: 'framework/$framework/installation',
4041
legacyPackages: ['react-location'],
4142
hideCodesandboxUrl: true,
4243
showVercelUrl: false,

src/libraries/start.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const startProject = {
3333
embedEditor: 'codesandbox',
3434
frameworks: ['react', 'solid'],
3535
defaultDocs: 'framework/react/overview',
36+
installPath: 'framework/$framework/build-from-scratch',
3637
scarfId: 'b6e2134f-e805-401d-95c3-2a7765d49a3d',
3738
showNetlifyUrl: true,
3839
showCloudflareUrl: true,

src/libraries/store.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const storeProject = {
2727
colorFrom: 'from-twine-500',
2828
colorTo: 'to-twine-700',
2929
textColor: 'text-twine-700',
30-
frameworks: ['react', 'solid', 'svelte', 'vue', 'angular'],
30+
frameworks: ['react', 'preact', 'solid', 'svelte', 'vue', 'angular'],
3131
scarfId: '302d0fef-cb3f-43c6-b45c-f055b9745edb',
3232
defaultDocs: 'overview',
3333
menu: [

src/libraries/table.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const tableProject = {
4141
],
4242
scarfId: 'dc8b39e1-3fe9-4f3a-8e56-d4e2cf420a9e',
4343
defaultDocs: 'introduction',
44+
corePackageName: 'table-core',
4445
legacyPackages: ['react-table'],
4546
handleRedirects: (href) => {
4647
handleRedirects(

src/libraries/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import * as React from 'react'
22

33
export type Framework =
4-
| 'react'
5-
| 'vue'
64
| 'angular'
7-
| 'svelte'
5+
| 'lit'
6+
| 'preact'
7+
| 'qwik'
8+
| 'react'
89
| 'solid'
10+
| 'svelte'
911
| 'vanilla'
12+
| 'vue'
1013

1114
export type Library = {
1215
id:
@@ -62,6 +65,8 @@ export type Library = {
6265
visible?: boolean
6366
// Legacy npm packages (non-@tanstack scope) to include in stats
6467
legacyPackages?: string[]
68+
installPath?: string
69+
corePackageName?: string
6570
}
6671

6772
export type LibraryMenuItem = {

0 commit comments

Comments
 (0)