Skip to content

Commit 997f39b

Browse files
committed
feat: improved frameworks page
1 parent e5f8fef commit 997f39b

8 files changed

Lines changed: 229 additions & 27 deletions

File tree

src/components/FrameworkCard.tsx

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

src/components/Markdown.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ const CustomHeading = ({
3030
// Convert children to array and strip any inner anchor (native 'a' or MarkdownLink)
3131
const childrenArray = React.Children.toArray(children)
3232
const sanitizedChildren = childrenArray.map((child) => {
33-
if (React.isValidElement(child) && (child.type === 'a' || child.type === MarkdownLink)) {
33+
if (
34+
React.isValidElement(child) &&
35+
(child.type === 'a' || child.type === MarkdownLink)
36+
) {
3437
// replace anchor child with its own children so outer anchor remains the only link
3538
return child.props.children ?? null
3639
}
@@ -45,7 +48,10 @@ const CustomHeading = ({
4548

4649
if (id) {
4750
return (
48-
<a href={`#${id}`} className={`anchor-heading *:scroll-my-20 *:lg:scroll-my-4`}>
51+
<a
52+
href={`#${id}`}
53+
className={`anchor-heading *:scroll-my-20 *:lg:scroll-my-4`}
54+
>
4955
{heading}
5056
</a>
5157
)
@@ -55,8 +61,7 @@ const CustomHeading = ({
5561
}
5662

5763
const makeHeading =
58-
(type: HeadingLevel) =>
59-
(props: HTMLProps<HTMLHeadingElement>) =>
64+
(type: HeadingLevel) => (props: HTMLProps<HTMLHeadingElement>) =>
6065
(
6166
<CustomHeading
6267
Comp={type}

src/libraries/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ export type Library = {
124124
frameworks: Framework[]
125125
scarfId?: string
126126
defaultDocs?: string
127+
installPath?: string
128+
corePackageName?: string
127129
handleRedirects?: (href: string) => void
128130
hideCodesandboxUrl?: true
129131
hideStackblitzUrl?: true

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
handleRedirects: (href: string) => {
3637
handleRedirects(
3738
reactQueryV3List,

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
hideCodesandboxUrl: true,
4142
showVercelUrl: false,
4243
showNetlifyUrl: true,

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/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
handleRedirects: (href) => {
4546
handleRedirects(
4647
reactTableV7List,

src/routes/$libraryId/$version.docs.framework.index.tsx

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,35 @@
1-
import { Link, createFileRoute } from '@tanstack/react-router'
1+
import { createFileRoute } from '@tanstack/react-router'
22
import { twMerge } from 'tailwind-merge'
3+
import { FaDiscord, FaGithub } from 'react-icons/fa'
34
import { DocContainer } from '~/components/DocContainer'
45
import { DocTitle } from '~/components/DocTitle'
56
import { getFrameworkOptions, getLibrary } from '~/libraries'
7+
import { FrameworkCard } from '~/components/FrameworkCard'
68

79
export const Route = createFileRoute('/$libraryId/$version/docs/framework/')({
810
component: RouteComponent,
911
})
1012

13+
function getPackageName(
14+
frameworkValue: string,
15+
libraryId: string,
16+
library: ReturnType<typeof getLibrary>
17+
): string {
18+
if (frameworkValue === 'vanilla') {
19+
// For vanilla, use corePackageName if provided, otherwise just libraryId
20+
const coreName = library.corePackageName || libraryId
21+
return `@tanstack/${coreName}`
22+
}
23+
// Special case: Angular Query uses experimental package
24+
if (frameworkValue === 'angular' && libraryId === 'query') {
25+
return `@tanstack/angular-query-experimental`
26+
}
27+
// For other frameworks, use {framework}-{libraryId} pattern (e.g., @tanstack/react-table)
28+
return `@tanstack/${frameworkValue}-${libraryId}`
29+
}
30+
1131
function RouteComponent() {
12-
const { libraryId } = Route.useParams()
32+
const { libraryId, version } = Route.useParams()
1333
const library = getLibrary(libraryId)
1434

1535
const frameworks = getFrameworkOptions(library.frameworks)
@@ -27,32 +47,68 @@ function RouteComponent() {
2747
<DocTitle>Supported {library.name} Frameworks</DocTitle>
2848
<div className="h-4" />
2949
<div className="h-px bg-gray-500 opacity-20" />
30-
<div className="h-4" />
50+
<div className="h-6" />
51+
52+
{/* Framework Cards Grid */}
3153
<div
3254
className={twMerge(
33-
'prose prose-gray prose-sm prose-p:leading-7 dark:prose-invert max-w-none',
34-
'styled-markdown-content'
55+
'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4'
3556
)}
3657
>
37-
<ul className="text-lg">
38-
{frameworks.map((framework) => (
39-
<li key={framework.value}>
40-
<Link
41-
to={`./${framework.value}`}
42-
className="flex items-center gap-2"
43-
>
44-
<img
45-
src={framework.logo}
46-
alt={framework.label}
47-
className="w-4 h-4 p-0 m-0"
48-
/>
49-
TanStack {framework.label}{' '}
50-
{library.name.replace('TanStack ', '')}
51-
</Link>
52-
</li>
53-
))}
54-
</ul>
58+
{frameworks.map((framework, i) => {
59+
const packageName = getPackageName(
60+
framework.value,
61+
libraryId,
62+
library
63+
)
64+
return (
65+
<FrameworkCard
66+
key={framework.value}
67+
framework={framework}
68+
libraryId={libraryId}
69+
version={version}
70+
packageName={packageName}
71+
index={i}
72+
library={library}
73+
/>
74+
)
75+
})}
5576
</div>
77+
78+
{/* Call to Action Message */}
79+
<div className="mt-12 pt-8 border-t border-gray-200 dark:border-gray-800">
80+
<div className="text-center max-w-2xl mx-auto">
81+
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-3">
82+
Want to add support for another framework?
83+
</h3>
84+
<p className="text-gray-600 dark:text-gray-400 mb-6">
85+
We'd love to help you create a framework adapter for{' '}
86+
{library.name}. Join our community to discuss implementation
87+
details and get support.
88+
</p>
89+
<div className="flex flex-wrap justify-center gap-4">
90+
<a
91+
href="https://tlinz.com/discord"
92+
target="_blank"
93+
rel="noopener noreferrer"
94+
className="inline-flex items-center gap-2 px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
95+
>
96+
<FaDiscord className="w-5 h-5" />
97+
Join Discord
98+
</a>
99+
<a
100+
href={`https://github.com/${library.repo}/discussions`}
101+
target="_blank"
102+
rel="noopener noreferrer"
103+
className="inline-flex items-center gap-2 px-6 py-3 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 rounded-lg hover:bg-gray-800 dark:hover:bg-gray-200 transition-colors"
104+
>
105+
<FaGithub className="w-5 h-5" />
106+
Start Discussion
107+
</a>
108+
</div>
109+
</div>
110+
</div>
111+
56112
<div className="h-24" />
57113
</div>
58114
</div>

0 commit comments

Comments
 (0)