From d021787bec2ead34c73ad960ccf5f857a53a8274 Mon Sep 17 00:00:00 2001 From: John Betancur <1385932+jbetancur@users.noreply.github.com> Date: Sun, 11 Oct 2026 08:56:29 -0400 Subject: [PATCH] fix(docs): improve descriptions and update demo loading behavior - Updated descriptions in inventory-editor, live-updates, persist-column-widths, row-groups, server-side, url-synced-state, resizable, row-interactions, row-pinning, rtl, selection, sorting, ssr, themes, and typescript documentation for clarity and consistency. - Changed demo loading behavior from `client:load` to `client:visible` with a `rootMargin` of '200px' to enhance performance and user experience. - Added a new file for generating a plain text overview of the documentation structure. - Introduced a test suite to ensure that documented props match the TypeScript definitions for `DataTable` and `TableColumn`. --- CLAUDE.md | 17 +- apps/docs/astro.config.mjs | 37 ++- apps/docs/public/llms.txt | 93 -------- apps/docs/src/components/Demo.astro | 6 +- .../src/components/demos/AnimationsDemo.tsx | 2 +- .../components/demos/ApprovalWorkflowDemo.tsx | 2 +- .../src/components/demos/ContextMenuDemo.tsx | 4 +- .../demos/ControlledSelectionDemo.tsx | 2 +- .../src/components/demos/HeadlessDemo.tsx | 2 +- apps/docs/src/components/demos/IconsDemo.tsx | 4 +- .../components/demos/KeyboardArrowNavDemo.tsx | 2 +- .../src/components/demos/KeyboardEditDemo.tsx | 2 +- .../docs/src/components/demos/LoadingDemo.tsx | 6 +- apps/docs/src/components/demos/MobileDemo.tsx | 2 +- .../src/components/demos/MultiSortDemo.tsx | 2 +- .../demos/PersistColumnWidthsDemo.tsx | 2 +- .../components/demos/RowContextMenuDemo.tsx | 4 +- .../src/components/demos/RowGroupingDemo.tsx | 2 +- .../components/demos/RowInteractionsDemo.tsx | 2 +- .../components/demos/ServerSideEditDemo.tsx | 4 +- .../docs/src/components/demos/SortingDemo.tsx | 2 +- .../src/components/demos/TimeFilterDemo.tsx | 2 +- .../docs/src/components/demos/UrlSyncDemo.tsx | 2 +- apps/docs/src/data/backers.ts | 2 +- apps/docs/src/data/nav.ts | 215 ++++++++++++++++++ apps/docs/src/data/props.ts | 52 ++++- apps/docs/src/layouts/DocsLayout.astro | 116 +--------- apps/docs/src/layouts/Layout.astro | 2 +- apps/docs/src/pages/docs/accessibility.md | 4 +- apps/docs/src/pages/docs/animations.astro | 2 +- apps/docs/src/pages/docs/api.astro | 41 +--- apps/docs/src/pages/docs/cells.astro | 6 +- apps/docs/src/pages/docs/column-groups.astro | 4 +- apps/docs/src/pages/docs/column-pinning.astro | 18 +- apps/docs/src/pages/docs/column-reorder.astro | 8 +- .../src/pages/docs/column-separators.astro | 6 +- .../src/pages/docs/column-visibility.astro | 14 +- apps/docs/src/pages/docs/columns.astro | 2 +- .../src/pages/docs/conditional-styles.astro | 8 +- apps/docs/src/pages/docs/context-menu.astro | 40 ++-- apps/docs/src/pages/docs/custom-styles.astro | 6 +- apps/docs/src/pages/docs/expandable.astro | 6 +- apps/docs/src/pages/docs/export.astro | 10 +- apps/docs/src/pages/docs/filtering.astro | 16 +- apps/docs/src/pages/docs/fixed-header.astro | 6 +- apps/docs/src/pages/docs/footer.astro | 30 +-- apps/docs/src/pages/docs/headless.astro | 30 +-- apps/docs/src/pages/docs/inline-editing.astro | 28 +-- .../src/pages/docs/keyboard-navigation.astro | 24 +- apps/docs/src/pages/docs/loading.astro | 2 +- apps/docs/src/pages/docs/localization.astro | 8 +- apps/docs/src/pages/docs/mobile.astro | 8 +- apps/docs/src/pages/docs/pagination.astro | 18 +- apps/docs/src/pages/docs/performance.astro | 2 +- apps/docs/src/pages/docs/recipes.astro | 67 +----- .../docs/recipes/approval-workflow.astro | 10 +- .../src/pages/docs/recipes/audit-log.astro | 6 +- .../src/pages/docs/recipes/csv-import.astro | 12 +- .../docs/recipes/dashboard-drilldown.astro | 14 +- .../docs/recipes/inline-row-actions.astro | 8 +- .../pages/docs/recipes/inventory-editor.astro | 14 +- .../src/pages/docs/recipes/live-updates.astro | 10 +- .../docs/recipes/persist-column-widths.astro | 4 +- .../src/pages/docs/recipes/row-groups.astro | 14 +- .../src/pages/docs/recipes/server-side.astro | 6 +- .../pages/docs/recipes/url-synced-state.astro | 10 +- apps/docs/src/pages/docs/resizable.astro | 2 +- .../src/pages/docs/row-interactions.astro | 8 +- apps/docs/src/pages/docs/row-pinning.astro | 14 +- apps/docs/src/pages/docs/rtl.astro | 4 +- apps/docs/src/pages/docs/selection.astro | 20 +- apps/docs/src/pages/docs/sorting.astro | 28 +-- apps/docs/src/pages/docs/ssr.astro | 10 +- apps/docs/src/pages/docs/themes.astro | 24 +- apps/docs/src/pages/docs/typescript.astro | 16 +- apps/docs/src/pages/llms.txt.ts | 43 ++++ src/__tests__/docsCatalog.test.ts | 47 ++++ vitest.config.mts | 3 + 78 files changed, 717 insertions(+), 614 deletions(-) delete mode 100644 apps/docs/public/llms.txt create mode 100644 apps/docs/src/data/nav.ts create mode 100644 apps/docs/src/pages/llms.txt.ts create mode 100644 src/__tests__/docsCatalog.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index 9a9389e4..709666b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -49,19 +49,22 @@ The docs live in `apps/docs/src/pages/docs/`. Each feature has its own `.astro` **When adding a new prop or changing an existing one:** -- Update the prop reference table on the relevant docs page. -- Update the type signature in the API reference at `apps/docs/src/pages/docs/api.astro`. +- Prop tables come from `apps/docs/src/data/props.ts`: `dataTableProps` for DataTable props (rendered on feature pages and the API reference via ``), `columnProps` for `TableColumn` fields (rendered in `api.astro`). +- `src/__tests__/docsCatalog.test.ts` fails when either catalog is missing a prop from `src/types.ts` or lists one that no longer exists. +- Update any type signature code blocks in `apps/docs/src/pages/docs/api.astro` by hand. **Nav and routing:** -- Add new pages to the sidebar in `apps/docs/src/layouts/DocsLayout.astro`. +- Add new pages to `apps/docs/src/data/nav.ts`. It drives the sidebar, prev/next links, the recipes index, and the generated `/llms.txt`. - If removing a page, add a `[[redirects]]` entry in `netlify.toml` pointing the old URL to its replacement. +- The docs build fails on internal links to pages that don't exist (`checkInternalLinks` in `astro.config.mjs`). Anchors aren't checked. +- Demo islands on docs pages use `client:visible={{ rootMargin: '200px' }}`, not `client:load`. -**Keeping `apps/docs/public/llms.txt` in sync:** +**`llms.txt`** is generated by `apps/docs/src/pages/llms.txt.ts` from `nav.ts`: -- Every docs page listed in `DocsLayout.astro`'s sidebar nav should have a matching entry in `llms.txt`, with a one-line factual (non-marketing) description. -- Add/remove/re-describe its `llms.txt` entry in the same change whenever a docs page is added, removed, or its scope changes materially — this applies especially to `comparisons.md`, since claims there go stale fastest. -- Keep descriptions neutral and factual — `llms.txt` is read by LLMs/crawlers, not just humans; slanted copy reads as manipulation and can get the file distrusted or ignored. +- Every `/docs/` link in `nav.ts` needs a `description`, which becomes its `llms.txt` line. The build fails without one. +- Re-describe a page in the same change whenever its scope changes materially. This applies especially to `comparisons.md`, since claims there go stale fastest. +- Keep descriptions neutral and factual. `llms.txt` is read by LLMs and crawlers, not just humans; slanted copy reads as manipulation and can get the file distrusted or ignored. --- diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs index 9a21ff78..aa4deb5e 100644 --- a/apps/docs/astro.config.mjs +++ b/apps/docs/astro.config.mjs @@ -2,13 +2,48 @@ import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; import sitemap from '@astrojs/sitemap'; import tailwindcss from '@tailwindcss/vite'; +import { existsSync } from 'node:fs'; +import { readdir, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +// Fails the build when a page links to a site path that wasn't generated. Anchors aren't +// checked: most heading ids are assigned client-side by TableOfContents. +const checkInternalLinks = { + name: 'check-internal-links', + hooks: { + 'astro:build:done': async ({ dir, logger }) => { + const root = fileURLToPath(dir); + const pages = (await readdir(root, { recursive: true })).filter(f => f.endsWith('.html')); + const broken = new Set(); + for (const page of pages) { + const html = await readFile(join(root, page), 'utf8'); + for (const [, href] of html.matchAll(/href="(\/[^"#?]*)/g)) { + // pagefind/ is generated after astro build + if (href.startsWith('//') || href.startsWith('/pagefind/')) { + continue; + } + const target = join(root, decodeURI(href)); + if (!existsSync(target) && !existsSync(`${target}.html`)) { + broken.add(`${page} -> ${href}`); + } + } + } + if (broken.size) { + throw new Error(`Broken internal links:\n${[...broken].join('\n')}`); + } + logger.info(`internal links OK across ${pages.length} pages`); + }, + }, +}; export default defineConfig({ site: 'https://reactdatatable.com', + prefetch: { prefetchAll: true, defaultStrategy: 'hover' }, markdown: { shikiConfig: { theme: 'catppuccin-macchiato' }, }, - integrations: [react(), sitemap()], + integrations: [react(), sitemap(), checkInternalLinks], vite: { plugins: [tailwindcss()], resolve: { diff --git a/apps/docs/public/llms.txt b/apps/docs/public/llms.txt deleted file mode 100644 index 78081a18..00000000 --- a/apps/docs/public/llms.txt +++ /dev/null @@ -1,93 +0,0 @@ -# react-data-table-component - -> A fast, accessible React data table component with sorting, pagination, row selection, expandable rows, column filtering, and theming built in. Zero runtime dependencies, Apache-2.0 licensed, free with no paid tier. A working table takes about 10 lines of code. - -Install: `npm install react-data-table-component` - -Minimal usage: - -```tsx -import DataTable from 'react-data-table-component'; - -const columns = [ - { name: 'Name', selector: row => row.name, sortable: true }, - { name: 'Role', selector: row => row.role }, -]; - - -``` - -## Getting started - -- [Getting Started](https://reactdatatable.com/docs/getting-started): install and render a first table -- [Installation](https://reactdatatable.com/docs/installation): package managers, CSS import, framework setup -- [API Reference](https://reactdatatable.com/docs/api): all props and types -- [Comparisons](https://reactdatatable.com/docs/comparisons): how this compares to TanStack Table, AG Grid, MUI X Data Grid, and the plain MUI Table -- [Migration Guide](https://reactdatatable.com/docs/migration): upgrading from v7 to v8 - -## Columns - -- [Columns Overview](https://reactdatatable.com/docs/columns): defining columns, selectors, formatting -- [Cell Rendering](https://reactdatatable.com/docs/cells): custom cell components -- [Column Groups](https://reactdatatable.com/docs/column-groups): spanning headers across columns -- [Column Visibility](https://reactdatatable.com/docs/column-visibility): show and hide columns -- [Column Reordering](https://reactdatatable.com/docs/column-reorder): drag to reorder, mouse or touch (press-and-hold on touch) -- [Column Resizing](https://reactdatatable.com/docs/resizable): drag column edges by mouse or touch, persist widths -- [Column Pinning](https://reactdatatable.com/docs/column-pinning): pin columns left or right -- [Column Separators](https://reactdatatable.com/docs/column-separators): vertical divider lines between header and body columns -- [Column Filtering](https://reactdatatable.com/docs/filtering): per-column text, number, date, datetime, and time-of-day filters with operator selection and two-condition AND/OR logic, plus set filters that pick from a checklist of a column's distinct values or a supplied list, optionally splitting cells that hold several values, and a filterServer option for backend filtering -- [Sorting](https://reactdatatable.com/docs/sorting): client-side and server-side sorting - -## Rows - -- [Row Selection](https://reactdatatable.com/docs/selection): checkbox multi-select, select all, controlled selection -- [Expandable Rows](https://reactdatatable.com/docs/expandable): detail panels with any component -- [Row Interactions](https://reactdatatable.com/docs/row-interactions): click, double-click, hover handlers -- [Row Pinning](https://reactdatatable.com/docs/row-pinning): pin rows to top -- [Pagination](https://reactdatatable.com/docs/pagination): built-in and custom pagination, server-side -- [Conditional Styles](https://reactdatatable.com/docs/conditional-styles): style rows and cells by data -- [Fixed Header](https://reactdatatable.com/docs/fixed-header): sticky header with scrollable body; when to disable the responsive scroll container -- [Footer](https://reactdatatable.com/docs/footer): summary and pagination footers -- [Loading State](https://reactdatatable.com/docs/loading): progress and skeleton states -- [Inline Editing](https://reactdatatable.com/docs/inline-editing): edit cells in place with built-in editor types, per-column validation, and optimistic server-save patterns - -## Styling - -- [Themes](https://reactdatatable.com/docs/themes): 5 built-in themes with dark mode, createTheme() -- [Custom Styles](https://reactdatatable.com/docs/custom-styles): style every table region - -## Advanced - -- [TypeScript](https://reactdatatable.com/docs/typescript): generic column and row typing -- [SSR](https://reactdatatable.com/docs/ssr): Next.js App Router, Remix, Astro -- [Performance](https://reactdatatable.com/docs/performance): large datasets, memoization -- [Headless Hooks](https://reactdatatable.com/docs/headless): use the logic without the UI -- [Accessibility](https://reactdatatable.com/docs/accessibility): ARIA roles and screen reader support -- [Keyboard Navigation](https://reactdatatable.com/docs/keyboard-navigation): cellNavigation prop for spreadsheet-style ARIA grid keyboard navigation -- [Context Menu](https://reactdatatable.com/docs/context-menu): right-click or menu-button menu with built-in header actions (sort, pin, hide, reset) and custom row actions -- [Localization](https://reactdatatable.com/docs/localization): translated labels and locales -- [RTL Support](https://reactdatatable.com/docs/rtl): right-to-left layouts -- [Mobile](https://reactdatatable.com/docs/mobile): responsive columns and layouts -- [Export](https://reactdatatable.com/docs/export): CSV and JSON export -- [Animations](https://reactdatatable.com/docs/animations): row animations - -## Recipes - -- [Server-side sort & filter](https://reactdatatable.com/docs/recipes/server-side) -- [Approval workflow with bulk actions](https://reactdatatable.com/docs/recipes/approval-workflow) -- [URL-synced table state](https://reactdatatable.com/docs/recipes/url-synced-state) -- [Dashboard drill-down](https://reactdatatable.com/docs/recipes/dashboard-drilldown) -- [Live-updating data grid](https://reactdatatable.com/docs/recipes/live-updates): flash-on-change rows using conditionalRowStyles and keyField -- [Editable inventory grid](https://reactdatatable.com/docs/recipes/inventory-editor): spreadsheet-style inline editing with a derived total column -- [Grouped rows with aggregates](https://reactdatatable.com/docs/recipes/row-groups): per-group totals and drill-down without a dedicated groupBy API -- [CSV import](https://reactdatatable.com/docs/recipes/csv-import): load a user-uploaded CSV file into the table -- [Audit log viewer](https://reactdatatable.com/docs/recipes/audit-log) -- [Persist column widths](https://reactdatatable.com/docs/recipes/persist-column-widths) -- [Inline row actions](https://reactdatatable.com/docs/recipes/inline-row-actions) - -## Optional - -- [Changelog](https://reactdatatable.com/docs/changelog): release history -- [Support & Sponsorship](https://reactdatatable.com/support): sponsor tiers, direct consulting and enterprise support -- [GitHub repository](https://github.com/jbetancur/react-data-table-component) -- [npm package](https://www.npmjs.com/package/react-data-table-component) diff --git a/apps/docs/src/components/Demo.astro b/apps/docs/src/components/Demo.astro index de2713c0..d4187953 100644 --- a/apps/docs/src/components/Demo.astro +++ b/apps/docs/src/components/Demo.astro @@ -1,13 +1,13 @@ --- /** - * Demo.astro — wraps a live React island with a toggleable "View code" panel. + * Demo.astro: wraps a live React island with a toggleable "View code" panel. * * Usage: * - * + * * * - * The component uses Astro's built-in Shiki highlighter — zero config. + * The component uses Astro's built-in Shiki highlighter, zero config. */ import { Code } from 'astro:components'; import type { ComponentProps } from 'astro/types'; diff --git a/apps/docs/src/components/demos/AnimationsDemo.tsx b/apps/docs/src/components/demos/AnimationsDemo.tsx index e49addf1..6b71d9c6 100644 --- a/apps/docs/src/components/demos/AnimationsDemo.tsx +++ b/apps/docs/src/components/demos/AnimationsDemo.tsx @@ -48,7 +48,7 @@ export default function AnimationsDemo() { Replay entrance animation - {animate ? 'Animations on — sort to see reorder animation' : 'Animations off'} + {animate ? 'Animations on: sort to see reorder animation' : 'Animations off'} diff --git a/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx b/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx index 01d99089..c4a59eea 100644 --- a/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx +++ b/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx @@ -143,7 +143,7 @@ export default function ApprovalWorkflowDemo() { return (
- {/* Bulk toolbar — actions change based on what's selected */} + {/* Bulk toolbar: actions change based on what's selected */} {selected.length > 0 && (
diff --git a/apps/docs/src/components/demos/ContextMenuDemo.tsx b/apps/docs/src/components/demos/ContextMenuDemo.tsx index 22a2bbd4..0e49594f 100644 --- a/apps/docs/src/components/demos/ContextMenuDemo.tsx +++ b/apps/docs/src/components/demos/ContextMenuDemo.tsx @@ -66,7 +66,7 @@ const initialData: Row[] = [ }, ]; -// Fixed widths so the table overflows horizontally — otherwise pinning a column +// Fixed widths so the table overflows horizontally, otherwise pinning a column // has nothing to stick against and appears to do nothing. const columns: TableColumn[] = [ { id: 'name', name: 'Name', selector: row => row.name, sortable: true, width: '180px', filterable: true }, @@ -87,7 +87,7 @@ const columns: TableColumn[] = [ ]; export default function ContextMenuDemo(): JSX.Element { - const [lastAction, setLastAction] = useState('none yet — right-click a header or use a ⋮ button'); + const [lastAction, setLastAction] = useState('none yet (right-click a header or use a ⋮ button)'); return (
diff --git a/apps/docs/src/components/demos/ControlledSelectionDemo.tsx b/apps/docs/src/components/demos/ControlledSelectionDemo.tsx index 86337f06..3522906a 100644 --- a/apps/docs/src/components/demos/ControlledSelectionDemo.tsx +++ b/apps/docs/src/components/demos/ControlledSelectionDemo.tsx @@ -94,7 +94,7 @@ export default function ControlledSelectionDemo() {
{selected.length > 0 ? `[${selected.map(r => r.id).join(', ')}]` : '[]'} {selected.length > 0 && ( - — {selected.map(r => r.name).join(', ')} + : {selected.map(r => r.name).join(', ')} )}
diff --git a/apps/docs/src/components/demos/HeadlessDemo.tsx b/apps/docs/src/components/demos/HeadlessDemo.tsx index fc2773e3..bfadad4a 100644 --- a/apps/docs/src/components/demos/HeadlessDemo.tsx +++ b/apps/docs/src/components/demos/HeadlessDemo.tsx @@ -116,7 +116,7 @@ export default function HeadlessDemo() { return (

- Custom markup — no DataTable component used. All logic from the four headless hooks. + Custom markup. No DataTable component used. All logic from the four headless hooks.

{/* Filter inputs */} diff --git a/apps/docs/src/components/demos/IconsDemo.tsx b/apps/docs/src/components/demos/IconsDemo.tsx index b4d3b68f..8660f236 100644 --- a/apps/docs/src/components/demos/IconsDemo.tsx +++ b/apps/docs/src/components/demos/IconsDemo.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import DataTable from '../ThemedDataTable'; import { type TableColumn } from 'react-data-table-component'; -// Inline SVG icon primitives — no external dependency +// Inline SVG icon primitives, no external dependency const ChevronLeft = () => ( (
- {row.name} — {row.bio} + {row.name}: {row.bio}
); diff --git a/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx b/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx index d4f3caf7..7f9262ab 100644 --- a/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx +++ b/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx @@ -28,7 +28,7 @@ export default function KeyboardArrowNavDemo() {

A plain read-only table. Click a cell, then use ← → ↑ ↓ to move,{' '} Home/End to jump to the row edges, and Ctrl+Home/Ctrl+ - End to jump to the grid corners. No editing, sorting, selection, or expansion here — just movement, + End to jump to the grid corners. No editing, sorting, selection, or expansion here, just movement, which works on any table.

diff --git a/apps/docs/src/components/demos/KeyboardEditDemo.tsx b/apps/docs/src/components/demos/KeyboardEditDemo.tsx index 4858dcd4..e6e87678 100644 --- a/apps/docs/src/components/demos/KeyboardEditDemo.tsx +++ b/apps/docs/src/components/demos/KeyboardEditDemo.tsx @@ -44,7 +44,7 @@ export default function KeyboardEditDemo() {

Arrow to Name or Salary, then press Enter or F2 to open the editor. Enter{' '} - commits and Escape cancels — either way, focus returns to the cell so arrow-key navigation continues + commits and Escape cancels. Either way, focus returns to the cell so arrow-key navigation continues immediately. Department has no editor, so Enter/F2 do nothing there.

diff --git a/apps/docs/src/components/demos/LoadingDemo.tsx b/apps/docs/src/components/demos/LoadingDemo.tsx index 1d2a4f01..7228fa33 100644 --- a/apps/docs/src/components/demos/LoadingDemo.tsx +++ b/apps/docs/src/components/demos/LoadingDemo.tsx @@ -94,9 +94,9 @@ export default function LoadingDemo() { {mode === 'initial' ? skeleton - ? 'Loading with no existing data — skeleton rows shown' - : 'Loading with no existing data — progress component shown' - : 'Re-fetching — existing rows dimmed, indicator overlaid'} + ? 'Loading with no existing data: skeleton rows shown' + : 'Loading with no existing data: progress component shown' + : 'Re-fetching: existing rows dimmed, indicator overlaid'} )}
diff --git a/apps/docs/src/components/demos/MobileDemo.tsx b/apps/docs/src/components/demos/MobileDemo.tsx index d4330e98..ca27302c 100644 --- a/apps/docs/src/components/demos/MobileDemo.tsx +++ b/apps/docs/src/components/demos/MobileDemo.tsx @@ -129,7 +129,7 @@ export default function MobileDemo() { {isNarrow && (

- Department and Salary are hidden at this width — use omit or{' '} + Department and Salary are hidden at this width. Use omit or{' '} hide: "sm" to drop columns on small screens.

)} diff --git a/apps/docs/src/components/demos/MultiSortDemo.tsx b/apps/docs/src/components/demos/MultiSortDemo.tsx index 4916e2ba..bc362b6e 100644 --- a/apps/docs/src/components/demos/MultiSortDemo.tsx +++ b/apps/docs/src/components/demos/MultiSortDemo.tsx @@ -40,7 +40,7 @@ export default function MultiSortDemo() { const summary = sortColumns.length === 0 - ? 'No sort — click a header to sort, click a third time to remove it' + ? 'No sort. Click a header to sort, click a third time to remove it' : sortColumns.map((s, i) => `${i + 1}. ${String(s.column.id)} ${s.sortDirection}`).join(' · '); return ( diff --git a/apps/docs/src/components/demos/PersistColumnWidthsDemo.tsx b/apps/docs/src/components/demos/PersistColumnWidthsDemo.tsx index 9d037aaf..8fc2155e 100644 --- a/apps/docs/src/components/demos/PersistColumnWidthsDemo.tsx +++ b/apps/docs/src/components/demos/PersistColumnWidthsDemo.tsx @@ -57,7 +57,7 @@ export default function PersistColumnWidthsDemo() { return (
- Drag column edges to resize — widths persist across sessions. + Drag column edges to resize. Widths persist across sessions.
)} diff --git a/apps/docs/src/components/demos/ServerSideEditDemo.tsx b/apps/docs/src/components/demos/ServerSideEditDemo.tsx index a51a3f83..f944a207 100644 --- a/apps/docs/src/components/demos/ServerSideEditDemo.tsx +++ b/apps/docs/src/components/demos/ServerSideEditDemo.tsx @@ -43,7 +43,7 @@ export default function ServerSideEditDemo() { setStatus({ ok: true, message: `Saved ${row.sku} → ${String(column.name)}: "${value}"` }); } catch (err) { setData(prev => prev.map(r => (r.id === row.id ? { ...r, [field]: row[field] } : r))); - setStatus({ ok: false, message: `${(err as Error).message} — rolled back ${row.sku}` }); + setStatus({ ok: false, message: `${(err as Error).message} (rolled back ${row.sku})` }); } finally { setSavingId(null); } @@ -77,7 +77,7 @@ export default function ServerSideEditDemo() {

Edits apply optimistically, then save to a simulated server with ~1s latency. The row dims while the save is in - flight. The server rejects any Price over $1,000 — try it to watch the edit roll back. + flight. The server rejects any Price over $1,000. Try it to watch the edit roll back.

- localeCompare sort (sort Name — watch where Étienne lands) + localeCompare sort (sort Name and watch where Étienne lands)
diff --git a/apps/docs/src/components/demos/TimeFilterDemo.tsx b/apps/docs/src/components/demos/TimeFilterDemo.tsx index 8af62014..38f189d6 100644 --- a/apps/docs/src/components/demos/TimeFilterDemo.tsx +++ b/apps/docs/src/components/demos/TimeFilterDemo.tsx @@ -7,7 +7,7 @@ interface LogEntry { service: string; level: 'info' | 'warn' | 'error'; message: string; - // Full ISO timestamps spanning several days — the time filter reads the + // Full ISO timestamps spanning several days. The time filter reads the // time-of-day and ignores the date, so a window matches across all of them. at: string; } diff --git a/apps/docs/src/components/demos/UrlSyncDemo.tsx b/apps/docs/src/components/demos/UrlSyncDemo.tsx index 8d63db8b..dc448df1 100644 --- a/apps/docs/src/components/demos/UrlSyncDemo.tsx +++ b/apps/docs/src/components/demos/UrlSyncDemo.tsx @@ -123,7 +123,7 @@ export default function UrlSyncDemo() { // Show current URL params so the demo is self-explanatory const [urlDisplay, setUrlDisplay] = useState(''); useEffect(() => { - setUrlDisplay(window.location.search || '(no params — default state)'); + setUrlDisplay(window.location.search || '(no params, default state)'); }, [sortId, sortDir, page, perPage, filters]); return ( diff --git a/apps/docs/src/data/backers.ts b/apps/docs/src/data/backers.ts index 49e8d9c7..4a3c08e6 100644 --- a/apps/docs/src/data/backers.ts +++ b/apps/docs/src/data/backers.ts @@ -1,6 +1,6 @@ // Single source of truth for Backer-tier ($20/mo) sponsors and above. // Rendered on both /support and the homepage sponsors section. -// Add names here as they sign on — keep README.md's "## Backers" list in sync by hand. +// Add names here as they sign on. Keep README.md's "## Backers" list in sync by hand. export interface Backer { name: string; url?: string; diff --git a/apps/docs/src/data/nav.ts b/apps/docs/src/data/nav.ts new file mode 100644 index 00000000..16701b9c --- /dev/null +++ b/apps/docs/src/data/nav.ts @@ -0,0 +1,215 @@ +// Single source for the docs sidebar, prev/next links, the recipes index, and /llms.txt. +// Every /docs/ link needs a `description`: it becomes the page's llms.txt line, so keep it +// factual and neutral (it is read by LLMs and crawlers, not just people). + +export interface NavLink { + label: string; + href: string; + description?: string; +} + +export interface NavGroup { + group: string; + links: NavLink[]; +} + +export const nav: NavGroup[] = [ + { + group: 'Introduction', + links: [ + { label: 'Getting Started', href: '/docs/getting-started', description: 'install and render a first table' }, + { label: 'Installation', href: '/docs/installation', description: 'package managers, CSS import, framework setup' }, + { + label: 'Comparisons', + href: '/docs/comparisons', + description: 'how this compares to TanStack Table, AG Grid, MUI X Data Grid, and the plain MUI Table', + }, + { label: 'Changelog', href: '/docs/changelog', description: 'release history' }, + { label: 'llms.txt', href: '/llms.txt' }, + ], + }, + { + group: 'Reference', + links: [ + { label: 'API Reference', href: '/docs/api', description: 'all props and types' }, + { label: 'Migration Guide', href: '/docs/migration', description: 'upgrading from v7 to v8' }, + ], + }, + { + group: 'Columns', + links: [ + { label: 'Overview', href: '/docs/columns', description: 'defining columns, selectors, formatting' }, + { label: 'Cell Rendering', href: '/docs/cells', description: 'custom cell components' }, + { label: 'Column Groups', href: '/docs/column-groups', description: 'spanning headers across columns' }, + { label: 'Column Visibility', href: '/docs/column-visibility', description: 'show and hide columns' }, + { + label: 'Column Reordering', + href: '/docs/column-reorder', + description: 'drag to reorder, mouse or touch (press-and-hold on touch)', + }, + { + label: 'Column Resizing', + href: '/docs/resizable', + description: 'drag column edges by mouse or touch, persist widths', + }, + { label: 'Column Pinning', href: '/docs/column-pinning', description: 'pin columns left or right' }, + { + label: 'Column Separators', + href: '/docs/column-separators', + description: 'vertical divider lines between header and body columns', + }, + { + label: 'Column Filtering', + href: '/docs/filtering', + description: + "per-column text, number, date, datetime, and time-of-day filters with operator selection and two-condition AND/OR logic, plus set filters that pick from a checklist of a column's distinct values or a supplied list, optionally splitting cells that hold several values, and a filterServer option for backend filtering", + }, + { label: 'Sorting', href: '/docs/sorting', description: 'client-side and server-side sorting' }, + ], + }, + { + group: 'Editing', + links: [ + { + label: 'Inline Editing', + href: '/docs/inline-editing', + description: + 'edit cells in place with built-in editor types, per-column validation, and optimistic server-save patterns', + }, + ], + }, + { + group: 'Rows', + links: [ + { + label: 'Row Interactions', + href: '/docs/row-interactions', + description: 'click, double-click, hover handlers', + }, + { + label: 'Row Selection', + href: '/docs/selection', + description: 'checkbox multi-select, select all, controlled selection', + }, + { label: 'Row Pinning', href: '/docs/row-pinning', description: 'pin rows to top' }, + { label: 'Expandable Rows', href: '/docs/expandable', description: 'detail panels with any component' }, + { label: 'Conditional Styles', href: '/docs/conditional-styles', description: 'style rows and cells by data' }, + { + label: 'Fixed Header', + href: '/docs/fixed-header', + description: 'sticky header with scrollable body; when to disable the responsive scroll container', + }, + { + label: 'Pagination', + href: '/docs/pagination', + description: 'built-in and custom pagination, server-side', + }, + { label: 'Footer', href: '/docs/footer', description: 'summary and pagination footers' }, + { label: 'Loading State', href: '/docs/loading', description: 'progress and skeleton states' }, + ], + }, + { + group: 'Interactivity', + links: [ + { label: 'Animations', href: '/docs/animations', description: 'row animations' }, + { label: 'Accessibility', href: '/docs/accessibility', description: 'ARIA roles and screen reader support' }, + { + label: 'Keyboard Navigation', + href: '/docs/keyboard-navigation', + description: 'cellNavigation prop for spreadsheet-style ARIA grid keyboard navigation', + }, + { + label: 'Context Menu', + href: '/docs/context-menu', + description: + 'right-click or menu-button menu with built-in header actions (sort, pin, hide, reset) and custom row actions', + }, + { label: 'Localization', href: '/docs/localization', description: 'translated labels and locales' }, + { label: 'RTL Support', href: '/docs/rtl', description: 'right-to-left layouts' }, + { label: 'Mobile', href: '/docs/mobile', description: 'responsive columns and layouts' }, + ], + }, + { + group: 'Styling', + links: [ + { label: 'Themes', href: '/docs/themes', description: '5 built-in themes with dark mode, createTheme()' }, + { label: 'Custom Styles', href: '/docs/custom-styles', description: 'style every table region' }, + ], + }, + { + group: 'Import / Export', + links: [{ label: 'Export (CSV / JSON)', href: '/docs/export', description: 'CSV and JSON export' }], + }, + { + group: 'Advanced', + links: [ + { label: 'Headless Hooks', href: '/docs/headless', description: 'use the logic without the UI' }, + { label: 'TypeScript', href: '/docs/typescript', description: 'generic column and row typing' }, + { label: 'SSR', href: '/docs/ssr', description: 'Next.js App Router, Remix, Astro' }, + { label: 'Performance', href: '/docs/performance', description: 'large datasets, memoization' }, + ], + }, + { + group: 'Recipes', + links: [ + { + label: 'Approval workflow', + href: '/docs/recipes/approval-workflow', + description: + "Bulk-approve or reject requests. Toolbar buttons adapt to the selected rows' status. Already-resolved rows are excluded automatically.", + }, + { + label: 'Server-side sort & filter', + href: '/docs/recipes/server-side', + description: 'Wire sorting, pagination, and column filters to your own API in a single useEffect.', + }, + { + label: 'URL-synced table state', + href: '/docs/recipes/url-synced-state', + description: 'Sync sort, filters, and pagination to the URL: shareable links, reload-safe, back button works.', + }, + { + label: 'Dashboard drill-down', + href: '/docs/recipes/dashboard-drilldown', + description: 'Department overview with expandable rows, health-status row tinting, and inline budget-spend bars.', + }, + { + label: 'Audit log viewer', + href: '/docs/recipes/audit-log', + description: 'Fixed header, severity row tinting, and multi-field live search, all wired together.', + }, + { + label: 'Persist column widths', + href: '/docs/recipes/persist-column-widths', + description: 'Save and restore resizable column widths to localStorage or a back-end API.', + }, + { + label: 'Inline row actions', + href: '/docs/recipes/inline-row-actions', + description: 'Per-row ⋮ menu with edit, duplicate, and delete, with confirmation modals included.', + }, + { + label: 'Live-updating grid', + href: '/docs/recipes/live-updates', + description: 'Flash-on-change rows for a real-time price grid, using conditionalRowStyles and keyField.', + }, + { + label: 'Editable inventory grid', + href: '/docs/recipes/inventory-editor', + description: 'Spreadsheet-style inline editing with a derived total column and a running grand total.', + }, + { + label: 'Grouped rows with aggregates', + href: '/docs/recipes/row-groups', + description: 'Per-group totals and drill-down built from a plain array reduce, no groupBy API needed.', + }, + { + label: 'CSV import', + href: '/docs/recipes/csv-import', + description: 'Load a user-uploaded CSV file into the table with FileReader and a small parser.', + }, + ], + }, +]; + +export const recipes = nav.find(({ group }) => group === 'Recipes')!.links; diff --git a/apps/docs/src/data/props.ts b/apps/docs/src/data/props.ts index 75cff694..27e33cc3 100644 --- a/apps/docs/src/data/props.ts +++ b/apps/docs/src/data/props.ts @@ -957,8 +957,8 @@ export const dataTableProps: PropDef[] = [ on: 'column', type: `${c("'left'")} | ${c("'right'")}`, default: '-', - description: `Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container — give columns explicit widths. See Column pinning.`, - brief: `Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container — give columns explicit widths.`, + description: `Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container, so give columns explicit widths. See Column pinning.`, + brief: `Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container, so give columns explicit widths.`, group: 'Column features', features: ['column-pinning'], }, @@ -968,7 +968,7 @@ export const dataTableProps: PropDef[] = [ name: 'contextMenu', type: `${c("boolean | { header?: boolean; row?: boolean; trigger?: 'right-click' | 'menu-button' | 'both'; menuPosition?: 'start' | 'end' }")}`, default: '-', - description: `Enable the context menu. ${c('true')} enables header and row menus with the right-click trigger. The header menu has built-in sort/pin/hide/reset actions; the row menu only opens when ${c('contextMenuActions.row')} returns items. ${c('menuPosition')} sets which inline edge the row menu button sits on (default ${c("'end'")} — right in LTR). See Context menu.`, + description: `Enable the context menu. ${c('true')} enables header and row menus with the right-click trigger. The header menu has built-in sort/pin/hide/reset actions; the row menu only opens when ${c('contextMenuActions.row')} returns items. ${c('menuPosition')} sets which inline edge the row menu button sits on (default ${c("'end'")}, right in LTR). See Context menu.`, brief: `Enable the context menu. ${c('true')} enables header and row menus with the right-click trigger. The header menu has built-in sort/pin/hide/reset actions; the row menu only opens when ${c('contextMenuActions.row')} returns items.`, group: 'Context menu', features: ['context-menu'], @@ -1035,3 +1035,49 @@ export const propGroupOrder: PropGroup[] = [ 'Context menu', 'Localization', ]; + +// TableColumn fields for the API reference. `section: 'inline-editing'` rows render +// under their own heading. +export interface ColumnPropDef { + name: string; + type: string; + description: string; + section?: 'inline-editing'; +} + +export const columnProps: ColumnPropDef[] = [ + { name: 'id', type: 'string | number', description: 'Stable identity used by defaultSortFieldId, columnGroups, and filterValues.' }, + { name: 'name', type: 'ReactNode', description: 'Column header label.' }, + { name: 'selector', type: '(row, index?) => Primitive | ReactNode', description: 'Data accessor. For sortable columns return a Primitive (string | number | boolean | bigint | Date). Rows whose selector returns null or undefined sort to the end. bigint displays as its decimal string; Date displays via toLocaleString(), which follows the viewer's locale and timezone. Use format when you need deterministic or SSR-stable output.' }, + { name: 'cell', type: '(row, index, column, id) => ReactNode', description: 'Custom cell renderer. Overrides the selector display value.' }, + { name: 'format', type: '(row, index) => ReactNode', description: 'Format the selector value for display without changing the sort key.' }, + { name: 'sortable', type: 'boolean', description: 'Enable sorting on this column.' }, + { name: 'sortFunction', type: '(a: T, b: T) => number', description: 'Custom comparator for this column.' }, + { name: 'sortField', type: 'string', description: 'Field key passed to onSort for server-side sort identification.' }, + { name: 'filterable', type: 'boolean', description: 'Show a filter popup for this column.' }, + { name: 'filterType', type: '"text" | "number" | "date" | "datetime" | "time" | "set"', description: 'Filter operator set and input widget. Defaults to "text". "set" shows a checklist of the column's distinct values instead of operators. See Filtering.' }, + { name: 'filterFunction', type: '(row, filter: FilterState) => boolean', description: 'Custom filter predicate. Overrides built-in operator logic. Receives the full FilterState including both conditions.' }, + { name: 'filterOptions', type: '{ values?: string[] | ((rows: T[]) => string[]); separator?: string | RegExp }', description: 'Settings for a "set" filter. values supplies the checklist instead of deriving it from the loaded rows. separator splits a multi-value cell into its parts. See Filtering.' }, + { name: 'width', type: 'string', description: 'Fixed column width, e.g. "120px".' }, + { name: 'minWidth', type: 'string', description: 'Minimum width.' }, + { name: 'maxWidth', type: 'string', description: 'Maximum width.' }, + { name: 'grow', type: 'number', description: 'Flex-grow factor (default 1). Set to 0 to prevent growing.' }, + { name: 'right', type: 'boolean', description: 'Right-align cell content.' }, + { name: 'center', type: 'boolean', description: 'Center cell content.' }, + { name: 'wrap', type: 'boolean', description: 'Allow cell text to wrap.' }, + { name: 'compact', type: 'boolean', description: 'Remove cell padding.' }, + { name: 'button', type: 'boolean', description: 'Center content and suppress row click propagation.' }, + { name: 'allowOverflow', type: 'boolean', description: 'Allow cell content to overflow (useful for dropdowns and popovers).' }, + { name: 'ignoreRowClick', type: 'boolean', description: 'Prevent clicks in this cell from firing onRowClicked.' }, + { name: 'hide', type: 'Media', description: 'Hide the column below the given breakpoint (Media.SM = 599px, MD = 959px, LG = 1280px).' }, + { name: 'omit', type: 'boolean', description: 'Exclude the column entirely. Toggle this to show/hide a column.' }, + { name: 'reorder', type: 'boolean', description: 'Allow drag-to-reorder for this column (requires reorder on at least two columns).' }, + { name: 'pinned', type: '\'left\' | \'right\'', description: 'Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container, so give columns explicit widths. See Column pinning.' }, + { name: 'style', type: 'CSSProperties', description: 'Inline styles applied to every cell in this column.' }, + { name: 'conditionalCellStyles', type: 'ConditionalStyles<T>[]', description: 'Per-cell conditional styles.' }, + { name: 'footer', type: 'ReactNode | (rows: T[]) => ReactNode', description: 'Footer cell for this column. Static node or a function receiving the filtered+sorted rows (typically used to render aggregates like sums or averages). When any visible column has a footer, a footer row renders below the body. See Footer.' }, + { name: 'editable', type: 'boolean', description: 'Shorthand for editor: { type: 'text' }. Ignored when editor is also set.', section: 'inline-editing' }, + { name: 'editor', type: 'CellEditor', description: 'Editor configuration. See CellEditor below. Takes precedence over editable.', section: 'inline-editing' }, + { name: 'validate', type: '(value, row, column) => true | false | string', description: 'Gate the edit before onCellEdit fires. Return true to accept, false to reject silently, or a string error to keep the editor open with an inline tooltip.', section: 'inline-editing' }, + { name: 'onCellEdit', type: 'CellEditCallback<T>', description: 'Called when the user commits an edit. Receives (row: T, value: string, column: TableColumn<T>). The value is always a string; parse it to the target type in your handler.', section: 'inline-editing' }, +]; diff --git a/apps/docs/src/layouts/DocsLayout.astro b/apps/docs/src/layouts/DocsLayout.astro index 3a70ce29..82103271 100644 --- a/apps/docs/src/layouts/DocsLayout.astro +++ b/apps/docs/src/layouts/DocsLayout.astro @@ -3,6 +3,7 @@ import Layout from './Layout.astro'; import Demo from '../components/Demo.astro'; import TableOfContents from '../components/TableOfContents.astro'; import HeadlessDemo from '../components/demos/HeadlessDemo.tsx'; +import { nav } from '../data/nav'; interface Props { title?: string; @@ -11,111 +12,6 @@ interface Props { const { title, description } = Astro.props; -const nav = [ - { - group: 'Introduction', - links: [ - { label: 'Getting Started', href: '/docs/getting-started' }, - { label: 'Installation', href: '/docs/installation' }, - { label: 'Comparisons', href: '/docs/comparisons' }, - { label: 'Changelog', href: '/docs/changelog' }, - { label: 'llms.txt', href: '/llms.txt' }, - ], - }, - { - group: 'Reference', - links: [ - { label: 'API Reference', href: '/docs/api' }, - { label: 'Migration Guide', href: '/docs/migration' }, - ], - }, - { - group: 'Columns', - links: [ - { label: 'Overview', href: '/docs/columns' }, - { label: 'Cell Rendering', href: '/docs/cells' }, - { label: 'Column Groups', href: '/docs/column-groups' }, - { label: 'Column Visibility', href: '/docs/column-visibility' }, - { label: 'Column Reordering', href: '/docs/column-reorder' }, - { label: 'Column Resizing', href: '/docs/resizable' }, - { label: 'Column Pinning', href: '/docs/column-pinning' }, - { label: 'Column Separators', href: '/docs/column-separators' }, - { label: 'Column Filtering', href: '/docs/filtering' }, - { label: 'Sorting', href: '/docs/sorting' }, - ], - }, - { - group: 'Editing', - links: [ - { label: 'Inline Editing', href: '/docs/inline-editing' }, - ], - }, - { - group: 'Rows', - links: [ - { label: 'Row Interactions', href: '/docs/row-interactions' }, - { label: 'Row Selection', href: '/docs/selection' }, - { label: 'Row Pinning', href: '/docs/row-pinning' }, - { label: 'Expandable Rows', href: '/docs/expandable' }, - { label: 'Conditional Styles', href: '/docs/conditional-styles' }, - { label: 'Fixed Header', href: '/docs/fixed-header' }, - { label: 'Pagination', href: '/docs/pagination' }, - { label: 'Footer', href: '/docs/footer' }, - { label: 'Loading State', href: '/docs/loading' }, - ], - }, - { - group: 'Interactivity', - links: [ - { label: 'Animations', href: '/docs/animations' }, - { label: 'Accessibility', href: '/docs/accessibility' }, - { label: 'Keyboard Navigation', href: '/docs/keyboard-navigation' }, - { label: 'Context Menu', href: '/docs/context-menu' }, - { label: 'Localization', href: '/docs/localization' }, - { label: 'RTL Support', href: '/docs/rtl' }, - { label: 'Mobile', href: '/docs/mobile' }, - ], - }, - { - group: 'Styling', - links: [ - { label: 'Themes', href: '/docs/themes' }, - { label: 'Custom Styles', href: '/docs/custom-styles' }, - ], - }, - { - group: 'Import / Export', - links: [ - { label: 'Export (CSV / JSON)', href: '/docs/export' }, - ], - }, - { - group: 'Advanced', - links: [ - { label: 'Headless Hooks', href: '/docs/headless' }, - { label: 'TypeScript', href: '/docs/typescript' }, - { label: 'SSR', href: '/docs/ssr' }, - { label: 'Performance', href: '/docs/performance' }, - ], - }, - { - group: 'Recipes', - links: [ - { label: 'Approval workflow', href: '/docs/recipes/approval-workflow' }, - { label: 'Server-side sort & filter', href: '/docs/recipes/server-side' }, - { label: 'URL-synced table state', href: '/docs/recipes/url-synced-state' }, - { label: 'Dashboard drill-down', href: '/docs/recipes/dashboard-drilldown' }, - { label: 'Audit log viewer', href: '/docs/recipes/audit-log' }, - { label: 'Persist column widths', href: '/docs/recipes/persist-column-widths' }, - { label: 'Inline row actions', href: '/docs/recipes/inline-row-actions' }, - { label: 'Live-updating grid', href: '/docs/recipes/live-updates' }, - { label: 'Editable inventory grid', href: '/docs/recipes/inventory-editor' }, - { label: 'Grouped rows with aggregates', href: '/docs/recipes/row-groups' }, - { label: 'CSV import', href: '/docs/recipes/csv-import' }, - ], - }, -]; - const currentPath = Astro.url.pathname; // Flatten nav to a single ordered list for prev/next (excludes non-page links like llms.txt) @@ -182,7 +78,7 @@ const nextLink = currentIndex < allLinks.length - 1 ? allLinks[currentIndex + 1] {currentPath.endsWith('/headless') && ( - {/* Filter inputs — your own markup */} + {/* Filter inputs: your own markup */}
{tableColumns.map(col => ( ))}
- {/* Table — completely custom markup */} + {/* Table: completely custom markup */} @@ -258,7 +154,7 @@ function HeadlessTable({ data }: { data: Employee[] }) { ))}
- {/* Pagination — your own controls */} + {/* Pagination: your own controls */} Page {currentPage} @@ -266,7 +162,7 @@ function HeadlessTable({ data }: { data: Employee[] }) { ); }`} > - +
)} diff --git a/apps/docs/src/layouts/Layout.astro b/apps/docs/src/layouts/Layout.astro index da9b70c7..30998385 100644 --- a/apps/docs/src/layouts/Layout.astro +++ b/apps/docs/src/layouts/Layout.astro @@ -201,7 +201,7 @@ const jsonLd = JSON.stringify({ if (!pagefindLoaded) { pagefindLoaded = true; - // pagefind-ui.js is an IIFE that sets window.PagefindUI — not an ES module. + // pagefind-ui.js is an IIFE that sets window.PagefindUI, not an ES module. // Use script/link tag injection so Vite never touches these URLs. // The files only exist after `astro build && pagefind --site dist`. const base = '/pagefind/'; diff --git a/apps/docs/src/pages/docs/accessibility.md b/apps/docs/src/pages/docs/accessibility.md index 05575fd7..4f8e0365 100644 --- a/apps/docs/src/pages/docs/accessibility.md +++ b/apps/docs/src/pages/docs/accessibility.md @@ -31,7 +31,7 @@ Always provide `ariaLabel` so screen readers can identify `DataTable`. Without i | Element | Role / attribute | | --- | --- | -| Table wrapper | `role="table"` (or `role="grid"` with `cellNavigation` — see below), `aria-label` (from `ariaLabel` prop), `aria-busy` during load | +| Table wrapper | `role="table"` (or `role="grid"` with `cellNavigation`, see below), `aria-label` (from `ariaLabel` prop), `aria-busy` during load | | Header section | `role="rowgroup"` | | Body section | `role="rowgroup"` | | Header row | `role="row"` | @@ -43,7 +43,7 @@ Always provide `ariaLabel` so screen readers can identify `DataTable`. Without i With `columnGroups`, the column headers sit in a single `role="row"`; the group labels above them are visual only. -By default this is a **static table**: nothing but sortable headers is focusable, which is the right shape for a screen reader's native table-reading commands. Passing `cellNavigation` turns it into an interactive **grid** (WAI-ARIA grid pattern) instead — every cell becomes focusable via a single roving tab stop, and arrow keys move between them. See [Keyboard navigation](/docs/keyboard-navigation) for the full key reference and the reasoning for making this opt-in rather than the default. +By default this is a **static table**: nothing but sortable headers is focusable, which is the right shape for a screen reader's native table-reading commands. Passing `cellNavigation` turns it into an interactive **grid** (WAI-ARIA grid pattern) instead: every cell becomes focusable via a single roving tab stop, and arrow keys move between them. See [Keyboard navigation](/docs/keyboard-navigation) for the full key reference and the reasoning for making this opt-in rather than the default. --- diff --git a/apps/docs/src/pages/docs/animations.astro b/apps/docs/src/pages/docs/animations.astro index 7e34dfe4..3e55e4a6 100644 --- a/apps/docs/src/pages/docs/animations.astro +++ b/apps/docs/src/pages/docs/animations.astro @@ -64,7 +64,7 @@ export default function App() { ); }`} > - +

What animates

diff --git a/apps/docs/src/pages/docs/api.astro b/apps/docs/src/pages/docs/api.astro index de368e7a..9165b9ad 100644 --- a/apps/docs/src/pages/docs/api.astro +++ b/apps/docs/src/pages/docs/api.astro @@ -3,6 +3,7 @@ import DocsLayout from '../../layouts/DocsLayout.astro'; import CodeBlock from '../../components/CodeBlock.astro'; import DocsTable from '../../components/DocsTable.astro'; import PropsTable from '../../components/PropsTable.astro'; +import { columnProps } from '../../data/props'; --- [] = [ id
', 'string | number', 'Stable identity used by defaultSortFieldId, columnGroups, and filterValues.'], - ['name', 'ReactNode', 'Column header label.'], - ['selector', '(row, index?) => Primitive | ReactNode', 'Data accessor. For sortable columns return a Primitive (string | number | boolean | bigint | Date). Rows whose selector returns null or undefined sort to the end. bigint displays as its decimal string; Date displays via toLocaleString(), which follows the viewer's locale and timezone. Use format when you need deterministic or SSR-stable output.'], - ['cell', '(row, index, column, id) => ReactNode', 'Custom cell renderer. Overrides the selector display value.'], - ['format', '(row, index) => ReactNode', 'Format the selector value for display without changing the sort key.'], - ['sortable', 'boolean', 'Enable sorting on this column.'], - ['sortFunction', '(a: T, b: T) => number', 'Custom comparator for this column.'], - ['sortField', 'string', 'Field key passed to onSort for server-side sort identification.'], - ['filterable', 'boolean', 'Show a filter popup for this column.'], - ['filterType', '"text" | "number" | "date" | "datetime" | "time" | "set"', 'Filter operator set and input widget. Defaults to "text". "set" shows a checklist of the column's distinct values instead of operators. See Filtering.'], - ['filterFunction', '(row, filter: FilterState) => boolean', 'Custom filter predicate. Overrides built-in operator logic. Receives the full FilterState including both conditions.'], - ['filterOptions', '{ values?: string[] | ((rows: T[]) => string[]); separator?: string | RegExp }', 'Settings for a "set" filter. values supplies the checklist instead of deriving it from the loaded rows. separator splits a multi-value cell into its parts. See Filtering.'], - ['width', 'string', 'Fixed column width, e.g. "120px".'], - ['minWidth', 'string', 'Minimum width.'], - ['maxWidth', 'string', 'Maximum width.'], - ['grow', 'number', 'Flex-grow factor (default 1). Set to 0 to prevent growing.'], - ['right', 'boolean', 'Right-align cell content.'], - ['center', 'boolean', 'Center cell content.'], - ['wrap', 'boolean', 'Allow cell text to wrap.'], - ['compact', 'boolean', 'Remove cell padding.'], - ['button', 'boolean', 'Center content and suppress row click propagation.'], - ['allowOverflow', 'boolean', 'Allow cell content to overflow (useful for dropdowns and popovers).'], - ['ignoreRowClick', 'boolean', 'Prevent clicks in this cell from firing onRowClicked.'], - ['hide', 'Media', 'Hide the column below the given breakpoint (Media.SM = 599px, MD = 959px, LG = 1280px).'], - ['omit', 'boolean', 'Exclude the column entirely. Toggle this to show/hide a column.'], - ['reorder', 'boolean', 'Allow drag-to-reorder for this column (requires reorder on at least two columns).'], - ['pinned', "'left' | 'right'", 'Freeze the column to an edge during horizontal scroll. Only visible when the table overflows its container — give columns explicit widths. See Column pinning.'], - ['style', 'CSSProperties', 'Inline styles applied to every cell in this column.'], - ['conditionalCellStyles', 'ConditionalStyles<T>[]', 'Per-cell conditional styles.'], - ['footer', 'ReactNode | (rows: T[]) => ReactNode', 'Footer cell for this column. Static node or a function receiving the filtered+sorted rows (typically used to render aggregates like sums or averages). When any visible column has a footer, a footer row renders below the body. See Footer.'], - ]} + rows={columnProps.filter(p => !p.section).map(p => [`${p.name}`, p.type, p.description])} />

Inline editing

editable
', 'boolean', 'Shorthand for editor: { type: 'text' }. Ignored when editor is also set.'], - ['editor', 'CellEditor', 'Editor configuration. See CellEditor below. Takes precedence over editable.'], - ['validate', '(value, row, column) => true | false | string', 'Gate the edit before onCellEdit fires. Return true to accept, false to reject silently, or a string error to keep the editor open with an inline tooltip.'], - ['onCellEdit', 'CellEditCallback<T>', 'Called when the user commits an edit. Receives (row: T, value: string, column: TableColumn<T>). The value is always a string; parse it to the target type in your handler.'], - ]} + rows={columnProps.filter(p => p.section === 'inline-editing').map(p => [`${p.name}`, p.type, p.description])} />

See Inline editing for examples, CSS variables, and styling guidance.

diff --git a/apps/docs/src/pages/docs/cells.astro b/apps/docs/src/pages/docs/cells.astro index 5bb5851e..0d1bd3db 100644 --- a/apps/docs/src/pages/docs/cells.astro +++ b/apps/docs/src/pages/docs/cells.astro @@ -39,7 +39,7 @@ import CellsDemo from '../../components/demos/CellsDemo.tsx'; sortable: true, } -// ✅ Currency column — format preserves sort order +// ✅ Currency column: format preserves sort order { name: 'Salary', selector: row => row.salary, // sort on raw number @@ -100,7 +100,7 @@ import CellsDemo from '../../components/demos/CellsDemo.tsx';

[] = [ }, ];`} > - +

diff --git a/apps/docs/src/pages/docs/column-groups.astro b/apps/docs/src/pages/docs/column-groups.astro index fb761007..975a4108 100644 --- a/apps/docs/src/pages/docs/column-groups.astro +++ b/apps/docs/src/pages/docs/column-groups.astro @@ -67,7 +67,7 @@ export default function App() { return ; }`} > - +

Drag to reorder groups

@@ -158,7 +158,7 @@ export default function App() {

- Each group is a ColumnGroup object — see the API + Each group is a ColumnGroup object. See the API reference for its name, columnIds, align, and reorder fields.

diff --git a/apps/docs/src/pages/docs/column-pinning.astro b/apps/docs/src/pages/docs/column-pinning.astro index 56df1b13..4050c25f 100644 --- a/apps/docs/src/pages/docs/column-pinning.astro +++ b/apps/docs/src/pages/docs/column-pinning.astro @@ -33,12 +33,12 @@ const columns: TableColumn[] = [ ;`} > - +

Columns need widths

- Pinning is only visible when the table overflows its container horizontally — a pinned + Pinning is only visible when the table overflows its container horizontally. A pinned column is position: sticky, and without horizontal scroll there is nothing to stick against. Columns without a width are fluid: they shrink and grow to fill the container, so the table never overflows and pinning appears to do nothing. Give your @@ -57,7 +57,7 @@ const columns: TableColumn[] = [

When responsive is enabled and at least one column is pinned, the table also - renders a custom horizontal scrollbar below the body that excludes the pinned regions — so + renders a custom horizontal scrollbar below the body that excludes the pinned regions, so the scroll thumb only spans the scrollable middle section. The browser's native horizontal scrollbar is hidden in this mode.

@@ -75,7 +75,7 @@ const columns: TableColumn[] = [

This matters most when reorder: true is enabled. Dragging a column into the pinned band pins it; dragging it out unpins it. You don't need to think about pin state - when reordering — the band membership follows position. + when reordering: the band membership follows position.

Pinning multiple columns

@@ -100,7 +100,7 @@ const columns: TableColumn[] = [

With selectable rows or expandable rows

- The checkbox and expander columns are always pinned to the left edge — they don't need + The checkbox and expander columns are always pinned to the left edge. They don't need pinned set. When you also pin a regular column to the left, its offset accounts for the checkbox/expander width automatically.

@@ -116,7 +116,7 @@ const columns: TableColumn[] = [

The system column width defaults to 48px and is controlled by the --rdt-system-col-width CSS variable. Override it in your stylesheet if you - customize the checkbox/expander cell width — pinning offsets will stay aligned because they + customize the checkbox/expander cell width, pinning offsets will stay aligned because they read the same variable.

@@ -155,7 +155,7 @@ const columns: TableColumn[] = [

Customizing the pin boundary

- A crisp 1px line marks the inner edge of each pin band — class + A crisp 1px line marks the inner edge of each pin band: class rdt_pinLeftLast on the rightmost left-pinned column and rdt_pinRightFirst on the leftmost right-pinned column. It defaults to the theme's divider color. Override it via the --rdt-color-pin-border CSS variable: @@ -174,10 +174,10 @@ const columns: TableColumn[] = [

Requirements

    -
  • Each pinned column must have an explicit id — the offset map is keyed by column id.
  • +
  • Each pinned column must have an explicit id. The offset map is keyed by column id.
  • Pinned columns should have a fixed width for predictable offsets. Columns without a width default to 100px for offset math.
  • The table's scroll container must be allowed to scroll horizontally. Don't set overflow: hidden on a parent.
  • -
  • Not compatible with columnGroups — see above.
  • +
  • Not compatible with columnGroups (see above).

Prop reference

diff --git a/apps/docs/src/pages/docs/column-reorder.astro b/apps/docs/src/pages/docs/column-reorder.astro index f62270ad..3ec2eb62 100644 --- a/apps/docs/src/pages/docs/column-reorder.astro +++ b/apps/docs/src/pages/docs/column-reorder.astro @@ -66,7 +66,7 @@ export default function App() { return ; }`} > - +

Enabling reorder per column

@@ -78,7 +78,7 @@ export default function App() {

[] = [ - { id: 'name', name: 'Name', selector: r => r.name /* no reorder — pinned */ }, + { id: 'name', name: 'Name', selector: r => r.name /* no reorder, pinned */ }, { id: 'role', name: 'Role', selector: r => r.role, reorder: true }, { id: 'dept', name: 'Dept', selector: r => r.dept, reorder: true }, { id: 'salary', name: 'Salary', selector: r => r.salary, reorder: true }, @@ -178,11 +178,11 @@ export default function App() { ); }`} > - +

- You can combine both — set reorder: true on columns AND on groups. + You can combine both: set reorder: true on columns AND on groups. Column drag is automatically restricted to within the same group, so spans stay correct. Group drag moves the entire block. This mirrors the AG Grid reorder experience.

diff --git a/apps/docs/src/pages/docs/column-separators.astro b/apps/docs/src/pages/docs/column-separators.astro index 8601dad8..159b6caa 100644 --- a/apps/docs/src/pages/docs/column-separators.astro +++ b/apps/docs/src/pages/docs/column-separators.astro @@ -22,10 +22,10 @@ import ColumnSeparatorsDemo from '../../components/demos/ColumnSeparatorsDemo.ts // Default: header separators on (subtle), no body separators -// Body separators — subtle inset (60% height, centered) +// Body separators: subtle inset (60% height, centered) -// Body separators — full-height +// Body separators: full-height // No header separators @@ -34,7 +34,7 @@ import ColumnSeparatorsDemo from '../../components/demos/ColumnSeparatorsDemo.ts // Full-height header separators + subtle body separators `} > - +

Values

diff --git a/apps/docs/src/pages/docs/column-visibility.astro b/apps/docs/src/pages/docs/column-visibility.astro index 5b79d4d7..0d9fdff6 100644 --- a/apps/docs/src/pages/docs/column-visibility.astro +++ b/apps/docs/src/pages/docs/column-visibility.astro @@ -41,18 +41,18 @@ export default function App() { ); }`} > - +

Hook API

[] — pass directly to DataTable, omit is managed for you - entries, // { column, visible }[] — drive your toggle UI from this - toggleColumn, // (columnId) => void — flip one column's visibility - isVisible, // (columnId) => boolean — check a specific column - showAll, // () => void — make all columns visible - hideAll, // () => void — hide all columns + columns, // TableColumn[]: pass directly to DataTable, omit is managed for you + entries, // { column, visible }[]: drive your toggle UI from this + toggleColumn, // (columnId) => void: flip one column's visibility + isVisible, // (columnId) => boolean: check a specific column + showAll, // () => void: make all columns visible + hideAll, // () => void: hide all columns } = useColumnVisibility(initialColumns);`} />

Column IDs are required

diff --git a/apps/docs/src/pages/docs/columns.astro b/apps/docs/src/pages/docs/columns.astro index b9c3f57f..1e1278ee 100644 --- a/apps/docs/src/pages/docs/columns.astro +++ b/apps/docs/src/pages/docs/columns.astro @@ -102,7 +102,7 @@ export default function App() { ); }`} > - +

Basic column definition

diff --git a/apps/docs/src/pages/docs/conditional-styles.astro b/apps/docs/src/pages/docs/conditional-styles.astro index f29b5cea..933d1239 100644 --- a/apps/docs/src/pages/docs/conditional-styles.astro +++ b/apps/docs/src/pages/docs/conditional-styles.astro @@ -10,12 +10,12 @@ import ConditionalStylesDemo from '../../components/demos/ConditionalStylesDemo.

Conditional styles

- Apply styles or CSS class names to rows and cells based on row data — no custom + Apply styles or CSS class names to rows and cells based on row data, no custom cell renderer required.

[] = [ ;`} > - +

