From 2ff9f9014ff96ba55460ebe28602d60cd4493b8e Mon Sep 17 00:00:00 2001
From: Kevin Van Cott
Date: Tue, 28 Apr 2026 21:11:36 -0500
Subject: [PATCH] feat: new libraries/framework pages
---
src/components/LibraryCard.tsx | 17 ++-
src/images/alpine-logo.svg | 1 +
src/libraries/frameworks.tsx | 22 ++--
src/libraries/libraries.ts | 5 +-
src/libraries/types.ts | 1 +
src/routeTree.gen.ts | 21 ++++
src/routes/-libraries-utils.ts | 43 +++++++
src/routes/libraries.tsx | 61 +++++++---
src/routes/libraries_.$framework.tsx | 161 +++++++++++++++++++++++++++
src/utils/npm-packages.ts | 1 +
10 files changed, 304 insertions(+), 29 deletions(-)
create mode 100644 src/images/alpine-logo.svg
create mode 100644 src/routes/-libraries-utils.ts
create mode 100644 src/routes/libraries_.$framework.tsx
diff --git a/src/components/LibraryCard.tsx b/src/components/LibraryCard.tsx
index a7bbf7a85..fe0c28df6 100644
--- a/src/components/LibraryCard.tsx
+++ b/src/components/LibraryCard.tsx
@@ -1,24 +1,35 @@
-import { Link } from '@tanstack/react-router'
+import { Link, useParams } from '@tanstack/react-router'
import { Library } from '~/libraries'
+import { frameworkOptions } from '~/libraries/frameworks'
import { twMerge } from 'tailwind-merge'
export default function LibraryCard({
library,
index = 0,
isGeneric = false,
+ namePrefix,
}: {
library: Library
index?: number
isGeneric?: boolean
+ namePrefix?: string
}) {
const isExternal = library.to?.startsWith('http')
const Component = isExternal ? 'a' : Link
const props = isExternal
? { href: library.to, target: '_blank', rel: 'noopener noreferrer' }
: { to: library.to ?? '#' }
+ const params = useParams({ strict: false })
+ const frameworkPrefix =
+ namePrefix ??
+ frameworkOptions.find((framework) => framework.value === params.framework)
+ ?.label
const hasTanStackPrefix = library.name.startsWith('TanStack ')
const nameWithoutPrefix = library.name.replace('TanStack ', '')
+ const displayName = frameworkPrefix
+ ? `${frameworkPrefix}-${nameWithoutPrefix}`
+ : nameWithoutPrefix
return (
- {nameWithoutPrefix}
+ {displayName}
>
) : (
@@ -93,7 +104,7 @@ export default function LibraryCard({
: 'text-current'
}
>
- {nameWithoutPrefix}
+ {displayName}
)}
diff --git a/src/images/alpine-logo.svg b/src/images/alpine-logo.svg
new file mode 100644
index 000000000..932c1bdaa
--- /dev/null
+++ b/src/images/alpine-logo.svg
@@ -0,0 +1 @@
+
diff --git a/src/libraries/frameworks.tsx b/src/libraries/frameworks.tsx
index f54b22962..38605e842 100644
--- a/src/libraries/frameworks.tsx
+++ b/src/libraries/frameworks.tsx
@@ -1,3 +1,4 @@
+import alpineLogo from '../images/alpine-logo.svg'
import angularLogo from '../images/angular-logo.svg'
import jsLogo from '../images/js-logo.svg'
import litLogo from '../images/lit-logo.svg'
@@ -45,13 +46,6 @@ export const frameworkOptions = [
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',
@@ -66,6 +60,20 @@ export const frameworkOptions = [
color: 'bg-indigo-500',
fontColor: 'text-indigo-500',
},
+ {
+ label: 'Lit',
+ value: 'lit',
+ logo: litLogo,
+ color: 'bg-emerald-500',
+ fontColor: 'text-emerald-500',
+ },
+ {
+ label: 'Alpine',
+ value: 'alpine',
+ logo: alpineLogo,
+ color: 'bg-slate-500',
+ fontColor: 'text-slate-500',
+ },
{
label: 'Vanilla',
value: 'vanilla',
diff --git a/src/libraries/libraries.ts b/src/libraries/libraries.ts
index 53a2fa926..a68835646 100644
--- a/src/libraries/libraries.ts
+++ b/src/libraries/libraries.ts
@@ -321,12 +321,13 @@ export const table: LibrarySlim = {
repo: 'tanstack/table',
frameworks: [
'angular',
- 'lit',
- 'qwik',
'react',
'solid',
'svelte',
'vue',
+ 'qwik',
+ 'lit',
+ 'alpine',
'vanilla',
],
latestVersion: 'v8',
diff --git a/src/libraries/types.ts b/src/libraries/types.ts
index b6debb7b3..12c9a6f4b 100644
--- a/src/libraries/types.ts
+++ b/src/libraries/types.ts
@@ -2,6 +2,7 @@ import * as React from 'react'
export type Framework =
| 'angular'
+ | 'alpine'
| 'lit'
| 'preact'
| 'qwik'
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index 103d0b56c..1e88095e5 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -57,6 +57,7 @@ import { Route as PartnersPartnerRouteImport } from './routes/partners.$partner'
import { Route as OauthTokenRouteImport } from './routes/oauth/token'
import { Route as OauthRegisterRouteImport } from './routes/oauth/register'
import { Route as OauthAuthorizeRouteImport } from './routes/oauth/authorize'
+import { Route as LibrariesFrameworkRouteImport } from './routes/libraries_.$framework'
import { Route as BuilderDocsRouteImport } from './routes/builder.docs'
import { Route as BlogSplatRouteImport } from './routes/blog.$'
import { Route as AuthSignoutRouteImport } from './routes/auth/signout'
@@ -390,6 +391,11 @@ const OauthAuthorizeRoute = OauthAuthorizeRouteImport.update({
path: '/oauth/authorize',
getParentRoute: () => rootRouteImport,
} as any)
+const LibrariesFrameworkRoute = LibrariesFrameworkRouteImport.update({
+ id: '/libraries_/$framework',
+ path: '/libraries/$framework',
+ getParentRoute: () => rootRouteImport,
+} as any)
const BuilderDocsRoute = BuilderDocsRouteImport.update({
id: '/docs',
path: '/docs',
@@ -925,6 +931,7 @@ export interface FileRoutesByFullPath {
'/auth/signout': typeof AuthSignoutRoute
'/blog/$': typeof BlogSplatRoute
'/builder/docs': typeof BuilderDocsRoute
+ '/libraries/$framework': typeof LibrariesFrameworkRoute
'/oauth/authorize': typeof OauthAuthorizeRoute
'/oauth/register': typeof OauthRegisterRoute
'/oauth/token': typeof OauthTokenRoute
@@ -1059,6 +1066,7 @@ export interface FileRoutesByTo {
'/auth/signout': typeof AuthSignoutRoute
'/blog/$': typeof BlogSplatRoute
'/builder/docs': typeof BuilderDocsRoute
+ '/libraries/$framework': typeof LibrariesFrameworkRoute
'/oauth/authorize': typeof OauthAuthorizeRoute
'/oauth/register': typeof OauthRegisterRoute
'/oauth/token': typeof OauthTokenRoute
@@ -1200,6 +1208,7 @@ export interface FileRoutesById {
'/auth/signout': typeof AuthSignoutRoute
'/blog/$': typeof BlogSplatRoute
'/builder/docs': typeof BuilderDocsRoute
+ '/libraries_/$framework': typeof LibrariesFrameworkRoute
'/oauth/authorize': typeof OauthAuthorizeRoute
'/oauth/register': typeof OauthRegisterRoute
'/oauth/token': typeof OauthTokenRoute
@@ -1344,6 +1353,7 @@ export interface FileRouteTypes {
| '/auth/signout'
| '/blog/$'
| '/builder/docs'
+ | '/libraries/$framework'
| '/oauth/authorize'
| '/oauth/register'
| '/oauth/token'
@@ -1478,6 +1488,7 @@ export interface FileRouteTypes {
| '/auth/signout'
| '/blog/$'
| '/builder/docs'
+ | '/libraries/$framework'
| '/oauth/authorize'
| '/oauth/register'
| '/oauth/token'
@@ -1618,6 +1629,7 @@ export interface FileRouteTypes {
| '/auth/signout'
| '/blog/$'
| '/builder/docs'
+ | '/libraries_/$framework'
| '/oauth/authorize'
| '/oauth/register'
| '/oauth/token'
@@ -1747,6 +1759,7 @@ export interface RootRouteChildren {
AuthCliRoute: typeof AuthCliRoute
AuthPopupSuccessRoute: typeof AuthPopupSuccessRoute
AuthSignoutRoute: typeof AuthSignoutRoute
+ LibrariesFrameworkRoute: typeof LibrariesFrameworkRoute
OauthAuthorizeRoute: typeof OauthAuthorizeRoute
OauthRegisterRoute: typeof OauthRegisterRoute
OauthTokenRoute: typeof OauthTokenRoute
@@ -2139,6 +2152,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof OauthAuthorizeRouteImport
parentRoute: typeof rootRouteImport
}
+ '/libraries_/$framework': {
+ id: '/libraries_/$framework'
+ path: '/libraries/$framework'
+ fullPath: '/libraries/$framework'
+ preLoaderRoute: typeof LibrariesFrameworkRouteImport
+ parentRoute: typeof rootRouteImport
+ }
'/builder/docs': {
id: '/builder/docs'
path: '/docs'
@@ -3041,6 +3061,7 @@ const rootRouteChildren: RootRouteChildren = {
AuthCliRoute: AuthCliRoute,
AuthPopupSuccessRoute: AuthPopupSuccessRoute,
AuthSignoutRoute: AuthSignoutRoute,
+ LibrariesFrameworkRoute: LibrariesFrameworkRoute,
OauthAuthorizeRoute: OauthAuthorizeRoute,
OauthRegisterRoute: OauthRegisterRoute,
OauthTokenRoute: OauthTokenRoute,
diff --git a/src/routes/-libraries-utils.ts b/src/routes/-libraries-utils.ts
new file mode 100644
index 000000000..50dd51ec8
--- /dev/null
+++ b/src/routes/-libraries-utils.ts
@@ -0,0 +1,43 @@
+import { libraries, type Framework, type LibrarySlim } from '~/libraries'
+
+export function getVisibleLibraries() {
+ return libraries.filter((library) => library.to && library.visible !== false)
+}
+
+export function orderLibrariesForBrowse(
+ allLibraries: Array,
+) {
+ const others = allLibraries.filter(
+ (library) =>
+ library.id !== 'ranger' &&
+ library.id !== 'config' &&
+ library.id !== 'react-charts',
+ )
+ const ranger = allLibraries.filter((library) => library.id === 'ranger')
+ const config = allLibraries.filter((library) => library.id === 'config')
+
+ const devtoolsIndex = others.findIndex((library) => library.id === 'devtools')
+
+ if (devtoolsIndex === -1) {
+ return [...others, ...config, ...ranger]
+ }
+
+ return [
+ ...others.slice(0, devtoolsIndex + 1),
+ ...config,
+ ...others.slice(devtoolsIndex + 1),
+ ...ranger,
+ ]
+}
+
+export function getFrameworkLibraryCounts(allLibraries: Array) {
+ const counts = {} as Partial>
+
+ for (const library of allLibraries) {
+ for (const framework of library.frameworks) {
+ counts[framework] = (counts[framework] ?? 0) + 1
+ }
+ }
+
+ return counts
+}
diff --git a/src/routes/libraries.tsx b/src/routes/libraries.tsx
index 0fa34d790..d0cc06df4 100644
--- a/src/routes/libraries.tsx
+++ b/src/routes/libraries.tsx
@@ -1,8 +1,14 @@
-import { createFileRoute } from '@tanstack/react-router'
+import { Link, createFileRoute } from '@tanstack/react-router'
import * as React from 'react'
-import { libraries, Library } from '~/libraries'
+import { type Library } from '~/libraries'
+import { frameworkOptions } from '~/libraries/frameworks'
import { reactChartsProject } from '~/libraries/react-charts'
import LibraryCard from '~/components/LibraryCard'
+import {
+ getFrameworkLibraryCounts,
+ getVisibleLibraries,
+ orderLibrariesForBrowse,
+} from './-libraries-utils'
export const Route = createFileRoute('/libraries')({
component: LibrariesPage,
@@ -20,22 +26,12 @@ export const Route = createFileRoute('/libraries')({
})
function LibrariesPage() {
- const allLibraries = libraries.filter((d) => d.to && d.visible !== false)
- const others = allLibraries.filter(
- (l) => l.id !== 'ranger' && l.id !== 'config' && l.id !== 'react-charts',
+ const allLibraries = getVisibleLibraries()
+ const ordered = orderLibrariesForBrowse(allLibraries)
+ const frameworkCounts = getFrameworkLibraryCounts(allLibraries)
+ const frameworksWithLibraries = frameworkOptions.filter(
+ (framework) => (frameworkCounts[framework.value] ?? 0) > 0,
)
- const ranger = allLibraries.filter((l) => l.id === 'ranger')
- const config = allLibraries.filter((l) => l.id === 'config')
-
- // Find devtools index in others to insert config after it
- const devtoolsIndex = others.findIndex((l) => l.id === 'devtools')
- const ordered = [
- ...others.slice(0, devtoolsIndex + 1),
- ...config,
- ...others.slice(devtoolsIndex + 1),
- ...ranger,
- ]
-
const deprecatedLibraries = [reactChartsProject]
return (
@@ -45,6 +41,37 @@ function LibrariesPage() {
Browse all TanStack libraries.
+
+ Browse by Your Framework
+
+ {frameworksWithLibraries.map((framework) => {
+ const count = frameworkCounts[framework.value] ?? 0
+
+ return (
+
+

+
{framework.label}
+
+ {count} {count === 1 ? 'library' : 'libraries'}
+
+
+ )
+ })}
+
+
+
{
+ const frameworkOption = getFrameworkOption(params.framework)
+
+ if (!frameworkOption) {
+ throw notFound()
+ }
+
+ return {
+ framework: frameworkOption,
+ }
+ },
+ head: ({ loaderData }) => ({
+ meta: [
+ {
+ title: loaderData?.framework
+ ? `TanStack for ${loaderData.framework.label} - Libraries`
+ : 'TanStack Libraries',
+ },
+ {
+ name: 'description',
+ content: loaderData?.framework
+ ? `Browse TanStack libraries with ${loaderData.framework.label} adapters.`
+ : 'Browse TanStack libraries by framework.',
+ },
+ ],
+ }),
+ component: LibrariesFrameworkPage,
+})
+
+function LibrariesFrameworkPage() {
+ const { framework } = Route.useParams()
+ const { framework: frameworkOption } = Route.useLoaderData()
+
+ const allLibraries = getVisibleLibraries()
+ const frameworkCounts = getFrameworkLibraryCounts(allLibraries)
+ const frameworksWithLibraries = frameworkOptions.filter(
+ (option) => (frameworkCounts[option.value] ?? 0) > 0,
+ )
+ const filteredLibraries = orderLibrariesForBrowse(
+ allLibraries.filter((library) =>
+ library.frameworks.includes(framework as Framework),
+ ),
+ )
+ const count = filteredLibraries.length
+
+ return (
+
+
+
+ All Libraries
+
+ /
+
+ {frameworkOption.label}
+
+
+
+
+
+
+

+
+ TanStack for {frameworkOption.label}!
+
+
+
+ Browse TanStack libraries with {frameworkOption.label} adapters.
+
+
+
+
+ {count} {count === 1 ? 'library' : 'libraries'}
+
+
+
+
+ Browse by Your Framework
+
+ {frameworksWithLibraries.map((option) => {
+ const optionCount = frameworkCounts[option.value] ?? 0
+ const isActive = option.value === frameworkOption.value
+
+ return (
+
+

+
{option.label}
+
+ {optionCount} {optionCount === 1 ? 'library' : 'libraries'}
+
+
+ )
+ })}
+
+
+
+ {filteredLibraries.length > 0 ? (
+
+ {filteredLibraries.map((library, i) => {
+ return (
+
+ )
+ })}
+
+ ) : (
+
+ No TanStack libraries currently list a {frameworkOption.label}{' '}
+ adapter.
+
+ )}
+
+ )
+}
+
+function getFrameworkOption(framework: string) {
+ return frameworkOptions.find((option) => option.value === framework)
+}
diff --git a/src/utils/npm-packages.ts b/src/utils/npm-packages.ts
index 53141a741..a30c48950 100644
--- a/src/utils/npm-packages.ts
+++ b/src/utils/npm-packages.ts
@@ -27,6 +27,7 @@ export const frameworkMeta: Record
=
vue: { name: 'Vue', color: '#42B883' },
svelte: { name: 'Svelte', color: '#FF3E00' },
angular: { name: 'Angular', color: '#DD0031' },
+ alpine: { name: 'Alpine', color: '#77C1D2' },
lit: { name: 'Lit', color: '#325CFF' },
qwik: { name: 'Qwik', color: '#18B6F6' },
vanilla: { name: 'Vanilla', color: '#F7DF1E' },