diff --git a/src/components/DocsLayout.tsx b/src/components/DocsLayout.tsx index 9260ffe19..92eaeaf50 100644 --- a/src/components/DocsLayout.tsx +++ b/src/components/DocsLayout.tsx @@ -20,9 +20,8 @@ import { VersionSelect } from './VersionSelect' const getFrameworkTextColor = (frameworkValue: string | undefined) => { if (!frameworkValue) return 'text-gray-500' const framework = frameworkOptions.find((f) => f.value === frameworkValue) - if (!framework) return 'text-gray-500' - // Convert bg-* to text-* color class - return framework.color.replace('bg-', 'text-') + + return framework?.fontColor ?? 'text-gray-500' } // Create context for width toggle state diff --git a/src/components/FrameworkCard.tsx b/src/components/FrameworkCard.tsx new file mode 100644 index 000000000..2dfb3983c --- /dev/null +++ b/src/components/FrameworkCard.tsx @@ -0,0 +1,134 @@ +import { Link } from '@tanstack/react-router' +import { twMerge } from 'tailwind-merge' +import { FaCheck, FaCopy } from 'react-icons/fa' +import { Library } from '~/libraries' +import { getFrameworkOptions } from '~/libraries/frameworks' +import { useCopyButton } from '~/components/CopyMarkdownButton' +import { useToast } from '~/components/ToastProvider' + +export function FrameworkCard({ + framework, + libraryId, + packageName, + index, + library, +}: { + framework: ReturnType[number] + libraryId: string + packageName: string + index: number + library: Library +}) { + const { notify } = useToast() + const [copied, onCopyClick] = useCopyButton(async () => { + await navigator.clipboard.writeText(packageName) + notify( +
+
Copied package name
+
+ {packageName} copied to clipboard +
+
+ ) + }) + + const hasCustomInstallPath = !!library.installPath + const installationPath = library.installPath + ? library.installPath + .replace('$framework', framework.value) + .replace('$libraryId', libraryId) + : 'installation' + + // Add framework hash fragment only for default installation pages (when installPath is not defined) + // Link component adds the # automatically, so we just pass the value without # + const installationHash = !hasCustomInstallPath ? framework.value : undefined + + return ( +
+ + {/* Framework Logo */} +
+ {framework.label} +
+ + {/* Framework Name */} +
+
+ {framework.label} +
+
+ + + {/* Package Name with Copy Button - Bottom of Card */} +
+
+ + {packageName} + + +
+ + Full install instructions + +
+ + {/* Accent indicator */} +
+
+ ) +} diff --git a/src/libraries/frameworks.tsx b/src/libraries/frameworks.tsx index 8d4bbb31f..f54b22962 100644 --- a/src/libraries/frameworks.tsx +++ b/src/libraries/frameworks.tsx @@ -1,28 +1,78 @@ -import reactLogo from '../images/react-logo.svg' -import vueLogo from '../images/vue-logo.svg' import angularLogo from '../images/angular-logo.svg' -import svelteLogo from '../images/svelte-logo.svg' -import solidLogo from '../images/solid-logo.svg' import jsLogo from '../images/js-logo.svg' +import litLogo from '../images/lit-logo.svg' +import qwikLogo from '../images/qwik-logo.svg' +import preactLogo from '../images/preact-logo.svg' +import reactLogo from '../images/react-logo.svg' +import solidLogo from '../images/solid-logo.svg' +import svelteLogo from '../images/svelte-logo.svg' +import vueLogo from '../images/vue-logo.svg' import type { Framework } from './types' export const frameworkOptions = [ - { label: 'React', value: 'react', logo: reactLogo, color: 'bg-blue-500' }, - { label: 'Vue', value: 'vue', logo: vueLogo, color: 'bg-green-500' }, + { + label: 'React', + value: 'react', + logo: reactLogo, + color: 'bg-blue-500', + fontColor: 'text-sky-500', + }, + { + label: 'Preact', + value: 'preact', + logo: preactLogo, + color: 'bg-purple-500', + fontColor: 'text-purple-500', + }, + { + label: 'Vue', + value: 'vue', + logo: vueLogo, + color: 'bg-green-500', + fontColor: 'text-green-500', + }, { label: 'Angular', value: 'angular', logo: angularLogo, color: 'bg-red-500', + fontColor: 'text-fuchsia-500', + }, + { + label: 'Solid', + value: 'solid', + logo: solidLogo, + color: 'bg-blue-600', + fontColor: 'text-blue-600', + }, + { + label: 'Lit', + value: 'lit', + logo: litLogo, + color: 'bg-emerald-500', + fontColor: 'text-emerald-500', }, { label: 'Svelte', value: 'svelte', logo: svelteLogo, - color: 'bg-orange-500', + color: 'bg-orange-600', + fontColor: 'text-orange-600', + }, + { + label: 'Qwik', + value: 'qwik', + logo: qwikLogo, + color: 'bg-indigo-500', + fontColor: 'text-indigo-500', + }, + { + label: 'Vanilla', + value: 'vanilla', + logo: jsLogo, + color: 'bg-yellow-500', + fontColor: 'text-yellow-500', }, - { label: 'Solid', value: 'solid', logo: solidLogo, color: 'bg-blue-600' }, - { label: 'Vanilla', value: 'vanilla', logo: jsLogo, color: 'bg-yellow-500' }, ] as const export function getFrameworkOptions(frameworkStrs: Framework[]) { diff --git a/src/libraries/maintainers.ts b/src/libraries/maintainers.ts index 120f59bf5..4fd90ebe5 100644 --- a/src/libraries/maintainers.ts +++ b/src/libraries/maintainers.ts @@ -114,7 +114,7 @@ export const allMaintainers: Maintainer[] = [ isCoreMaintainer: true, avatar: 'https://github.com/jherr.png', github: 'jherr', - creatorOf: ['create-tsrouter-app'], + creatorOf: ['ai', 'create-tsrouter-app'], frameworkExpertise: ['react'], specialties: ['Templates'], workshopsAvailable: true, @@ -168,7 +168,7 @@ export const allMaintainers: Maintainer[] = [ name: 'Alem Tuzlak', avatar: 'https://github.com/AlemTuzlak.png', github: 'AlemTuzlak', - creatorOf: ['devtools'], + creatorOf: ['ai', 'devtools'], contributorOf: ['pacer', 'form'], frameworkExpertise: ['react'], specialties: ['DevTools', 'Routers', 'Vite Plugins'], @@ -273,6 +273,31 @@ export const allMaintainers: Maintainer[] = [ website: 'https://www.linkedin.com/in/jonghyeonko', }, }, + { + name: 'Sarah Gerrard', + avatar: 'https://github.com/ladybluenotes.png', + github: 'ladybluenotes', + contributorOf: [ + 'ai', + 'config', + 'db', + 'devtools', + 'form', + 'pacer', + 'query', + 'ranger', + 'router', + 'start', + 'store', + 'table', + 'virtual', + ], + frameworkExpertise: ['react', 'solid'], + specialties: ['Documentation'], + social: { + bluesky: 'https://bsky.app/profile/ladybluenotes.dev', + }, + }, { name: 'Riccardo Perra', avatar: diff --git a/src/libraries/query.tsx b/src/libraries/query.tsx index 9d3bf6544..d6d8150ed 100644 --- a/src/libraries/query.tsx +++ b/src/libraries/query.tsx @@ -32,6 +32,7 @@ export const queryProject = { frameworks: ['react', 'solid', 'vue', 'svelte', 'angular'], scarfId: '53afb586-3934-4624-a37a-e680c1528e17', defaultDocs: 'framework/react/overview', + installPath: 'framework/$framework/installation', legacyPackages: ['react-query'], handleRedirects: (href: string) => { handleRedirects( diff --git a/src/libraries/router.tsx b/src/libraries/router.tsx index 3d32e922c..e19caea17 100644 --- a/src/libraries/router.tsx +++ b/src/libraries/router.tsx @@ -37,6 +37,7 @@ export const routerProject = { frameworks: ['react', 'solid'], scarfId: '3d14fff2-f326-4929-b5e1-6ecf953d24f4', defaultDocs: 'framework/react/overview', + installPath: 'framework/$framework/installation', legacyPackages: ['react-location'], hideCodesandboxUrl: true, showVercelUrl: false, diff --git a/src/libraries/start.tsx b/src/libraries/start.tsx index bb3ad2ead..931e9c30d 100644 --- a/src/libraries/start.tsx +++ b/src/libraries/start.tsx @@ -33,6 +33,7 @@ export const startProject = { embedEditor: 'codesandbox', frameworks: ['react', 'solid'], defaultDocs: 'framework/react/overview', + installPath: 'framework/$framework/build-from-scratch', scarfId: 'b6e2134f-e805-401d-95c3-2a7765d49a3d', showNetlifyUrl: true, showCloudflareUrl: true, diff --git a/src/libraries/store.tsx b/src/libraries/store.tsx index 7beb2734a..6f03132f3 100644 --- a/src/libraries/store.tsx +++ b/src/libraries/store.tsx @@ -27,7 +27,7 @@ export const storeProject = { colorFrom: 'from-twine-500', colorTo: 'to-twine-700', textColor: 'text-twine-700', - frameworks: ['react', 'solid', 'svelte', 'vue', 'angular'], + frameworks: ['react', 'preact', 'solid', 'svelte', 'vue', 'angular'], scarfId: '302d0fef-cb3f-43c6-b45c-f055b9745edb', defaultDocs: 'overview', menu: [ diff --git a/src/libraries/table.tsx b/src/libraries/table.tsx index 686b42603..4d63d70a6 100644 --- a/src/libraries/table.tsx +++ b/src/libraries/table.tsx @@ -41,6 +41,7 @@ export const tableProject = { ], scarfId: 'dc8b39e1-3fe9-4f3a-8e56-d4e2cf420a9e', defaultDocs: 'introduction', + corePackageName: 'table-core', legacyPackages: ['react-table'], handleRedirects: (href) => { handleRedirects( diff --git a/src/libraries/types.ts b/src/libraries/types.ts index 9c42ea579..04721d3de 100644 --- a/src/libraries/types.ts +++ b/src/libraries/types.ts @@ -1,12 +1,15 @@ import * as React from 'react' export type Framework = - | 'react' - | 'vue' | 'angular' - | 'svelte' + | 'lit' + | 'preact' + | 'qwik' + | 'react' | 'solid' + | 'svelte' | 'vanilla' + | 'vue' export type Library = { id: @@ -62,6 +65,8 @@ export type Library = { visible?: boolean // Legacy npm packages (non-@tanstack scope) to include in stats legacyPackages?: string[] + installPath?: string + corePackageName?: string } export type LibraryMenuItem = { diff --git a/src/routes/$libraryId/$version.docs.framework.index.tsx b/src/routes/$libraryId/$version.docs.framework.index.tsx index c08ad2f12..45a729400 100644 --- a/src/routes/$libraryId/$version.docs.framework.index.tsx +++ b/src/routes/$libraryId/$version.docs.framework.index.tsx @@ -1,16 +1,36 @@ -import { Link, createFileRoute } from '@tanstack/react-router' +import { createFileRoute } from '@tanstack/react-router' import { twMerge } from 'tailwind-merge' +import { FaDiscord, FaGithub } from 'react-icons/fa' import { DocContainer } from '~/components/DocContainer' import { DocTitle } from '~/components/DocTitle' import { getLibrary } from '~/libraries' import { getFrameworkOptions } from '~/libraries/frameworks' +import { FrameworkCard } from '~/components/FrameworkCard' export const Route = createFileRoute('/$libraryId/$version/docs/framework/')({ component: RouteComponent, }) +function getPackageName( + frameworkValue: string, + libraryId: string, + library: ReturnType +): string { + if (frameworkValue === 'vanilla') { + // For vanilla, use corePackageName if provided, otherwise just libraryId + const coreName = library.corePackageName || libraryId + return `@tanstack/${coreName}` + } + // Special case: Angular Query uses experimental package + if (frameworkValue === 'angular' && libraryId === 'query') { + return `@tanstack/angular-query-experimental` + } + // For other frameworks, use {framework}-{libraryId} pattern (e.g., @tanstack/react-table) + return `@tanstack/${frameworkValue}-${libraryId}` +} + function RouteComponent() { - const { libraryId } = Route.useParams() + const { libraryId, version } = Route.useParams() const library = getLibrary(libraryId) const frameworks = getFrameworkOptions(library.frameworks) @@ -28,32 +48,67 @@ function RouteComponent() { Supported {library.name} Frameworks
-
+
+ + {/* Framework Cards Grid */}
-
    - {frameworks.map((framework) => ( -
  • - - {framework.label} - TanStack {framework.label}{' '} - {library.name.replace('TanStack ', '')} - -
  • - ))} -
+ {frameworks.map((framework, i) => { + const packageName = getPackageName( + framework.value, + libraryId, + library + ) + return ( + + ) + })}
+ + {/* Call to Action Message */} +
+
+

+ Want to add support for another framework? +

+

+ We'd love to help you create a framework adapter for{' '} + {library.name}. Join our community to discuss implementation + details and get support. +

+ +
+
+