conditionalRowStyles

@@ -144,7 +144,7 @@ const conditionalRowStyles: ConditionalStyles[] = [

Conditional row styles are applied after the base row style, so they override the default background. The hover highlight from highlightOnHover will override a conditional - background on hover — which is usually the desired behavior. If you need the conditional color + background on hover, which is usually the desired behavior. If you need the conditional color to persist on hover, set it via rows.highlightOnHoverStyle in customStyles.

diff --git a/apps/docs/src/pages/docs/context-menu.astro b/apps/docs/src/pages/docs/context-menu.astro index cc1ab7b0..cd0144b8 100644 --- a/apps/docs/src/pages/docs/context-menu.astro +++ b/apps/docs/src/pages/docs/context-menu.astro @@ -13,8 +13,8 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx';

Set contextMenu to give the table a built-in context menu. Header cells get a - menu of built-in actions — sort ascending/descending, clear sort, pin left/right, hide - column, and reset — plus any custom items you add. Rows get a menu of whatever actions you + menu of built-in actions (sort ascending/descending, clear sort, pin left/right, hide + column, and reset) plus any custom items you add. Rows get a menu of whatever actions you supply via contextMenuActions.row (edit, delete, duplicate, and so on); with no actions supplied, rows have no menu. The menu opens on right-click, from a menu (⋮) button, or both. @@ -22,13 +22,13 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx'; { if (ctx.type === 'header') console.log(action.id, ctx.column); @@ -36,7 +36,7 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx'; resizable />;`} > - +

Enabling the menu

@@ -46,15 +46,15 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx';

    -
  • 'right-click' (default) — right-click a header cell or a row to open its menu.
  • -
  • 'menu-button' — show a ⋮ menu button instead; the best choice for touch-first UIs, where right-click isn't available. Row menu buttons appear on hover and are always visible on touch devices.
  • -
  • 'both' — right-click and a ⋮ menu button.
  • +
  • 'right-click' (default): right-click a header cell or a row to open its menu.
  • +
  • 'menu-button': show a ⋮ menu button instead; the best choice for touch-first UIs, where right-click isn't available. Row menu buttons appear on hover and are always visible on touch devices.
  • +
  • 'both': right-click and a ⋮ menu button.
-// Menu buttons only — the right choice for touch-first UIs +// Menu buttons only, the right choice for touch-first UIs // Both triggers, header menu only @@ -67,7 +67,7 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx';

contextMenu on its own gives you the header menu. The row menu has no built-in actions, so it stays off until you supply them via - contextMenuActions.row — a function that returns the menu items for a given + contextMenuActions.row, a function that returns the menu items for a given row. It receives the row and its index, so you can vary or disable items per row. A row that returns an empty array (or no row function at all) has no menu, and right-clicking it falls through to the browser's native menu. @@ -75,7 +75,7 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx'; [ // 1. Turn the feature on (right-click, menu-button, or both). // menuPosition moves the row ⋮ button: 'end' (default) or 'start'. contextMenu={{ trigger: 'both', menuPosition: 'end' }} - // 2. Supply the row menu items — without this, rows have no menu + // 2. Supply the row menu items. Without this, rows have no menu contextMenuActions={{ row: rowActions }} // 3. Handle the selection onContextMenuAction={(action, ctx) => { @@ -101,22 +101,22 @@ const rowActions = (row: Task): ContextMenuAction[] => [ }} />;`} > - +

