From 3493f7ccab95c1e14ed8ae32a4ffc6c23ff7e58b Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 3 Aug 2026 07:34:35 -0500 Subject: [PATCH 1/5] feat: update to table v9 and redesign table landing page --- package.json | 2 +- pnpm-lock.yaml | 31 +- src/components/landing/TableLanding.tsx | 601 +++++++++++++++--------- src/components/landing/codeExamples.ts | 16 +- src/libraries/libraries.ts | 9 +- src/libraries/maintainers.ts | 2 +- src/libraries/table.tsx | 46 +- src/routes/admin/audit.tsx | 8 +- src/routes/admin/github-stats.tsx | 8 +- src/routes/admin/logins.tsx | 12 +- src/routes/admin/roles.$roleId.tsx | 8 +- src/routes/admin/roles.index.tsx | 8 +- src/routes/admin/users.tsx | 12 +- 13 files changed, 453 insertions(+), 310 deletions(-) diff --git a/package.json b/package.json index 14449fa4f..556f5c9b1 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@tanstack/react-router-ssr-query": "1.167.1", "@tanstack/react-start": "1.168.26", "@tanstack/react-start-client": "1.168.14", - "@tanstack/react-table": "^8.21.3", + "@tanstack/react-table": "9.0.0-beta.78", "@tanstack/workflow-core": "0.0.4", "@tanstack/workflow-runtime": "0.0.3", "@tanstack/workflow-store-drizzle-postgres": "0.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70cfa082d..98441e61d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -127,8 +127,8 @@ importers: specifier: 1.168.14 version: 1.168.14(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + specifier: 9.0.0-beta.78 + version: 9.0.0-beta.78(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/workflow-core': specifier: 0.0.4 version: 0.0.4(@opentelemetry/api@1.9.1) @@ -3560,12 +3560,11 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/react-table@8.21.3': - resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} - engines: {node: '>=12'} + '@tanstack/react-table@9.0.0-beta.78': + resolution: {integrity: sha512-jEnsR1mWQy0qCFO8TfZkZiGUa/6u2iEoCy1ymAaQY4xq+uoDMjPPOQ8+08+3A726R/DyGzrd9Aj1v0450UYtlg==} + engines: {node: '>=20'} peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' + react: '>=18' '@tanstack/redact@0.0.19': resolution: {integrity: sha512-fsEaG6sCooTQawyXff83FnLaZjhGoKoAWLPPTkEib/lqxATeloZc7Rq3TDRn9nQE1HAkQfuMjjCsYNRW3swRnQ==} @@ -3659,9 +3658,9 @@ packages: '@tanstack/store@0.9.3': resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} - '@tanstack/table-core@8.21.3': - resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} - engines: {node: '>=12'} + '@tanstack/table-core@9.0.0-beta.78': + resolution: {integrity: sha512-PbVO8JCaBKD6/hipdpFaq6t7PWA/VbioJClSR6fWFmPbQdT0C3pJHdne1o24XBY62BqlHc8h/twY6nRsLj2DIw==} + engines: {node: '>=20'} '@tanstack/virtual-file-routes@1.162.0': resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==} @@ -9570,11 +9569,13 @@ snapshots: react-dom: 19.2.3(react@19.2.3) use-sync-external-store: 1.6.0(react@19.2.3) - '@tanstack/react-table@8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@tanstack/react-table@9.0.0-beta.78(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@tanstack/table-core': 8.21.3 + '@tanstack/react-store': 0.11.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/table-core': 9.0.0-beta.78 react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - react-dom '@tanstack/redact@0.0.19(vite@8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0))': optionalDependencies: @@ -9703,7 +9704,9 @@ snapshots: '@tanstack/store@0.9.3': {} - '@tanstack/table-core@8.21.3': {} + '@tanstack/table-core@9.0.0-beta.78': + dependencies: + '@tanstack/store': 0.11.0 '@tanstack/virtual-file-routes@1.162.0': {} diff --git a/src/components/landing/TableLanding.tsx b/src/components/landing/TableLanding.tsx index b7f948d27..cce2a3f0a 100644 --- a/src/components/landing/TableLanding.tsx +++ b/src/components/landing/TableLanding.tsx @@ -1,24 +1,32 @@ import * as React from 'react' import { - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, + cellSelectionFeature, + columnFilteringFeature, + columnVisibilityFeature, + createFilteredRowModel, + createPaginatedRowModel, + createSortedRowModel, + globalFilteringFeature, + rowPaginationFeature, + rowSelectionFeature, + rowSortingFeature, + sortFn_alphanumeric, + sortFn_basic, + tableFeatures, + useTable, + type Cell, type ColumnFiltersState, type ColumnDef, + type ColumnVisibilityState, type PaginationState, type RowSelectionState, type SortingState, - type VisibilityState, } from '@tanstack/react-table' +import { useHotkeys } from '@tanstack/react-hotkeys' import { - ArrowsHorizontalIcon, + BracketsCurlyIcon, CaretDownIcon, CaretUpIcon, - FunnelIcon, - GridNineIcon, MagnifyingGlassIcon, RowsIcon, SlidersHorizontalIcon, @@ -34,7 +42,7 @@ import { } from './LibraryLanding' const tablePrompt = - 'Build a TanStack Table data grid for a TypeScript app. Keep it headless: define column definitions, row models, sorting, filtering, pagination, selection, visibility, and controlled state without prescribing markup. Render semantic table elements and synchronize table state to the URL or server only where the product needs it.' + 'Build a TanStack Table V9 data grid for a TypeScript app. Keep it headless. Define a stable tableFeatures object with only the feature plugins, create*RowModel slots, and function registries the product needs. Use TanStack Store-backed table state, selectors, or table.Subscribe for reactive reads, and external atoms only for slices the app must own. Render semantic table elements and synchronize state to the URL or server only where the product needs it.' type TableIssue = { id: string @@ -45,8 +53,20 @@ type TableIssue = { } type StatusFilter = 'all' | TableIssue['status'] -type Surface = 'table' | 'cards' | 'dense' -type StateOwner = 'component' | 'url' | 'server' +type StateMode = 'selected' | 'subscribed' | 'external' + +const tableWorkbenchFeatures = tableFeatures({ + cellSelectionFeature, + columnFilteringFeature, + globalFilteringFeature, + rowSortingFeature, + rowPaginationFeature, + rowSelectionFeature, + columnVisibilityFeature, + filteredRowModel: createFilteredRowModel(), + sortedRowModel: createSortedRowModel(), + paginatedRowModel: createPaginatedRowModel(), +}) const tableRows: Array = [ { @@ -114,54 +134,94 @@ const statusFilters: Array<{ label: string; value: StatusFilter }> = [ { label: 'Shipped', value: 'shipped' }, ] -const rowModels = [ - { label: 'Core', code: 'getCoreRowModel()', rows: '8 rows', icon: RowsIcon }, +const tableResponsibilities = ['State', 'Row processing', 'Typed APIs'] as const + +const developerControls = [ + 'Markup & semantics', + 'Styles & components', + 'Events & interactions', +] as const + +const stateModes: Record< + StateMode, + { code: string; note: string; path: string } +> = { + selected: { + code: 'const sorting = table.state.sorting', + note: 'Select the state a component needs when creating its table instance.', + path: 'table store → selected state → component', + }, + subscribed: { + code: ' state.sorting}>', + note: 'Move a reactive read to the smallest part of the tree that renders it.', + path: 'sorting atom → subscription island → UI', + }, + external: { + code: 'atoms: { sorting: sortingAtom }', + note: 'Give an external TanStack Store atom ownership when other systems share the slice.', + path: 'external atom → table feature → app', + }, +} + +const rowModelStages = [ + { + code: 'automatic', + label: 'Core', + note: 'data to rows', + }, { + code: 'createFilteredRowModel()', label: 'Filter', - code: 'getFilteredRowModel()', - rows: '5 rows', - icon: FunnelIcon, + note: 'column + global', }, { + code: 'createGroupedRowModel()', + label: 'Group', + note: 'grouped rows', + }, + { + code: 'createSortedRowModel()', label: 'Sort', - code: 'getSortedRowModel()', - rows: '5 rows', - icon: ArrowsHorizontalIcon, + note: 'ordered rows', + }, + { + code: 'createExpandedRowModel()', + label: 'Expand', + note: 'visible sub-rows', }, { - label: 'Page', - code: 'getPaginationRowModel()', - rows: '4 rows', - icon: StackIcon, + code: 'createPaginatedRowModel()', + label: 'Paginate', + note: 'current page', }, ] as const -const stateOwners: Record< - StateOwner, - { code: string; note: string; path: string } -> = { - component: { - code: 'onSortingChange: setSorting', - note: 'Fast local interaction with no external coordination.', - path: 'header → table state → row model', +const tableToolbox = [ + { + label: 'Custom features', + code: 'tableFeatures({ densityFeature })', + detail: + 'Add state, options, and APIs through the same extension system used by built-in features.', }, - url: { - code: 'navigate({ search: { sort } })', - note: 'Shareable, restorable state for product navigation.', - path: 'header → URL search → table state', + { + label: 'Reusable tables', + code: 'createTableHook({ features, ... })', + detail: + 'Share typed features, options, column helpers, and registered components across a product.', }, - server: { - code: 'manualSorting: true', - note: 'Table reports the next sort state; your data layer fetches the ordered rows.', - path: 'header → controlled sorting → API query', + { + label: 'Devtools', + code: 'useTanStackTableDevtools(table)', + detail: + 'Inspect table state and derived data in supported framework integrations instead of logging internals.', }, -} +] as const export default function TableLanding() { return ( } libraryId="table" prompt={tablePrompt} @@ -170,51 +230,59 @@ export default function TableLanding() {
-
- - -
+ + + + + +
) } function TableWorkbench() { + const gridRef = React.useRef(null) const [globalFilter, setGlobalFilter] = React.useState('') const [statusFilter, setStatusFilter] = React.useState('all') const [sorting, setSorting] = React.useState([ { id: 'score', desc: true }, ]) const [columnVisibility, setColumnVisibility] = - React.useState({ owner: false }) + React.useState({ owner: false }) const [rowSelection, setRowSelection] = React.useState({}) const [pagination, setPagination] = React.useState({ pageIndex: 0, @@ -226,10 +294,13 @@ function TableWorkbench() { [statusFilter], ) - const columns = React.useMemo>>( + const columns = React.useMemo< + Array> + >( () => [ { id: 'select', + enableCellSelection: false, header: ({ table }) => ( { if (input) { - input.indeterminate = table.getIsSomePageRowsSelected() + input.indeterminate = + table.getIsSomePageRowsSelected() && + !table.getIsAllPageRowsSelected() } }} type="checkbox" @@ -260,29 +333,38 @@ function TableWorkbench() { /> ), }, - { accessorKey: 'project', header: 'Project' }, - { accessorKey: 'owner', header: 'Owner' }, + { + accessorKey: 'project', + header: 'Project', + sortFn: sortFn_alphanumeric, + }, + { + accessorKey: 'owner', + header: 'Owner', + sortFn: sortFn_alphanumeric, + }, { accessorKey: 'status', header: 'Status', cell: ({ getValue }) => ( ()} /> ), + filterFn: (row, columnId, value) => + row.getValue(columnId) === value, + sortFn: sortFn_alphanumeric, }, - { accessorKey: 'score', header: 'Score' }, + { accessorKey: 'score', header: 'Score', sortFn: sortFn_basic }, ], [], ) - const table = useReactTable({ + const table = useTable({ + features: tableWorkbenchFeatures, columns, data: tableRows, + enableCellSelection: true, enableRowSelection: true, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), getRowId: (row) => row.id, - getSortedRowModel: getSortedRowModel(), globalFilterFn: (row, _columnId, filterValue) => { const search = String(filterValue).trim().toLowerCase() if (!search) return true @@ -306,8 +388,40 @@ function TableWorkbench() { }, }) + useHotkeys( + [ + { hotkey: 'ArrowUp', callback: () => table.moveCellSelection('up') }, + { hotkey: 'ArrowDown', callback: () => table.moveCellSelection('down') }, + { hotkey: 'ArrowLeft', callback: () => table.moveCellSelection('left') }, + { + hotkey: 'ArrowRight', + callback: () => table.moveCellSelection('right'), + }, + { + hotkey: 'Shift+ArrowUp', + callback: () => table.extendCellSelection('up'), + }, + { + hotkey: 'Shift+ArrowDown', + callback: () => table.extendCellSelection('down'), + }, + { + hotkey: 'Shift+ArrowLeft', + callback: () => table.extendCellSelection('left'), + }, + { + hotkey: 'Shift+ArrowRight', + callback: () => table.extendCellSelection('right'), + }, + { hotkey: 'Mod+A', callback: () => table.selectAllCells() }, + { hotkey: 'Escape', callback: () => table.resetCellSelection(true) }, + ], + { preventDefault: true, target: gridRef }, + ) + const filteredRows = table.getFilteredRowModel().rows.length const selectedRows = table.getSelectedRowModel().rows.length + const selectedCells = table.getSelectedCellCount() return ( @@ -381,7 +495,23 @@ function TableWorkbench() { -
+
+ +
+ +
{table.getHeaderGroups().map((headerGroup) => ( @@ -407,10 +537,7 @@ function TableWorkbench() { onClick={header.column.getToggleSortingHandler()} type="button" > - {flexRender( - header.column.columnDef.header, - header.getContext(), - )} + {sort === 'asc' ? ( ))} @@ -486,7 +621,7 @@ function TableWorkbench() { - page {table.getState().pagination.pageIndex + 1} /{' '} + page {table.state.pagination.pageIndex + 1} /{' '} {Math.max(table.getPageCount(), 1)} @@ -495,178 +630,88 @@ function TableWorkbench() { ) } -function RowPipeline() { +function OwnershipModel() { return ( -
- {rowModels.map((model, index) => { - const Icon = model.icon - return ( -
-
- ) - })} -
- ) -} - -function SurfaceLab() { - const [surface, setSurface] = React.useState('table') - const rows = tableRows.slice(0, 4) - - return ( - -
-
- {(['table', 'cards', 'dense'] as const).map((item) => ( - - ))} -
-
-
- {surface === 'table' ? ( -
- {rows.map((row) => ( -
- - {row.id} - - - {row.project} - - - {row.score} - -
- ))} -
- ) : surface === 'cards' ? ( -
- {rows.map((row) => ( + +
+
+

+ TanStack Table handles +

+

+ Headless table logic +

+
+ {tableResponsibilities.map((responsibility) => (
- -

- {row.project} -

-

- {row.owner} · score {row.score} -

+ {responsibility}
))}
- ) : ( -
- {rows.map((row) => ( +
+ +
+

+ You control +

+

+ 100% of the rendered result +

+
+ {developerControls.map((control) => (
- - - {row.id} - - - {row.project} - - - {row.status} - + {control}
))}
- )} +
- - ) -} -function TableVirtualBoundary() { - return ( -
-
-

- Table -

-

- Owns columns, row models, feature state, and the data handed to your - renderer. -

-
-
-

- Virtual -

-

- Measures the scroll surface and limits which rows or columns are - mounted. Pair it with Table when rendering volume demands it. -

+
+
+

+ Works with your stack +

+

+ Use any component library or design system, including your own. +

+
+ + headless logic → your UI +
-
+ ) } function StateSwitchboard() { - const [owner, setOwner] = React.useState('url') - const selected = stateOwners[owner] + const [mode, setMode] = React.useState('subscribed') + const selected = stateModes[mode] return ( - +
- {(['component', 'url', 'server'] as const).map((item) => ( + {(['selected', 'subscribed', 'external'] as const).map((item) => (
{headerGroup.headers.map((header) => ( ))} @@ -310,7 +308,7 @@ export default function SimpleTable() { {row.getVisibleCells().map((cell) => ( ))} diff --git a/src/libraries/libraries.ts b/src/libraries/libraries.ts index 92aa984c3..69ffa3ba3 100644 --- a/src/libraries/libraries.ts +++ b/src/libraries/libraries.ts @@ -292,9 +292,9 @@ export const table: LibrarySlim = { ...categoryStyles.ui, name: 'TanStack Table', to: '/table', - tagline: 'Headless UI for building powerful tables & datagrids', + tagline: 'Headless, type-safe table and data-grid infrastructure', description: - 'Supercharge your tables or build a datagrid from scratch in any framework while retaining 100% control over markup and styles.', + 'Build tables and data grids with feature-level tree shaking, reactive state, fast row models, and complete control over markup and styles.', badge: undefined, repo: 'tanstack/table', frameworks: [ @@ -306,14 +306,13 @@ export const table: LibrarySlim = { 'solid', 'svelte', 'vue', - 'qwik', 'lit', 'alpine', 'vanilla', ], - latestVersion: 'v8', + latestVersion: 'v9', latestBranch: 'main', - availableVersions: ['v8', 'beta'], + availableVersions: ['v9', 'v8', 'beta'], scarfId: 'dc8b39e1-3fe9-4f3a-8e56-d4e2cf420a9e', defaultDocs: 'overview', sitemap: { diff --git a/src/libraries/maintainers.ts b/src/libraries/maintainers.ts index 4b384c2d9..be684e229 100644 --- a/src/libraries/maintainers.ts +++ b/src/libraries/maintainers.ts @@ -145,7 +145,7 @@ export const allMaintainers: Maintainer[] = [ maintainerOf: ['table', 'store'], contributorOf: ['virtual'], consultantOf: ['query'], - frameworkExpertise: ['react', 'preact', 'solid'], + frameworkExpertise: ['react', 'preact', 'solid', 'octane'], specialties: ['Data Grids', 'Performance', 'Dashboards'], social: { twitter: 'https://x.com/kevinvancott', diff --git a/src/libraries/table.tsx b/src/libraries/table.tsx index 825d9c28b..fe9b39bed 100644 --- a/src/libraries/table.tsx +++ b/src/libraries/table.tsx @@ -1,5 +1,5 @@ import { - PersonSimpleIcon, + BracketsCurlyIcon, LightningIcon, GearIcon, } from '@phosphor-icons/react' @@ -10,7 +10,7 @@ const textStyles = 'text-category-ui' export const tableProject = { ...table, - description: `Supercharge your tables or build a datagrid from scratch for TS/JS, React, Vue, Solid, Svelte, Qwik, Angular, and Lit while retaining 100% control over markup and styles.`, + description: `Headless, type-safe table and data-grid infrastructure with feature-level tree shaking, reactive state, fast row models, and adapters for React, Preact, Vue, Solid, Svelte, Angular, Ember, Lit, Alpine, and Octane.`, latestBranch: 'main', defaultDocs: 'introduction', legacyPackages: ['react-table'], @@ -46,52 +46,54 @@ export const tableProject = { ], featureHighlights: [ { - title: 'Designed for zero design', + title: '100% Control', icon: (
- +
), description: (
- What good is a powerful table if that super hip designer you just - hired can't work their UI magic on it?{' '} + TanStack Table provides the state, row processing, and typed APIs + without prescribing your markup or styles. Having 100% control of your + code matters more than ever.{' '} - TanStack Table is headless by design + Your semantics, components, design system, interactions, and source + code stay yours. Use any component library or design system, + including your own. - , which means 100% control down to the very smallest HTML tag, - component, class and style. Pixel Perfection? Go for it!
), }, { - title: 'Big Power, Small Package', + title: 'Reactive by Design', icon: , description: (
- Don't be fooled by the small bundle size. TanStack Table is a - workhorse. It's built to materialize, filter, sort, group, aggregate, - paginate and display massive data sets using a very small API surface. - Wire up your new or existing tables and{' '} + Every registered state slice is backed by TanStack Store, whose + fine-grained reactivity is built on alien-signals. Read a selected + snapshot, subscribe exactly where state is rendered, or own a slice + with an external atom.{' '} - watch your users become instantly more productive + Fine-grained reactivity keeps unrelated parts of a large table out + of the render path. - .
), }, { - title: 'Extensible', + title: 'Built to Extend', icon: , description: (
- TanStack table ships with excellent defaults to get you off the ground - as fast as possible, but nothing is stopping you from{' '} + Custom features use the same extension points as Table's built-ins. + Compose shared options with tableOptions, build app-level table + systems with createTableHook, and inspect live instances with the + supported Devtools integration.{' '} - customizing and overriding literally everything to your liking + Scale from one headless table to a typed table platform for your + whole product. - . Feeling tenacious enough to build your own Sheets/Excel/AirTable - clone? Be our guest 😉
), }, diff --git a/src/routes/admin/audit.tsx b/src/routes/admin/audit.tsx index 5dc8c153e..76b0b0c87 100644 --- a/src/routes/admin/audit.tsx +++ b/src/routes/admin/audit.tsx @@ -12,12 +12,12 @@ import { TableRow, TableCell, } from '~/components/TableComponents' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type ColumnDef, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy' import * as v from 'valibot' import { listAuditLogs } from '~/utils/audit.functions' import { ShieldIcon, CaretDownIcon, CaretUpIcon } from '@phosphor-icons/react' diff --git a/src/routes/admin/github-stats.tsx b/src/routes/admin/github-stats.tsx index bf952b3c7..8a36b7f72 100644 --- a/src/routes/admin/github-stats.tsx +++ b/src/routes/admin/github-stats.tsx @@ -10,12 +10,12 @@ import { TableRow, TableCell, } from '~/components/TableComponents' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type ColumnDef, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy' import { listGitHubStatsCache, refreshGitHubStats, diff --git a/src/routes/admin/logins.tsx b/src/routes/admin/logins.tsx index ae3d072e2..e00e3a38f 100644 --- a/src/routes/admin/logins.tsx +++ b/src/routes/admin/logins.tsx @@ -12,13 +12,15 @@ import { TableRow, TableCell, } from '~/components/TableComponents' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type ColumnDef, - type Column, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { + LegacyColumn as Column, + LegacyColumnDef as ColumnDef, +} from '@tanstack/react-table/legacy' import * as v from 'valibot' import { listLoginHistory } from '~/utils/audit.functions' import { SignInIcon } from '@phosphor-icons/react' diff --git a/src/routes/admin/roles.$roleId.tsx b/src/routes/admin/roles.$roleId.tsx index 27a020fb5..85cd06bad 100644 --- a/src/routes/admin/roles.$roleId.tsx +++ b/src/routes/admin/roles.$roleId.tsx @@ -4,12 +4,12 @@ import { useRemoveUsersFromRole } from '~/utils/mutations' import { useQuery } from '@tanstack/react-query' import { getRole, getUsersWithRole } from '~/utils/roles.functions' import { useCurrentUserQuery } from '~/hooks/useCurrentUser' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type ColumnDef, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy' import { ArrowLeftIcon, LockIcon, diff --git a/src/routes/admin/roles.index.tsx b/src/routes/admin/roles.index.tsx index 7bd8b6df7..ded0d002f 100644 --- a/src/routes/admin/roles.index.tsx +++ b/src/routes/admin/roles.index.tsx @@ -14,12 +14,12 @@ import { useState, useMemo, useCallback } from 'react' import { useQuery, keepPreviousData } from '@tanstack/react-query' import { useCreateRole, useUpdateRole, useDeleteRole } from '~/utils/mutations' import { listRoles, sendTestModeratorEmail } from '~/utils/roles.functions' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type Row, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { LegacyRow as Row } from '@tanstack/react-table/legacy' import { PencilSimpleLineIcon, PlusIcon, diff --git a/src/routes/admin/users.tsx b/src/routes/admin/users.tsx index 970386855..65177d8cb 100644 --- a/src/routes/admin/users.tsx +++ b/src/routes/admin/users.tsx @@ -12,13 +12,15 @@ import { TableRow, TableCell, } from '~/components/TableComponents' +import { flexRender } from '@tanstack/react-table' import { - useReactTable, getCoreRowModel, - flexRender, - type ColumnDef, - type Column, -} from '@tanstack/react-table' + useLegacyTable as useReactTable, +} from '@tanstack/react-table/legacy' +import type { + LegacyColumn as Column, + LegacyColumnDef as ColumnDef, +} from '@tanstack/react-table/legacy' import { useUpdateUserCapabilities, useAdminSetAdsDisabled, From 5ef68aa71fb4c2279d7a30ce06b88545401d6896 Mon Sep 17 00:00:00 2001 From: Kevin Van Cott Date: Mon, 3 Aug 2026 21:16:26 -0500 Subject: [PATCH 2/5] remove contributors link to maintainers --- src/components/Footer.tsx | 1 - src/components/Navbar.tsx | 6 - src/components/landing/TableLanding.tsx | 407 ++++++++++++------------ 3 files changed, 206 insertions(+), 208 deletions(-) diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index c7037b0ee..a27a57f73 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -42,7 +42,6 @@ const FOOTER_COLUMNS: Array = [ { label: 'Discord', to: 'https://tlinz.com/discord' }, { label: 'GitHub', to: 'https://github.com/TanStack' }, { label: 'Maintainers', to: '/maintainers' }, - { label: 'Contributors', to: '/maintainers' }, { label: 'Showcase', to: '/showcase' }, ], }, diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index aba068c51..77856d377 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -207,12 +207,6 @@ const NAV_GROUPS = [ description: 'Meet the people maintaining the stack.', icon: CodeIcon, }, - { - label: 'Contributors', - to: '/maintainers', - description: 'Core, library, and community contributors.', - icon: UsersIcon, - }, { label: 'Showcase', to: '/showcase', diff --git a/src/components/landing/TableLanding.tsx b/src/components/landing/TableLanding.tsx index cce2a3f0a..47524fb2a 100644 --- a/src/components/landing/TableLanding.tsx +++ b/src/components/landing/TableLanding.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import * as React from "react"; import { cellSelectionFeature, columnFilteringFeature, @@ -21,8 +21,8 @@ import { type PaginationState, type RowSelectionState, type SortingState, -} from '@tanstack/react-table' -import { useHotkeys } from '@tanstack/react-hotkeys' +} from "@tanstack/react-table"; +import { useHotkeys } from "@tanstack/react-hotkeys"; import { BracketsCurlyIcon, CaretDownIcon, @@ -31,29 +31,29 @@ import { RowsIcon, SlidersHorizontalIcon, StackIcon, -} from '@phosphor-icons/react' -import { Badge } from '~/ui/Badge' +} from "@phosphor-icons/react"; +import { Badge } from "~/ui/Badge"; import { LandingSection, LandingSectionIntro, LandingWindow, LibraryLandingShell, -} from './LibraryLanding' +} from "./LibraryLanding"; const tablePrompt = - 'Build a TanStack Table V9 data grid for a TypeScript app. Keep it headless. Define a stable tableFeatures object with only the feature plugins, create*RowModel slots, and function registries the product needs. Use TanStack Store-backed table state, selectors, or table.Subscribe for reactive reads, and external atoms only for slices the app must own. Render semantic table elements and synchronize state to the URL or server only where the product needs it.' + "Build a TanStack Table V9 data grid for a TypeScript app. Keep it headless. Define a stable tableFeatures object with only the feature plugins, create*RowModel slots, and function registries the product needs. Use TanStack Store-backed table state, selectors, or table.Subscribe for reactive reads, and external atoms only for slices the app must own. Render semantic table elements and synchronize state to the URL or server only where the product needs it."; type TableIssue = { - id: string - owner: string - project: string - score: number - status: 'active' | 'review' | 'shipped' -} + id: string; + owner: string; + project: string; + score: number; + status: "active" | "review" | "shipped"; +}; -type StatusFilter = 'all' | TableIssue['status'] -type StateMode = 'selected' | 'subscribed' | 'external' +type StatusFilter = "all" | TableIssue["status"]; +type StateMode = "selected" | "subscribed" | "external"; const tableWorkbenchFeatures = tableFeatures({ cellSelectionFeature, @@ -66,156 +66,160 @@ const tableWorkbenchFeatures = tableFeatures({ filteredRowModel: createFilteredRowModel(), sortedRowModel: createSortedRowModel(), paginatedRowModel: createPaginatedRowModel(), -}) +}); const tableRows: Array = [ { - id: 'TS-732', - owner: 'Tanner', - project: 'Router docs', + id: "TS-732", + owner: "Tanner", + project: "Router docs", score: 98, - status: 'active', + status: "active", }, { - id: 'TS-681', - owner: 'Dominik', - project: 'Query cache', + id: "TS-681", + owner: "Dominik", + project: "Query cache", score: 94, - status: 'review', + status: "review", }, { - id: 'TS-644', - owner: 'Kevin', - project: 'Table filters', + id: "TS-644", + owner: "Kevin", + project: "Table filters", score: 91, - status: 'shipped', + status: "shipped", }, { - id: 'TS-612', - owner: 'Ben', - project: 'Virtual lists', + id: "TS-612", + owner: "Ben", + project: "Virtual lists", score: 88, - status: 'active', + status: "active", }, { - id: 'TS-590', - owner: 'Arthur', - project: 'Column pinning', + id: "TS-590", + owner: "Arthur", + project: "Column pinning", score: 84, - status: 'review', + status: "review", }, { - id: 'TS-551', - owner: 'Noel', - project: 'Faceted search', + id: "TS-551", + owner: "Noel", + project: "Faceted search", score: 79, - status: 'shipped', + status: "shipped", }, { - id: 'TS-523', - owner: 'Zach', - project: 'Bulk actions', + id: "TS-523", + owner: "Zach", + project: "Bulk actions", score: 76, - status: 'active', + status: "active", }, { - id: 'TS-507', - owner: 'Luca', - project: 'Density switch', + id: "TS-507", + owner: "Luca", + project: "Density switch", score: 72, - status: 'review', + status: "review", }, -] +]; const statusFilters: Array<{ label: string; value: StatusFilter }> = [ - { label: 'All', value: 'all' }, - { label: 'Active', value: 'active' }, - { label: 'Review', value: 'review' }, - { label: 'Shipped', value: 'shipped' }, -] - -const tableResponsibilities = ['State', 'Row processing', 'Typed APIs'] as const + { label: "All", value: "all" }, + { label: "Active", value: "active" }, + { label: "Review", value: "review" }, + { label: "Shipped", value: "shipped" }, +]; + +const tableResponsibilities = [ + "State", + "Row processing", + "Typed APIs", +] as const; const developerControls = [ - 'Markup & semantics', - 'Styles & components', - 'Events & interactions', -] as const + "Markup & semantics", + "Styles & components", + "Events & interactions", +] as const; const stateModes: Record< StateMode, { code: string; note: string; path: string } > = { selected: { - code: 'const sorting = table.state.sorting', - note: 'Select the state a component needs when creating its table instance.', - path: 'table store → selected state → component', + code: "const sorting = table.state.sorting", + note: "Select the state a component needs when creating its table instance.", + path: "table store → selected state → component", }, subscribed: { - code: ' state.sorting}>', - note: 'Move a reactive read to the smallest part of the tree that renders it.', - path: 'sorting atom → subscription island → UI', + code: " state.sorting}>", + note: "Move a reactive read to the smallest part of the tree that renders it.", + path: "sorting atom → subscription island → UI", }, external: { - code: 'atoms: { sorting: sortingAtom }', - note: 'Give an external TanStack Store atom ownership when other systems share the slice.', - path: 'external atom → table feature → app', + code: "atoms: { sorting: sortingAtom }", + note: "Give an external TanStack Store atom ownership when other systems share the slice.", + path: "external atom → table feature → app", }, -} +}; const rowModelStages = [ { - code: 'automatic', - label: 'Core', - note: 'data to rows', + code: "automatic", + label: "Core", + note: "data to rows", }, { - code: 'createFilteredRowModel()', - label: 'Filter', - note: 'column + global', + code: "createFilteredRowModel()", + label: "Filter", + note: "column + global", }, { - code: 'createGroupedRowModel()', - label: 'Group', - note: 'grouped rows', + code: "createGroupedRowModel()", + label: "Group", + note: "grouped rows", }, { - code: 'createSortedRowModel()', - label: 'Sort', - note: 'ordered rows', + code: "createSortedRowModel()", + label: "Sort", + note: "ordered rows", }, { - code: 'createExpandedRowModel()', - label: 'Expand', - note: 'visible sub-rows', + code: "createExpandedRowModel()", + label: "Expand", + note: "visible sub-rows", }, { - code: 'createPaginatedRowModel()', - label: 'Paginate', - note: 'current page', + code: "createPaginatedRowModel()", + label: "Paginate", + note: "current page", }, -] as const +] as const; const tableToolbox = [ { - label: 'Custom features', - code: 'tableFeatures({ densityFeature })', + label: "Custom features", + code: "tableFeatures({ densityFeature })", detail: - 'Add state, options, and APIs through the same extension system used by built-in features.', + "Add state, options, and APIs through the same extension system used by built-in features.", }, { - label: 'Reusable tables', - code: 'createTableHook({ features, ... })', + label: "Reusable tables", + code: "createTableHook({ features, ... })", detail: - 'Share typed features, options, column helpers, and registered components across a product.', + "Share typed features, options, column helpers, and registered components across a product.", }, { - label: 'Devtools', - code: 'useTanStackTableDevtools(table)', + label: "Devtools", + code: "useTanStackTableDevtools(table)", detail: - 'Inspect table state and derived data in supported framework integrations instead of logging internals.', + "Inspect table state and derived data in supported framework integrations instead of logging internals.", }, -] as const +] as const; export default function TableLanding() { return ( @@ -245,7 +249,7 @@ export default function TableLanding() { body="Every registered state slice is backed by TanStack Store, whose fine-grained reactivity is built on alien-signals. Read selected state from table.state, create local subscription islands with table.Subscribe, or hand ownership to an external writable atom when the rest of the app needs it." eyebrow="Fine-grained state" icon={
{headerGroup.headers.map((header) => { - const sort = header.column.getIsSorted() + const sort = header.column.getIsSorted(); return ( - ) + ); })} ))} @@ -621,13 +626,13 @@ function TableWorkbench() { - page {table.state.pagination.pageIndex + 1} /{' '} + page {table.state.pagination.pageIndex + 1} /{" "} {Math.max(table.getPageCount(), 1)} - ) + ); } function OwnershipModel() { @@ -687,12 +692,12 @@ function OwnershipModel() { - ) + ); } function StateSwitchboard() { - const [mode, setMode] = React.useState('subscribed') - const selected = stateModes[mode] + const [mode, setMode] = React.useState("subscribed"); + const selected = stateModes[mode]; return ( @@ -702,7 +707,7 @@ function StateSwitchboard() { className="grid gap-2 sm:grid-cols-3" role="group" > - {(['selected', 'subscribed', 'external'] as const).map((item) => ( + {(["selected", "subscribed", "external"] as const).map((item) => ( - ); + ) })} ( {headerGroup.headers.map((header) => { - const sort = header.column.getIsSorted(); + const sort = header.column.getIsSorted() return ( - ); + ) })} ))} @@ -626,13 +621,13 @@ function TableWorkbench() { - page {table.state.pagination.pageIndex + 1} /{" "} + page {table.state.pagination.pageIndex + 1} /{' '} {Math.max(table.getPageCount(), 1)} - ); + ) } function OwnershipModel() { @@ -692,12 +687,12 @@ function OwnershipModel() { - ); + ) } function StateSwitchboard() { - const [mode, setMode] = React.useState("subscribed"); - const selected = stateModes[mode]; + const [mode, setMode] = React.useState('subscribed') + const selected = stateModes[mode] return ( @@ -707,7 +702,7 @@ function StateSwitchboard() { className="grid gap-2 sm:grid-cols-3" role="group" > - {(["selected", "subscribed", "external"] as const).map((item) => ( + {(['selected', 'subscribed', 'external'] as const).map((item) => (
- {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} +
- {flexRender( - header.column.columnDef.header, - header.getContext(), - )} +
- {flexRender(cell.column.columnDef.cell, cell.getContext())} +
- {sort === 'asc' ? ( + {sort === "asc" ? (
- {sort === "asc" ? ( + {sort === 'asc' ? (