Built-in header actions

    -
  • Sort ascending / descending / clear sort — shown for sortable columns. Explicit direction, no cycling.
  • -
  • Pin left / Pin right / Unpin — wires to the same mechanism as column pinning; the column moves into the pinned band. Pinning is only visible when the table scrolls horizontally, so give columns explicit widths (see columns need widths). Hidden when column groups are used, since pinning would tear a column out of its group.
  • -
  • Hide column — removes the column from view. Disabled when it is the last visible column.
  • -
  • Reset table — restores the original column order, visibility, and pin state, clears the sort, and clears all column filters.
  • +
  • Sort ascending / descending / clear sort: shown for sortable columns. Explicit direction, no cycling.
  • +
  • Pin left / Pin right / Unpin: wires to the same mechanism as column pinning; the column moves into the pinned band. Pinning is only visible when the table scrolls horizontally, so give columns explicit widths (see columns need widths). Hidden when column groups are used, since pinning would tear a column out of its group.
  • +
  • Hide column: removes the column from view. Disabled when it is the last visible column.
  • +
  • Reset table: restores the original column order, visibility, and pin state, clears the sort, and clears all column filters.

Custom actions

Header items are appended after the built-ins, as a static list or a function of the column. Row items are always a function of the row. Selections arrive in - onContextMenuAction — built-ins fire it too, after their effect is applied, + onContextMenuAction. Built-ins fire it too, after their effect is applied, so you can persist state changes (for example the new pin layout via onColumnOrderChange).

@@ -146,12 +146,12 @@ const rowActions = (row: Task): ContextMenuAction[] => [
  • The menu renders with role="menu" / role="menuitem"; Arrow keys, Home, and End move focus, Escape closes and returns focus to the trigger.
  • Menu buttons carry aria-haspopup="menu" and localizable labels; row menu buttons appear on hover/focus and are always visible on touch devices.
  • -
  • On filterable columns the header menu button renders after the filter icon, so it is always the outermost control — mirrored automatically in RTL.
  • +
  • On filterable columns the header menu button renders after the filter icon, so it is always the outermost control, mirrored automatically in RTL.
  • The menu flips above the pointer near the bottom edge and clamps within the viewport; it closes on outside click, scroll, and resize.
  • The built-in actions carry icons. If any item in a menu has one, items without an icon still reserve the space, so every label starts at the same offset.
  • The menu fades in on open, and holds still when prefers-reduced-motion: reduce is set.
  • In RTL the button-anchored menu aligns to the button's opposite edge automatically.
  • -
  • All labels can be translated via localization.contextMenu — see Localization. The bundled locales include translations.
  • +
  • All labels can be translated via localization.contextMenu. See Localization. The bundled locales include translations.

Props

diff --git a/apps/docs/src/pages/docs/custom-styles.astro b/apps/docs/src/pages/docs/custom-styles.astro index 488bd4e4..a80935f9 100644 --- a/apps/docs/src/pages/docs/custom-styles.astro +++ b/apps/docs/src/pages/docs/custom-styles.astro @@ -104,7 +104,7 @@ export default function App() { ); }`} > - +

customStyles: targeting table parts

@@ -219,7 +219,7 @@ const conditionalRowStyles: ConditionalStyles[] = [

- customStyles takes a TableStyles object — - see the API reference for every styleable slot. + customStyles takes a TableStyles object. + See the API reference for every styleable slot.

diff --git a/apps/docs/src/pages/docs/expandable.astro b/apps/docs/src/pages/docs/expandable.astro index f0557776..792cb99b 100644 --- a/apps/docs/src/pages/docs/expandable.astro +++ b/apps/docs/src/pages/docs/expandable.astro @@ -110,7 +110,7 @@ export default function App() { ); }`} > - +

The expander component

@@ -183,7 +183,7 @@ export default function App() {

Expand all / collapse all

expandableRowExpanded re-syncs any time its result changes, so a single boolean - in state — driven by an "Expand all" button — expands or collapses every row at once. Rows a + in state, driven by an "Expand all" button, expands or collapses every row at once. Rows a user has manually toggled since then are left alone until the shared state changes again:

Localization

Override the aria-labels on the expand/collapse button via the localization prop. - Use a pre-built locale or supply your own — every key is optional. + Use a pre-built locale or supply your own. Every key is optional.

useTableExport is a headless hook that turns your columns and rows into CSV or JSON. It can also trigger a browser download or copy to the clipboard. It does not - touch your DataTable; you pass it the same data the table is rendering — or a filtered / + touch your DataTable; you pass it the same data the table is rendering, or a filtered / sorted slice if you only want to export the "current view".

@@ -62,7 +62,7 @@ export default function App() { ); }`} > - +

Basic usage

@@ -105,7 +105,7 @@ function EmployeesTable({ data }: { data: Employee[] }) {

DataTable owns sort and pagination state internally, but you can lift them out by using - the headless hooks directly — or just keep a copy of the filtered/sorted rows via the + the headless hooks directly, or just keep a copy of the filtered/sorted rows via the callbacks. The simplest pattern for "what the user is looking at right now" is to drive DataTable from the same hooks that feed useTableExport:

@@ -144,7 +144,7 @@ function App() {

For server-side data, just pass whatever rows the server returned for your current - filters — useTableExport doesn't care where the rows came from. + filters. useTableExport doesn't care where the rows came from.

Customizing column output

@@ -210,7 +210,7 @@ function App() {

Values containing commas, quotes, or newlines are wrapped in double quotes and any - interior quotes are doubled — the standard RFC 4180 rule. Booleans and numbers are + interior quotes are doubled, the standard RFC 4180 rule. Booleans and numbers are coerced with String(). null and undefined become empty cells. Anything more complex (objects, React nodes) is run through JSON.stringify as a safety net. diff --git a/apps/docs/src/pages/docs/filtering.astro b/apps/docs/src/pages/docs/filtering.astro index 7d2f44f3..f89252fb 100644 --- a/apps/docs/src/pages/docs/filtering.astro +++ b/apps/docs/src/pages/docs/filtering.astro @@ -66,7 +66,7 @@ export default function App() { return ; }`} > - +

@@ -158,7 +158,7 @@ export default function App() { // ... ];`} > - +

Set filters: pick from a checklist

@@ -185,7 +185,7 @@ export default function App() { // ... ];`} > - +
    @@ -264,7 +264,7 @@ const columns: TableColumn[] = [ }} />;`} > - +

    @@ -323,7 +323,7 @@ const columns: TableColumn[] = [ }, ];`} > - +

      @@ -400,7 +400,7 @@ const columns: TableColumn[] = [ const nameFilter: FilterState = { condition1: { operator: 'startsWith', value: 'J' }, condition2: { operator: 'endsWith', value: 'son' }, - logic: 'AND', // 'AND' | 'OR' — defaults to 'AND' + logic: 'AND', // 'AND' | 'OR', defaults to 'AND' }; // Set filter: "keep Engineering and Design rows, plus rows with no department" @@ -446,9 +446,9 @@ emptyFilterState('number'); // { condition1: { operator: 'equals' } } emptyFilterState('text'); // { condition1: { operator: 'contains' } } // Check whether a FilterState is actually filtering anything -isFilterActive({ condition1: { operator: 'contains' } }); // false — no value +isFilterActive({ condition1: { operator: 'contains' } }); // false, no value isFilterActive({ condition1: { operator: 'contains', value: 'a' } }); // true -isFilterActive({ condition1: { operator: 'blank' } }); // true — no value needed`} /> +isFilterActive({ condition1: { operator: 'blank' } }); // true, no value needed`} />

      Custom filter function

      diff --git a/apps/docs/src/pages/docs/fixed-header.astro b/apps/docs/src/pages/docs/fixed-header.astro index 0a2a4034..f955479b 100644 --- a/apps/docs/src/pages/docs/fixed-header.astro +++ b/apps/docs/src/pages/docs/fixed-header.astro @@ -43,7 +43,7 @@ const columns: TableColumn[] = [ highlightOnHover />;`} > - +

      How it works

      @@ -130,7 +130,7 @@ const columns: TableColumn[] = [ [] = [ onScroll={e => setScrollTop(Math.round((e.target as HTMLDivElement).scrollTop))} />;`} > - +

      Prop reference

      diff --git a/apps/docs/src/pages/docs/footer.astro b/apps/docs/src/pages/docs/footer.astro index aa60aedb..6e89863d 100644 --- a/apps/docs/src/pages/docs/footer.astro +++ b/apps/docs/src/pages/docs/footer.astro @@ -21,12 +21,12 @@ import FooterCustomDemo from '../../components/demos/FooterCustomDemo.tsx';
      • - Column-level footer — the 80% case. Add a footer field + Column-level footer: the 80% case. Add a footer field to any TableColumn. The footer row appears automatically when at least one visible column has one.
      • - footerComponent prop — escape hatch. Replace the entire footer + footerComponent prop: escape hatch. Replace the entire footer row with a custom component when you need multi-row summaries, custom layouts, or aggregates that span columns.
      • @@ -36,13 +36,13 @@ import FooterCustomDemo from '../../components/demos/FooterCustomDemo.tsx';

        Pass footer as either a static ReactNode or a function that receives - the filtered+sorted rows and returns a node. Use the function form to compute aggregates — + the filtered+sorted rows and returns a node. Use the function form to compute aggregates: it sees exactly the rows the user sees in the body, so totals update automatically when you sort or filter.

        [] = [ selector: r => r.salary, right: true, format: r => \`$\${r.salary.toLocaleString()}\`, - // aggregate function — receives the current visible rows + // aggregate function: receives the current visible rows footer: rows => \`$\${rows.reduce((s, r) => s + r.salary, 0).toLocaleString()}\`, }, { @@ -82,12 +82,12 @@ const columns: TableColumn[] = [ ;`} > - +

        Each footer cell inherits its column's right, center, - width, minWidth, and maxWidth — so totals line up + width, minWidth, and maxWidth, so totals line up under their column automatically, including after a resize or sort.

        @@ -101,12 +101,12 @@ const columns: TableColumn[] = [

        Pass footerComponent to replace the footer row entirely. The component receives - {`{ rows, columns }`} and can render whatever it likes — multi-stat summaries, + {`{ rows, columns }`} and can render whatever it likes: multi-stat summaries, action buttons, contextual copy, anything.

        ) { ;`} > - +

        @@ -163,9 +163,9 @@ function SummaryFooter({ rows }: FooterComponentProps) {

          -
        • showFooter — force the footer row on even if no column defines a footer.
        • -
        • showFooter={`{false}`} — suppress the footer entirely, overriding column footers and footerComponent. Useful for toggling the summary on and off.
        • -
        • Omitted (default) — auto: render when any visible column has footer or footerComponent is provided.
        • +
        • showFooter: force the footer row on even if no column defines a footer.
        • +
        • showFooter={`{false}`}: suppress the footer entirely, overriding column footers and footerComponent. Useful for toggling the summary on and off.
        • +
        • Omitted (default): render automatically when any visible column has footer or footerComponent is provided.

        @@ -223,7 +223,7 @@ function SummaryFooter({ rows }: FooterComponentProps) {

        Pinned columns

        - Footer cells in pinned columns stick to the table edge exactly like their body cells — no + Footer cells in pinned columns stick to the table edge exactly like their body cells, no extra config required. Pinning offsets are computed from the same source as the rest of the table, so they stay aligned after resizing.

        @@ -241,7 +241,7 @@ function SummaryFooter({ rows }: FooterComponentProps) {
      • For multi-row footers, group subtotals, or any layout that doesn't map 1:1 to columns, - reach for footerComponent — it renders inside a role="rowgroup" + reach for footerComponent. It renders inside a role="rowgroup" container for accessibility and has no constraints on its internal structure.
      diff --git a/apps/docs/src/pages/docs/headless.astro b/apps/docs/src/pages/docs/headless.astro index 89cfab39..887e4480 100644 --- a/apps/docs/src/pages/docs/headless.astro +++ b/apps/docs/src/pages/docs/headless.astro @@ -140,7 +140,7 @@ graph TD <DataTable />'s ARIA roles switch from table/cell to grid/gridcell when cellNavigation is enabled, adding keyboard grid navigation on top of the same tree. The useCellNavigation hook below is the same - logic that powers it — compose it into your own markup to get the same roving-tabindex behavior. See + logic that powers it. Compose it into your own markup to get the same roving-tabindex behavior. See Keyboard navigation for the interaction pattern it implements.

      @@ -160,14 +160,14 @@ graph TD

      Feature hooks

      -

      Compose any of these on top of the pipeline as needed — none of them depend on each other.

      +

      Compose any of these on top of the pipeline as needed. None of them depend on each other.

      useColumnVisibility', 'Show/hide columns by id, independent of the sort/filter pipeline'], ['useColumnResize', 'Drag-to-resize column widths (mouse-driven, DOM-only state)'], - ['useCellNavigation', 'Keyboard grid navigation — arrows, Home/End, roving tabindex. Same logic behind cellNavigation'], + ['useCellNavigation', 'Keyboard grid navigation: arrows, Home/End, roving tabindex. Same logic behind cellNavigation'], ['useTableExport', 'Generates CSV / JSON from columns and rows, with download + clipboard helpers'], ['getPinnedOffsets / getPinnedTotalWidths / getPinnedCellMeta', 'Pure functions (not hooks) that compute sticky-column offsets and shadow metadata for column.pinned'], ]} @@ -256,7 +256,7 @@ function MyTable({ data }: { data: Person[] }) { onSort: () => {}, }); - // 4. Optional: per-column filtering (uncontrolled — internal state) + // 4. Optional: per-column filtering (uncontrolled, internal state) const { filteredData } = useColumnFilter(tableColumns); const rows = filteredData(tableRows); @@ -403,7 +403,7 @@ function MyTable({ data }: { data: Person[] }) { ['rowsPerPage', 'number'], ['selectedColumn', 'TableColumn<T>'], ['sortDirection', 'SortOrder'], - ['sortColumns', 'SortColumn<T>[] — full sort config in priority order; required by useTableData'], + ['sortColumns', 'SortColumn<T>[]: full sort config in priority order; required by useTableData'], ['selectedRows', 'T[]'], ['selectedCount', 'number'], ['allSelected', 'boolean'], @@ -433,7 +433,7 @@ function MyTable({ data }: { data: Person[] }) { ]} /> -

      sortColumns (from tableState.sortColumns) is required — omitting it is a type error.

      +

      sortColumns (from tableState.sortColumns) is required. Omitting it is a type error.

      ( />

      - filteredData is a stable memoised function — safe to call in render. + filteredData is a stable memoized function, safe to call in render. Each column can define filterFunction: (row: T, filter: FilterState) => boolean to override the built-in operator logic. See Filtering for the full FilterState type and operator reference. @@ -501,7 +501,7 @@ function useColumnFilter(

      Powers the WAI-ARIA grid pattern behind cellNavigation: arrow keys, Home/End, Ctrl+Home/Ctrl+End, and a roving tabindex. It reads and writes plain - data-nav-row / data-nav-col attributes on your own cells — it doesn't know or + data-nav-row / data-nav-col attributes on your own cells. It doesn't know or care what markup they're attached to.

      @@ -519,7 +519,7 @@ function useColumnFilter( ['expandableRows', 'boolean', 'Reserves the next nav column for an expander button, if you render one.'], ['expandableRowsHideExpander', 'boolean', 'Skip the expander column reservation even when expandableRows is true.'], ['effectiveColumns', 'TableColumn<T>[]', 'Your current column list. Columns with omit: true are excluded from the nav grid.'], - ['filteredTableRowCount', 'number', 'Row count for the currently visible page — clamps ArrowDown/End.'], + ['filteredTableRowCount', 'number', 'Row count for the currently visible page. Clamps ArrowDown/End.'], ['showTableHead', 'boolean', 'Whether a header row participates in the grid (row -1). false removes header cells from the tab sequence entirely.'], ]} /> @@ -571,7 +571,7 @@ function MyTable({ data, columns }) {

      Cells that hold an interactive widget instead of plain text (a checkbox, an expander button) should - also set data-nav-widget="true" — the hook then focuses the widget itself rather than + also set data-nav-widget="true", and the hook then focuses the widget itself rather than the cell wrapper, so Space/Enter activate it natively. See Keyboard navigation for the full key reference this reproduces. @@ -596,7 +596,7 @@ function MyTable({ data, columns }) {

      Attach handleResizeStart to a drag handle's onMouseDown. The hook tracks - mousemove/mouseup on document itself — you don't need to wire + mousemove/mouseup on document itself, so you don't need to wire those up. Apply columnWidths[column.id] as an inline width on your header and body cells for that column.

      @@ -606,7 +606,7 @@ function MyTable({ data, columns }) {

      Pinning: getPinnedOffsets, getPinnedTotalWidths, getPinnedCellMeta

      - Column pinning has no hook — there's no state to own beyond column.pinned on your own + Column pinning has no hook. There's no state to own beyond column.pinned on your own column list. These are pure functions: given your columns and current widths, they compute sticky offsets and shadow metadata.

      @@ -648,7 +648,7 @@ function Cell({ column, pinnedOffsets }: { column: TableColumn; pinnedOffse getPinnedCellMeta returns a style object (position: 'sticky', the computed left/right offset, and zIndex) that's enough to render pinned columns on its own. className adds rdt_pinLeftLast / - rdt_pinRightFirst on the pinned boundary's edge column — those classes only draw a + rdt_pinRightFirst on the pinned boundary's edge column. Those classes only draw a shadow if you import react-data-table-component/css (or match the selectors yourself); they're no-ops otherwise. See Column pinning for the column.pinned API these functions read. @@ -712,7 +712,7 @@ useEffect(() => {

      Generates CSV or JSON from any set of columns and rows. Pair it with useTableData and useColumnFilter to export exactly what the - user sees — sorted, filtered, and paginated — rather than the raw dataset. + user sees (sorted, filtered, and paginated) rather than the raw dataset.

      (options: UseTableExportOptions): UseTableExportResult;`} /> @@ -742,7 +742,7 @@ useEffect(() => { ]} /> -

      Example — export the current filtered view

      +

      Example: export the current filtered view

      [] = [ - // Text editor — editable: true is shorthand for { editor: { type: 'text' } } + // Text editor. editable: true is shorthand for { editor: { type: 'text' } } { id: 'name', name: 'Name', @@ -111,7 +111,7 @@ export default function App() { return ; }`} > - +

      How it works

      @@ -129,7 +129,7 @@ export default function App() {

      Pick the right editor for the value type. Every editor calls onCellEdit(row, value, column) - with the new value as a string — you parse it into a number, boolean, or + with the new value as a string. You parse it into a number, boolean, or date in your update handler.

      @@ -143,7 +143,7 @@ export default function App() { { id: 'name', name: 'Name', selector: r => r.name, editable: true, onCellEdit } -// Equivalent explicit form — supports placeholder +// Equivalent explicit form, supports placeholder { id: 'name', name: 'Name', selector: r => r.name, editor: { type: 'text', placeholder: 'Enter a name…' }, onCellEdit }`} /> @@ -189,7 +189,7 @@ export default function App() { r.salary, editor: { type: 'number', min: 0, step: 1000 }, - onCellEdit, // value arrives as a string — Number(value) it on commit + onCellEdit, // value arrives as a string, so Number(value) it on commit }`} />

      Date picker

      @@ -272,7 +272,7 @@ export default function App() {

      The ctx.row field gives you the row data for context-aware editors - (dependent dropdowns, etc.). The cell wrapper still owns padding and the focus ring — + (dependent dropdowns, etc.). The cell wrapper still owns padding and the focus ring, so your custom editor only needs to render the input.

      @@ -346,7 +346,7 @@ export default function App() { };`} /> - +

      Validation

      @@ -405,9 +405,9 @@ export default function App() { onCellEdit fires. Return:

        -
      • true — accept the value, commit the edit.
      • -
      • false — reject silently, close the editor without firing the callback.
      • -
      • A string — keep the editor open, paint an error ring, and show the message as an inline tooltip.
      • +
      • true: accept the value, commit the edit.
      • +
      • false: reject silently, close the editor without firing the callback.
      • +
      • A string: keep the editor open, paint an error ring, and show the message as an inline tooltip.
      [] = [

      Keyboard cell navigation

      - Pass cellNavigation to navigate the whole table with the keyboard, spreadsheet-style — + Pass cellNavigation to navigate the whole table with the keyboard, spreadsheet-style: arrow keys between cells, Enter/F2 to open the focused cell's editor, and Escape to cancel and return focus to the cell. See Keyboard navigation for the full key reference; it also @@ -575,7 +575,7 @@ interface CustomCellEditorContext { />

      - cellNavigation is a table-level prop covered on its own page — see + cellNavigation is a table-level prop covered on its own page. See Keyboard navigation.

      diff --git a/apps/docs/src/pages/docs/keyboard-navigation.astro b/apps/docs/src/pages/docs/keyboard-navigation.astro index 31e6a542..d558cfac 100644 --- a/apps/docs/src/pages/docs/keyboard-navigation.astro +++ b/apps/docs/src/pages/docs/keyboard-navigation.astro @@ -33,7 +33,7 @@ import PropsTable from '../../components/PropsTable.astro'; ['← → ↑ ↓', 'Move between cells. ↑ from the first body row moves into the header row. Clamped at the edges, no wraparound.'], ['Home / End', 'Jump to the first / last cell in the current row.'], ['Ctrl+Home / Ctrl+End', 'Jump to the first / last cell of the grid.'], - ['Enter or F2', "Open the focused cell's editor, if it has one (editable/editor — see Inline editing)."], + ['Enter or F2', "Open the focused cell's editor, if it has one (editable/editor, see Inline editing)."], ['Escape', 'Cancel an open editor and return focus to the cell so navigation can continue.'], ['Enter or Space on a header', 'Sort by that column (sortable columns only).'], ['Space on a checkbox / Enter on an expander', 'Toggle row selection / expand or collapse the row.'], @@ -50,24 +50,24 @@ import PropsTable from '../../components/PropsTable.astro';

      The base behavior works on any table, editable or not. Arrow keys move one cell at a time and - clamp at the edges — there's no wraparound from the last column back to the first. Home + clamp at the edges. There's no wraparound from the last column back to the first. Home and End jump to the first / last cell in the current row; add Ctrl (⌘ on macOS) to jump to the first / last cell of the whole grid instead.

      ;`} > - +

      Sorting from the keyboard

      Arrow ↑ out of the first body row to reach the header. On a sortable column, - Enter or Space cycles the sort exactly like a click would — ascending, + Enter or Space cycles the sort exactly like a click would: ascending, then descending, then back to unsorted. See Sorting for the full behavior, including multi-column sort.

      @@ -83,7 +83,7 @@ import PropsTable from '../../components/PropsTable.astro'; ;`} > - +

      Editing from the keyboard

      @@ -107,14 +107,14 @@ import PropsTable from '../../components/PropsTable.astro'; ;`} > - +

      Selection and expansion from the keyboard

      Selection checkboxes and expander buttons sit in the nav grid like any other cell. Arrow onto - a row's checkbox and press Space to select it — arrow up far enough and you reach the + a row's checkbox and press Space to select it. Arrow up far enough and you reach the header's select-all checkbox, which works the same way. Arrow onto an expander button and press Enter to expand or collapse that row.

      @@ -131,19 +131,19 @@ import PropsTable from '../../components/PropsTable.astro'; expandableRowsComponent={RowDetail} />;`} > - +

      The grid pattern

      Without cellNavigation, the table renders role="table" with plain - role="cell" cells — the right shape for a screen reader's native table-reading + role="cell" cells, the right shape for a screen reader's native table-reading commands, since nothing in the table is meant to hold focus. Turning on cellNavigation switches every region to the WAI-ARIA grid pattern: role="grid", role="gridcell" for body cells, and a single roving tabIndex={'{0}'} that follows the last-focused cell. This is why it's an opt-in - prop rather than always-on — it's the right choice for tables you expect people to operate, not a + prop rather than always-on. It's the right choice for tables you expect people to operate, not a strict upgrade over the default.

      @@ -155,7 +155,7 @@ import PropsTable from '../../components/PropsTable.astro';

      Limitations

      • Clamps at row/column edges rather than wrapping.
      • -
      • Doesn't cross page boundaries — arrowing past the last row does not auto-paginate.
      • +
      • Doesn't cross page boundaries: arrowing past the last row does not auto-paginate.
      • Key bindings are fixed. There's no prop to remap them.
      diff --git a/apps/docs/src/pages/docs/loading.astro b/apps/docs/src/pages/docs/loading.astro index d9ac4904..95a5183a 100644 --- a/apps/docs/src/pages/docs/loading.astro +++ b/apps/docs/src/pages/docs/loading.astro @@ -57,7 +57,7 @@ export default function UsersTable() { return ; }`} > - +

      Custom spinner or message

      diff --git a/apps/docs/src/pages/docs/localization.astro b/apps/docs/src/pages/docs/localization.astro index a1b19a79..eb4fb2f9 100644 --- a/apps/docs/src/pages/docs/localization.astro +++ b/apps/docs/src/pages/docs/localization.astro @@ -10,7 +10,7 @@ import PropsTable from '../../components/PropsTable.astro';

      Pass a localization object to DataTable to override every - string and aria-label rendered by the table — filter panel, pagination navigation, and + string and aria-label rendered by the table: filter panel, pagination navigation, and expand/collapse buttons. All keys are optional and fall back to English defaults.

      @@ -48,7 +48,7 @@ import { fr } from 'react-data-table-component/locales'; />

      - Each locale is a standalone module. Only the locale you import enters your bundle — the + Each locale is a standalone module. Only the locale you import enters your bundle. The rest are tree-shaken away.

      @@ -73,8 +73,8 @@ const myLocale: Localization = {

      Custom locale from scratch

      - Build a locale object from the Localization type. Every key is optional — - any omitted key falls back to the English default. + Build a locale object from the Localization type. Every key is optional, + and any omitted key falls back to the English default.

      Mobile

      - A data table is fundamentally a desktop pattern — comparing values across rows requires seeing + A data table is fundamentally a desktop pattern: comparing values across rows requires seeing multiple columns at once. On small screens the table stays a table, but several defaults are tuned to make that experience as comfortable as possible.

      @@ -44,7 +44,7 @@ const columns: TableColumn[] = [

      Use the hide property on a column to remove it below a breakpoint. - The column header and all its cells disappear — the remaining columns fill the available width. + The column header and all its cells disappear, and the remaining columns fill the available width.

      [] = [ @@ -115,8 +115,8 @@ function MyTable() {

      For apps where the primary use case on mobile is looking up a single record rather than - comparing rows, consider rendering a completely different component below your breakpoint — - a simple list or card feed — and only mounting DataTable on wider viewports. + comparing rows, consider rendering a completely different component below your breakpoint + (a simple list or card feed) and only mounting DataTable on wider viewports. Reusing the useMediaQuery hook from above:

      diff --git a/apps/docs/src/pages/docs/pagination.astro b/apps/docs/src/pages/docs/pagination.astro index 00348986..33a4883a 100644 --- a/apps/docs/src/pages/docs/pagination.astro +++ b/apps/docs/src/pages/docs/pagination.astro @@ -83,7 +83,7 @@ export default function App() { ); }`} > - +

      Pagination position

      @@ -102,7 +102,7 @@ export default function App() { paginationPosition="top" // "top" | "bottom" | "both" />;`} > - +

      Server-side pagination

      @@ -171,7 +171,7 @@ export default function App() { ); }`} > - +

      Callback contract

      @@ -212,7 +212,7 @@ export default function App() { - +

      Full example: search, sort, and pagination

      @@ -362,7 +362,7 @@ export default function App() { ); }`} > - +

      With React Query

      @@ -514,7 +514,7 @@ function MyPagination({ rowsPerPage, rowCount, currentPage, onChangePage }: Pagi

      Localization

      All navigation aria-labels on the built-in pagination bar are controlled via the - localization prop. Use a pre-built locale or supply your own — every key is + localization prop. Use a pre-built locale or supply your own. Every key is optional and falls back to the English default. Visible text such as rowsPerPageText and rangeSeparatorText is still set via paginationComponentOptions. @@ -526,7 +526,7 @@ import { fr } from 'react-data-table-component/locales'; ;`} /> - ;`} /> - - +

      Recommendations

      diff --git a/apps/docs/src/pages/docs/recipes.astro b/apps/docs/src/pages/docs/recipes.astro index 7bc3838a..ea22cb1b 100644 --- a/apps/docs/src/pages/docs/recipes.astro +++ b/apps/docs/src/pages/docs/recipes.astro @@ -1,83 +1,26 @@ --- import DocsLayout from '../../layouts/DocsLayout.astro'; - -const recipes = [ - { - href: '/docs/recipes/approval-workflow', - title: 'Approval workflow', - desc: 'Bulk-approve or reject requests. Toolbar buttons adapt to the selected rows\' status — already-resolved rows are excluded automatically.', - }, - { - href: '/docs/recipes/server-side', - title: 'Server-side sort, page & filter', - desc: 'Wire sorting, pagination, and column filters to your own API in a single useEffect.', - }, - { - href: '/docs/recipes/url-synced-state', - title: 'URL-synced table state', - desc: 'Sync sort, filters, and pagination to the URL — shareable links, reload-safe, back button works.', - }, - { - href: '/docs/recipes/dashboard-drilldown', - title: 'Dashboard with drill-down', - desc: 'Department overview with expandable rows, health-status row tinting, and inline budget-spend bars.', - }, - { - href: '/docs/recipes/audit-log', - title: 'Audit log viewer', - desc: 'Fixed header, severity row tinting, and multi-field live search — all wired together.', - }, - { - href: '/docs/recipes/persist-column-widths', - title: 'Persist column widths', - desc: 'Save and restore resizable column widths to localStorage or a back-end API.', - }, - { - href: '/docs/recipes/inline-row-actions', - title: 'Inline row actions', - desc: 'Per-row ⋮ menu with edit, duplicate, and delete — confirmation modals included.', - }, - { - href: '/docs/recipes/live-updates', - title: 'Live-updating data grid', - desc: 'Flash-on-change rows for a real-time price grid, using conditionalRowStyles and keyField.', - }, - { - href: '/docs/recipes/inventory-editor', - title: 'Editable inventory grid', - desc: 'Spreadsheet-style inline editing with a derived total column and a running grand total.', - }, - { - href: '/docs/recipes/row-groups', - title: 'Grouped rows with aggregates', - desc: 'Per-group totals and drill-down built from a plain array reduce — no groupBy API needed.', - }, - { - href: '/docs/recipes/csv-import', - title: 'CSV import', - desc: 'Load a user-uploaded CSV file into the table with FileReader and a small parser.', - }, -]; +import { recipes } from '../../data/nav'; ---

      Recipes

      - Working patterns for common product needs. Each recipe is self-contained — copy into your + Working patterns for common product needs. Each recipe is self-contained: copy into your project and adjust.

      - {recipes.map(({ href, title, desc }) => ( + {recipes.map(({ href, label, description }) => (
      - {title} → + {label} →
      -

      {desc}

      +

      {description}

      ))}
      diff --git a/apps/docs/src/pages/docs/recipes/approval-workflow.astro b/apps/docs/src/pages/docs/recipes/approval-workflow.astro index 8cc19ae8..91f97fba 100644 --- a/apps/docs/src/pages/docs/recipes/approval-workflow.astro +++ b/apps/docs/src/pages/docs/recipes/approval-workflow.astro @@ -6,25 +6,25 @@ import ApprovalWorkflowDemo from '../../../components/demos/ApprovalWorkflowDemo import approvalWorkflowSource from '../../../components/demos/ApprovalWorkflowDemo.tsx?raw'; --- - +

      Multi-step approval workflow

      Select rows from an approval queue and act on them in bulk. The toolbar buttons change based - on which rows are selected — already-resolved requests (approved or rejected) are + on which rows are selected. Already-resolved requests (approved or rejected) are automatically excluded from the action, so reviewers can't accidentally re-process them.

      - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      Disabling already-resolved rows

      diff --git a/apps/docs/src/pages/docs/recipes/audit-log.astro b/apps/docs/src/pages/docs/recipes/audit-log.astro index cc071df5..80932550 100644 --- a/apps/docs/src/pages/docs/recipes/audit-log.astro +++ b/apps/docs/src/pages/docs/recipes/audit-log.astro @@ -20,11 +20,11 @@ import auditLogSource from '../../../components/demos/AuditLogDemo.tsx?raw'; description="Search across user, action, resource, and IP, or filter by severity. The header stays fixed while the body scrolls." code={auditLogSource} > - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      Fixed header with capped scroll height

      @@ -57,7 +57,7 @@ import auditLogSource from '../../../components/demos/AuditLogDemo.tsx?raw';

      Filter client-side across several fields by combining a search string with a severity - toggle — no extra library needed: + toggle, no extra library needed:

      { diff --git a/apps/docs/src/pages/docs/recipes/csv-import.astro b/apps/docs/src/pages/docs/recipes/csv-import.astro index 5e6b1f84..832af27e 100644 --- a/apps/docs/src/pages/docs/recipes/csv-import.astro +++ b/apps/docs/src/pages/docs/recipes/csv-import.astro @@ -6,11 +6,11 @@ import CsvImportDemo from '../../../components/demos/CsvImportDemo.tsx'; import csvImportSource from '../../../components/demos/CsvImportDemo.tsx?raw'; --- - +

      CSV import

      - Since data is just an array, importing is not a table feature to configure — + Since data is just an array, importing is not a table feature to configure: it's reading a file, parsing it, and calling setData. Upload a file below, or load the sample:

      @@ -20,11 +20,11 @@ import csvImportSource from '../../../components/demos/CsvImportDemo.tsx?raw'; description="Upload a .csv file, or load the sample data to see it parsed into the table." code={csvImportSource} > - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      Read the file

      @@ -44,7 +44,7 @@ import csvImportSource from '../../../components/demos/CsvImportDemo.tsx?raw';

      Parse rows into your row type

      - A minimal parser is a few lines — split on newlines, split each line on commas, and zip + A minimal parser is a few lines: split on newlines, split each line on commas, and zip against the header row. It doesn't handle quoted commas; reach for a library like papaparse if your CSVs need that:

      @@ -61,7 +61,7 @@ import csvImportSource from '../../../components/demos/CsvImportDemo.tsx?raw';

      Pass it straight to data

      -

      No import prop, no adapter — the parsed array is the table's data:

      +

      No import prop, no adapter: the parsed array is the table's data:

      ([]); diff --git a/apps/docs/src/pages/docs/recipes/dashboard-drilldown.astro b/apps/docs/src/pages/docs/recipes/dashboard-drilldown.astro index 81580153..cfd8e7f1 100644 --- a/apps/docs/src/pages/docs/recipes/dashboard-drilldown.astro +++ b/apps/docs/src/pages/docs/recipes/dashboard-drilldown.astro @@ -13,7 +13,7 @@ import dashboardDrilldownSource from '../../../components/demos/DashboardDrilldo A department overview table that combines expandable rows, conditional row styles for health status, and custom cell renderers for inline progress bars. Expand any row to drill into a card grid of individual - team members — the detail panel is a plain React component, not another table. + team members. The detail panel is a plain React component, not another table.

      - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      Conditional row styles

      - Tint rows based on a derived health field so problems are visible at a glance — no extra + Tint rows based on a derived health field so problems are visible at a glance, no extra column needed:

      @@ -43,7 +43,7 @@ import dashboardDrilldownSource from '../../../components/demos/DashboardDrilldo

      A card layout in the expander, not a nested table

      - expandableRowsComponent can render anything — it doesn't have to be another + expandableRowsComponent can render anything. It doesn't have to be another DataTable. A grid of team-member cards reads better here than a table-in-a-table. The parent row arrives as data:

      @@ -56,7 +56,7 @@ import dashboardDrilldownSource from '../../../components/demos/DashboardDrilldo
      {initials(member.name)}
      - {member.name} — {member.role} + {member.name}: {member.role}
      @@ -78,7 +78,7 @@ import dashboardDrilldownSource from '../../../components/demos/DashboardDrilldo

      expandableRowExpanded re-syncs whenever its result changes, so a single boolean - in state — flipped by the "Expand all" button above the demo — expands or collapses every + in state, flipped by the "Expand all" button above the demo, expands or collapses every department at once:

      diff --git a/apps/docs/src/pages/docs/recipes/inline-row-actions.astro b/apps/docs/src/pages/docs/recipes/inline-row-actions.astro index 4986ae00..a2fe7bf0 100644 --- a/apps/docs/src/pages/docs/recipes/inline-row-actions.astro +++ b/apps/docs/src/pages/docs/recipes/inline-row-actions.astro @@ -6,12 +6,12 @@ import InlineRowActionsDemo from '../../../components/demos/InlineRowActionsDemo import inlineRowActionsSource from '../../../components/demos/InlineRowActionsDemo.tsx?raw'; --- - +

      Inline row actions

      A per-row ⋮ menu with edit, duplicate, and delete. Destructive actions - show a confirmation modal before committing. State is updated optimistically — no network + show a confirmation modal before committing. State is updated optimistically, no network call needed to see the result.

      @@ -20,11 +20,11 @@ import inlineRowActionsSource from '../../../components/demos/InlineRowActionsDe description="Click the ⋮ menu on any row to edit, duplicate, or delete it." code={inlineRowActionsSource} > - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      The actions column

      diff --git a/apps/docs/src/pages/docs/recipes/inventory-editor.astro b/apps/docs/src/pages/docs/recipes/inventory-editor.astro index b29f52fb..34d02ad4 100644 --- a/apps/docs/src/pages/docs/recipes/inventory-editor.astro +++ b/apps/docs/src/pages/docs/recipes/inventory-editor.astro @@ -6,7 +6,7 @@ import EditableGridDemo from '../../../components/demos/EditableGridDemo.tsx'; import editableGridSource from '../../../components/demos/EditableGridDemo.tsx?raw'; --- - +

      Editable inventory grid

      @@ -20,11 +20,11 @@ import editableGridSource from '../../../components/demos/EditableGridDemo.tsx?r description="Edit product, quantity, or price directly in the cells. Total and grand total update automatically." code={editableGridSource} > - +

      - This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

      Editable columns with a shared handler

      @@ -55,7 +55,7 @@ const columns: TableColumn[] = [

      A derived column that isn't editable

      - The total column has no editable flag — it's computed straight + The total column has no editable flag. It's computed straight from the row on every render, so it never drifts out of sync with quantity or price:

      @@ -70,8 +70,8 @@ const columns: TableColumn[] = [

      Running grand total

      - Reduce over data on every render and show it in a footer bar below the table — - it updates automatically as cells are edited, with no extra event wiring: + Reduce over data on every render and show it in a footer bar below the table. + It updates automatically as cells are edited, with no extra event wiring:

      sum + r.quantity * r.price, 0); @@ -82,7 +82,7 @@ const columns: TableColumn[] = [
`} />

- Prefer it inside the table itself? Pass a footer to a column instead — see + Prefer it inside the table itself? Pass a footer to a column instead. See Footer for the built-in summary row.

diff --git a/apps/docs/src/pages/docs/recipes/live-updates.astro b/apps/docs/src/pages/docs/recipes/live-updates.astro index 4dc75a1b..49031a9e 100644 --- a/apps/docs/src/pages/docs/recipes/live-updates.astro +++ b/apps/docs/src/pages/docs/recipes/live-updates.astro @@ -6,11 +6,11 @@ import LiveUpdatesDemo from '../../../components/demos/LiveUpdatesDemo.tsx'; import liveUpdatesSource from '../../../components/demos/LiveUpdatesDemo.tsx?raw'; --- - +

Live-updating data grid

- A price ticker where rows briefly highlight when their value changes — the same pattern behind + A price ticker where rows briefly highlight when their value changes, the same pattern behind trading blotters and live ops dashboards. It's just three ingredients you already have: keyField to identify rows across updates, a small piece of state tracking which rows just changed, and conditionalRowStyles to flash them. @@ -21,11 +21,11 @@ import liveUpdatesSource from '../../../components/demos/LiveUpdatesDemo.tsx?raw description="Prices tick every ~1.2s. Rows flash briefly when their price changes." code={liveUpdatesSource} > - +

- This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

Match rows across updates with keyField

@@ -59,7 +59,7 @@ function updatePrice(symbol: string, next: number) {

Flash the row with conditionalRowStyles

- No animation library needed — a CSS transition on the inline style fades the + No animation library needed: a CSS transition on the inline style fades the highlight out on its own:

diff --git a/apps/docs/src/pages/docs/recipes/persist-column-widths.astro b/apps/docs/src/pages/docs/recipes/persist-column-widths.astro index 3a32489c..ca5cb1a2 100644 --- a/apps/docs/src/pages/docs/recipes/persist-column-widths.astro +++ b/apps/docs/src/pages/docs/recipes/persist-column-widths.astro @@ -21,11 +21,11 @@ import persistColumnWidthsSource from '../../../components/demos/PersistColumnWi description="Drag a column edge to resize. Widths are saved to localStorage and restored on reload." code={persistColumnWidthsSource} > - +

- This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

localStorage

diff --git a/apps/docs/src/pages/docs/recipes/row-groups.astro b/apps/docs/src/pages/docs/recipes/row-groups.astro index e90ee083..a6800526 100644 --- a/apps/docs/src/pages/docs/recipes/row-groups.astro +++ b/apps/docs/src/pages/docs/recipes/row-groups.astro @@ -6,7 +6,7 @@ import RowGroupingDemo from '../../../components/demos/RowGroupingDemo.tsx'; import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw'; --- - +

Grouped rows with aggregates

@@ -21,17 +21,17 @@ import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw description="Expand a region to see its individual orders. The Total column sums per-group and grand-totals in the footer." code={rowGroupingSource} > - +

- This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

Group the flat data yourself

- No groupBy prop to learn — reduce your flat rows into per-group summary objects + No groupBy prop to learn. Reduce your flat rows into per-group summary objects before they ever reach the table:

@@ -49,7 +49,7 @@ import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw

Each summary row already carries its own orders, so the expander component needs no extra - data fetching. It doesn't need to be a DataTable either — a simple line-item + data fetching. It doesn't need to be a DataTable either: a simple line-item list reads more clearly than a table nested inside a table:

@@ -77,7 +77,7 @@ import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw

Expand all / collapse all

- Same pattern as any other expandable table — one boolean in state, flipped by the + Same pattern as any other expandable table: one boolean in state, flipped by the button above the demo, expands or collapses every region at once:

@@ -96,7 +96,7 @@ import rowGroupingSource from '../../../components/demos/RowGroupingDemo.tsx?raw

The same footer function that produces a table-wide total also works on grouped - data — it receives whatever rows are currently visible: + data, since it receives whatever rows are currently visible:

Enable sortServer, paginationServer, and filterServer, then pass controlled filterValues. Refetch from a single effect that depends on every state knob. - The demo below simulates a 400ms API delay — filter by name or department to see it in action. + The demo below simulates a 400ms API delay. Filter by name or department to see it in action.

- +

- This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

- Try sorting a column, typing in a filter, or changing the page — watch the URL update live. + Try sorting a column, typing in a filter, or changing the page, and watch the URL update live. Copy the URL and open it in a new tab.

- +

- This demo uses Tailwind utility classes for layout — swap in your own CSS when copying into your project. + This demo uses Tailwind utility classes for layout. Swap in your own CSS when copying into your project.

Reading initial state from the URL

@@ -84,7 +84,7 @@ const [filters, setFilters] = useState(/* map init.filters to FilterState */);`}

Pass the state as controlled props. Use sortServer to prevent the table from - re-sorting internally — the URL is the source of truth, so the component controls sort: + re-sorting internally. The URL is the source of truth, so the component controls sort:

[] = [ ;`} > - +

How it works

diff --git a/apps/docs/src/pages/docs/row-interactions.astro b/apps/docs/src/pages/docs/row-interactions.astro index 365f2e54..fec6b00a 100644 --- a/apps/docs/src/pages/docs/row-interactions.astro +++ b/apps/docs/src/pages/docs/row-interactions.astro @@ -42,14 +42,14 @@ export default function App() { setSelected(row)} /> {selected && (
- {selected.name} — {selected.role}, {selected.department} + {selected.name}: {selected.role}, {selected.department}
)}
); }`} > - +

Rows as navigation links

@@ -116,7 +116,7 @@ export default function App() { ); }`} > - +

Action buttons in cells

@@ -161,7 +161,7 @@ export default function App() { ); }`} > - +

Prop reference

diff --git a/apps/docs/src/pages/docs/row-pinning.astro b/apps/docs/src/pages/docs/row-pinning.astro index 9606f8c6..99b30bef 100644 --- a/apps/docs/src/pages/docs/row-pinning.astro +++ b/apps/docs/src/pages/docs/row-pinning.astro @@ -22,7 +22,7 @@ import RowPinningDemo from '../../components/demos/RowPinningDemo.tsx';
Coming in a future release: native pinnedRows / pinnedRowIds props - are on the roadmap. The patterns below are the recommended workaround until then — they compose + are on the roadmap. The patterns below are the recommended workaround until then. They compose cleanly with all existing features and are easy to swap out for the native API when it lands.
@@ -94,7 +94,7 @@ export default function App() { return ; }`} > - +

How it works

@@ -105,7 +105,7 @@ export default function App() {

Because sortFunction is called on every sort change, the pinned set just needs to - live in React state — update the set and the rows re-order instantly without remounting the table. + live in React state. Update the set and the rows re-order instantly without remounting the table.

Static pinned rows (hardcoded)

@@ -133,7 +133,7 @@ const sortFunction: SortFunction = (rows, selector, direction) => {

Pinning by a condition (not an ID)

- You can pin based on any row property — not just an ID. For example, always show active + You can pin based on any row property, not just an ID. For example, always show active employees before terminated ones, or float rows flagged as urgent:

@@ -196,7 +196,7 @@ const sortFunction: SortFunction = (rows, selector, direction) => {

Visual treatment

- Pinned rows don't receive any special styling by default — add your own via + Pinned rows don't receive any special styling by default. Add your own via conditionalRowStyles to make them visually distinct:

@@ -218,8 +218,8 @@ const sortFunction: SortFunction = (rows, selector, direction) => {

Limitations

    -
  • This is a sort-layer workaround — rows are reordered by the sort function, not truly fixed in the DOM. If you use sortServer, you must apply pin order yourself after the fetch (see above).
  • -
  • Pinned rows participate in pagination. If a pinned row is on page 2, it won't appear on page 1 — pin logic only affects order within the current data array. For cross-page pinning, pre-sort your data array before passing it to data.
  • +
  • This is a sort-layer workaround: rows are reordered by the sort function, not truly fixed in the DOM. If you use sortServer, you must apply pin order yourself after the fetch (see above).
  • +
  • Pinned rows participate in pagination. If a pinned row is on page 2, it won't appear on page 1: pin logic only affects order within the current data array. For cross-page pinning, pre-sort your data array before passing it to data.
  • Native pinnedRows support (sticky DOM positioning, cross-page pinning, built-in styling) is planned for a future release.
diff --git a/apps/docs/src/pages/docs/rtl.astro b/apps/docs/src/pages/docs/rtl.astro index 5ab7df40..4badff98 100644 --- a/apps/docs/src/pages/docs/rtl.astro +++ b/apps/docs/src/pages/docs/rtl.astro @@ -22,7 +22,7 @@ import PropsTable from '../../components/PropsTable.astro'; ;`} > - +

Direction values

@@ -72,7 +72,7 @@ import PropsTable from '../../components/PropsTable.astro';

Pairing with localization

direction controls layout; localization controls strings. For a - fully translated RTL table, use both together. The library keeps them separate on purpose — + fully translated RTL table, use both together. The library keeps them separate on purpose: you may want French strings without changing layout direction, or an Arabic locale on a mixed-direction page where you manage dir yourself.

diff --git a/apps/docs/src/pages/docs/selection.astro b/apps/docs/src/pages/docs/selection.astro index 89933eea..68a960ca 100644 --- a/apps/docs/src/pages/docs/selection.astro +++ b/apps/docs/src/pages/docs/selection.astro @@ -99,7 +99,7 @@ export default function App() { ); }`} > - +

Range selection (Shift-click)

@@ -109,7 +109,7 @@ export default function App() { selection is enabled by default; opt out with selectableRowsRange={false}.

- The behavior respects selectableRowDisabled — disabled rows in the range are + The behavior respects selectableRowDisabled: disabled rows in the range are skipped, not toggled. It also stays within the current page when pagination is enabled.

); }`} > - +

@@ -216,7 +216,7 @@ ref.current?.clearSelectedRows();`} />

It also changes what happens when the page changes: the selection is cleared. That keeps - "select all" honest — it always refers to the rows in front of you — but it means a + "select all" honest (it always refers to the rows in front of you), but it means a selection cannot be accumulated across pages. Leave the prop off if you need that.

;`} /> @@ -238,7 +238,7 @@ ref.current?.clearSelectedRows();`} /> selectableRowsVisibleOnly />`} > - +

onSelectedRowsChange

@@ -261,8 +261,8 @@ tableRef.current?.clearSelectedRows();`} />

Custom selection toolbar

v8 removed the built-in contextMessage and contextActions props. - Use onSelectedRowsChange to drive your own toolbar rendered outside the table — - you get full control over layout, copy, and actions. See the + Use onSelectedRowsChange to drive your own toolbar rendered outside the table. + You get full control over layout, copy, and actions. See the bulk-action toolbar recipe for a complete example.

@@ -292,7 +292,7 @@ tableRef.current?.clearSelectedRows();`} />

Your component receives checked, disabled, name, onClick, and a ref. Forward that ref to a real - <input type="checkbox"> — DataTable sets .indeterminate on the + <input type="checkbox">. DataTable sets .indeterminate on the node directly for the partial-selection state on the header checkbox. Keep the input in the DOM and style a sibling element off it, rather than replacing it, so keyboard and screen reader behavior still work. @@ -329,10 +329,10 @@ tableRef.current?.clearSelectedRows();`} /> .vinyl-wrap input:checked ~ .vinyl-disc { transform: rotate(180deg); } .vinyl-wrap input:indeterminate ~ .vinyl-disc { box-shadow: 0 0 0 2px #f59e0b; }`} > - +

- A prop value can also be a function — it is called with the checkbox's indeterminate state + A prop value can also be a function. It is called with the checkbox's indeterminate state (true for the header checkbox when only some rows are selected) and its return value is passed to the component instead. Useful for components that express the indeterminate state through a different prop: diff --git a/apps/docs/src/pages/docs/sorting.astro b/apps/docs/src/pages/docs/sorting.astro index 17b092e2..b0fb45a5 100644 --- a/apps/docs/src/pages/docs/sorting.astro +++ b/apps/docs/src/pages/docs/sorting.astro @@ -21,7 +21,7 @@ import MultiSortDemo from '../../components/demos/MultiSortDemo.tsx'; Columns without sortable are not interactive and are excluded from the tab order.

- Enable sortMulti to let users sort by several columns at once — hold + Enable sortMulti to let users sort by several columns at once: hold Ctrl (or ⌘ on macOS) while clicking additional headers. See Multi-column sorting below.

@@ -55,7 +55,7 @@ const columns: TableColumn[] = [ highlightOnHover />;`} > - +

Removable sorting

@@ -64,15 +64,15 @@ const columns: TableColumn[] = [ user can clear a sort directly from the header instead of resetting the whole table:

    -
  1. First click — sort ascending (or the direction set by defaultSortAsc).
  2. -
  3. Second click — sort descending.
  4. -
  5. Third click — remove the sort.
  6. +
  7. First click: sort ascending (or the direction set by defaultSortAsc).
  8. +
  9. Second click: sort descending.
  10. +
  11. Third click: remove the sort.

When the sort is removed, onSort fires with an empty column ({`{}`}), the default direction, and an empty sortColumns array. The table returns to its original (unsorted) row order. This is also the signal a server-side handler should use to drop - its ORDER BY clause — see Server-side sorting. + its ORDER BY clause. See Server-side sorting.

Multi-column sorting

@@ -116,7 +116,7 @@ export default function App() { ); }`} > - +

@@ -139,7 +139,7 @@ export default function App() {

onSort fires on every sort change, whether triggered by clicking a header or by a defaultSortFieldId change. It receives four arguments: the primary sorted column, - its direction, the fully sorted row array, and sortColumns — the complete sort + its direction, the fully sorted row array, and sortColumns, the complete sort configuration in priority order.

@@ -250,7 +250,7 @@ const withSecondarySalary: SortFunction = (rows, selector, direction) return primary !== 0 ? primary : b.salary - a.salary; // salary desc as tiebreaker });`} > - +

Sorting null / empty values last

@@ -308,14 +308,14 @@ const SortIcon = () => ( to your query.

- The removable and multi-column behaviors apply to server-side sorting too — the table sends the + The removable and multi-column behaviors apply to server-side sorting too: the table sends the intent, your handler turns it into a query. Two cases to handle:

  • Cleared sort: the three-state cycle means a header can now resolve to no sort. When that happens onSort fires with an empty - sortColumns array and an empty primary column — drop your ORDER BY and + sortColumns array and an empty primary column. Drop your ORDER BY and fetch the default order.
  • @@ -387,7 +387,7 @@ export default function App() { ); }`} > - +

    Combined: server-side sorting and pagination

    @@ -461,7 +461,7 @@ export default function App() { ); }`} > - +

    Resetting sort programmatically

    @@ -489,7 +489,7 @@ function App() { ); }`} > - +

    Prop reference

    diff --git a/apps/docs/src/pages/docs/ssr.astro b/apps/docs/src/pages/docs/ssr.astro index de42cc15..1c3b95d5 100644 --- a/apps/docs/src/pages/docs/ssr.astro +++ b/apps/docs/src/pages/docs/ssr.astro @@ -7,7 +7,7 @@ import CodeBlock from '../../components/CodeBlock.astro';

    Server-Side Rendering

    - react-data-table-component is a client-only component — it reads the DOM for + react-data-table-component is a client-only component: it reads the DOM for measurements, uses useLayoutEffect internally, and relies on browser APIs for features like column resizing and shift-click selection. It renders nothing meaningful on the server and hydrates fully in the browser. @@ -21,7 +21,7 @@ import CodeBlock from '../../components/CodeBlock.astro';

      -
    • No layout shift caused by row measurements — the table is absent until hydrated
    • +
    • No layout shift caused by row measurements, since the table is absent until hydrated
    • No useLayoutEffect warning when rendered server-side
    • Styles may not be present in the initial HTML unless you import the CSS explicitly (see below)
    @@ -33,7 +33,7 @@ import CodeBlock from '../../components/CodeBlock.astro'; <DataTable> directly from any Server Component file without wrapping it.

    - - + `} lang="astro" />

    Dynamic import (escape hatch)

    @@ -156,7 +156,7 @@ import dynamic from 'next/dynamic'; const DataTable = dynamic(() => import('react-data-table-component'), { ssr: false });`} /> - import('react-data-table-component')); diff --git a/apps/docs/src/pages/docs/themes.astro b/apps/docs/src/pages/docs/themes.astro index 99e9f4b8..d4b4841b 100644 --- a/apps/docs/src/pages/docs/themes.astro +++ b/apps/docs/src/pages/docs/themes.astro @@ -30,7 +30,7 @@ import PropsTable from '../../components/PropsTable.astro'; `} > - +

    Structural themes

    @@ -98,7 +98,7 @@ createTheme('material-violet', { primary: '#6200EE' }, 'material'); - // Follow OS / page toggle (default, explicit) @@ -114,10 +114,10 @@ createTheme('material-violet', { primary: '#6200EE' }, 'material'); ;`} lang="ts" /> @@ -152,7 +152,7 @@ createTheme('violet', { disabled: '#d1c4e9', }, context: { background: '#6200EE', text: '#ffffff' }, - // Dark-mode overrides — only what changes + // Dark-mode overrides, only what changes darkMode: { primary: '#BB86FC', text: { primary: '#e0e0e0', secondary: '#b0b0b0' }, @@ -180,7 +180,7 @@ createTheme('violet', { description="Switch between the built-in icons and custom inline-SVG replacements for pagination, expander, and sort." code={iconsDemoSource} > - +

    Override via props

    @@ -191,14 +191,14 @@ createTheme('violet', { } columns={columns} data={data} /> -// Expander icons — collapsed / expanded state +// Expander icons: collapsed / expanded state -// Pagination icons — any subset; omitted keys fall back to defaults +// Pagination icons: any subset; omitted keys fall back to defaults , expandable: { collapsed: , - // expanded: omitted — falls back to the built-in default + // expanded: omitted, falls back to the built-in default }, pagination: { next: , previous: , - // first/last: omitted — fall back to defaults + // first/last: omitted, fall back to defaults }, }, }); @@ -310,7 +310,7 @@ MyCheckbox.displayName = 'MyCheckbox'; // Remove the default header separator `} > - +

    CSS variable override

    diff --git a/apps/docs/src/pages/docs/typescript.astro b/apps/docs/src/pages/docs/typescript.astro index 6ca1b94f..b03ddff3 100644 --- a/apps/docs/src/pages/docs/typescript.astro +++ b/apps/docs/src/pages/docs/typescript.astro @@ -3,11 +3,11 @@ import DocsLayout from '../../layouts/DocsLayout.astro'; import CodeBlock from '../../components/CodeBlock.astro'; --- - +

    TypeScript

    - Every public API is fully typed. In most cases you don't need to import any types at all — + Every public API is fully typed. In most cases you don't need to import any types at all: column shapes and event handlers infer from the row type you pass to DataTable. This page covers the patterns where explicit typing is useful.

    @@ -41,7 +41,7 @@ const columns: TableColumn[] = [

    If you ever see any appearing in your editor, double-check that the columns - array has an explicit TableColumn<Employee>[] annotation — TypeScript + array has an explicit TableColumn<Employee>[] annotation, because TypeScript cannot infer the row type from data alone when columns are declared separately.

    @@ -100,7 +100,7 @@ const conditionalRowStyles: ConditionalStyles[] = [ function EmployeeDetail({ data: row }: ExpanderComponentProps) { return (
    - {row.name} — ID {row.id} + {row.name} (ID {row.id})
    ); } @@ -129,8 +129,8 @@ const [filters, setFilters] = useState>({});

    Headless hooks

    - Each hook re-exports the types it accepts and returns. Most users won't need to import them - — destructured returns are fully typed. If you wrap a hook in another, import the option + Each hook re-exports the types it accepts and returns. Most users won't need to import them, + since destructured returns are fully typed. If you wrap a hook in another, import the option types:

    @@ -160,7 +160,7 @@ import type { UseColumnVisibilityResult } from 'react-data-table-component';`} / object will fail. Stringify or wrap in JSX:

    - row.tags } // row.tags: Tag[] // ✅ Either format or render @@ -178,7 +178,7 @@ import type { UseColumnVisibilityResult } from 'react-data-table-component';`} / setData(prev => prev.map(r => (r.id === row.id ? { ...r, [field]: value } : r))); };`} /> -

    Imported types — full list

    +

    Imported types: full list

    A fast, accessible React data table component with sorting, pagination, row selection, expandable rows, column filtering, and theming built in. Zero runtime dependencies, Apache-2.0 licensed, free with no paid tier. A working table takes about 10 lines of code. + +Install: \`npm install react-data-table-component\` + +Minimal usage: + +\`\`\`tsx +import DataTable from 'react-data-table-component'; + +const columns = [ + { name: 'Name', selector: row => row.name, sortable: true }, + { name: 'Role', selector: row => row.role }, +]; + + +\`\`\``; + +const optional = `## Optional + +- [Support & Sponsorship](https://reactdatatable.com/support): sponsor tiers, direct consulting and enterprise support +- [GitHub repository](https://github.com/jbetancur/react-data-table-component) +- [npm package](https://www.npmjs.com/package/react-data-table-component)`; + +export const GET: APIRoute = ({ site }) => { + const sections = nav.map(({ group, links }) => { + const lines = links + .filter(({ href }) => href.startsWith('/docs/')) + .map(({ label, href, description }) => { + if (!description) throw new Error(`nav.ts: ${href} needs a description for llms.txt`); + return `- [${label}](${new URL(href, site)}): ${description}`; + }); + return `## ${group}\n\n${lines.join('\n')}`; + }); + + return new Response([header, ...sections, optional].join('\n\n') + '\n', { + headers: { 'Content-Type': 'text/plain; charset=utf-8' }, + }); +}; diff --git a/src/__tests__/docsCatalog.test.ts b/src/__tests__/docsCatalog.test.ts new file mode 100644 index 00000000..da6d6b8b --- /dev/null +++ b/src/__tests__/docsCatalog.test.ts @@ -0,0 +1,47 @@ +// @vitest-environment node +import ts from 'typescript'; +import { columnProps, dataTableProps } from '../../apps/docs/src/data/props'; + +const typesFile = decodeURIComponent(new URL('../types.ts', import.meta.url).pathname); +const program = ts.createProgram([typesFile], { strict: true, jsx: ts.JsxEmit.React, skipLibCheck: true }); +const checker = program.getTypeChecker(); + +function propertyNames(typeName: string): string[] { + const decl = program + .getSourceFile(typesFile) + ?.statements.find( + (s): s is ts.TypeAliasDeclaration | ts.InterfaceDeclaration => + (ts.isTypeAliasDeclaration(s) || ts.isInterfaceDeclaration(s)) && s.name.text === typeName, + ); + if (!decl) { + throw new Error(`${typeName} not found in types.ts`); + } + const type = checker.getTypeAtLocation(decl.name); + // TableProps is an intersection containing unions; collect keys from every member. + const members = type.isUnion() ? type.types : [type]; + return [...new Set(members.flatMap(t => t.getProperties().map(p => p.name)))].sort(); +} + +const diff = (a: string[], b: string[]) => a.filter(x => !b.includes(x)).sort(); + +describe('docs prop catalog matches the types', () => { + const tableKeys = propertyNames('TableProps'); + const columnKeys = propertyNames('TableColumn'); + + test('every DataTable prop is documented, and nothing extra', () => { + const documented = dataTableProps.filter(p => !p.on).map(p => p.name); + expect(diff(tableKeys, documented)).toEqual([]); + expect(diff(documented, tableKeys)).toEqual([]); + }); + + test('every TableColumn field is documented, and nothing extra', () => { + const documented = columnProps.map(p => p.name); + expect(diff(columnKeys, documented)).toEqual([]); + expect(diff(documented, columnKeys)).toEqual([]); + }); + + test('column.* entries on feature pages name real TableColumn fields', () => { + const featureEntries = dataTableProps.filter(p => p.on === 'column').map(p => p.name.replace(/^column\./, '')); + expect(diff(featureEntries, columnKeys)).toEqual([]); + }); +}); diff --git a/vitest.config.mts b/vitest.config.mts index 7d9b013b..4f5a3de6 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,6 +1,9 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ + // Use one tsconfig for every file. Otherwise Vite picks the nearest one, and docsCatalog.test.ts + // imports from apps/docs, whose tsconfig extends a preset from the docs' own (uninstalled in CI) deps. + tsconfig: './tsconfig.test.json', test: { environment: 'jsdom', globals: true,