Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<PropsTable>`), `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.

---

Expand Down
37 changes: 36 additions & 1 deletion apps/docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
93 changes: 0 additions & 93 deletions apps/docs/public/llms.txt

This file was deleted.

6 changes: 3 additions & 3 deletions apps/docs/src/components/Demo.astro
Original file line number Diff line number Diff line change
@@ -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:
* <Demo code={`import DataTable from '...'`} title="Basic example">
* <MyDemoIsland client:load />
* <MyDemoIsland client:visible={{ rootMargin: '200px' }} />
* </Demo>
*
* The <Code> component uses Astro's built-in Shiki highlighter — zero config.
* The <Code> component uses Astro's built-in Shiki highlighter, zero config.
*/
import { Code } from 'astro:components';
import type { ComponentProps } from 'astro/types';
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/AnimationsDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default function AnimationsDemo() {
Replay entrance animation
</button>
<span className="text-gray-400 text-xs ml-auto">
{animate ? 'Animations on — sort to see reorder animation' : 'Animations off'}
{animate ? 'Animations on: sort to see reorder animation' : 'Animations off'}
</span>
</div>
<DataTable key={mountKey} columns={columns} data={BASE_DATA} animateRows={animate} highlightOnHover />
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export default function ApprovalWorkflowDemo() {

return (
<div className="relative space-y-2">
{/* Bulk toolbar — actions change based on what's selected */}
{/* Bulk toolbar: actions change based on what's selected */}
{selected.length > 0 && (
<div className="flex items-center gap-3 px-4 py-2.5 bg-brand-50 border border-brand-100 rounded-lg text-sm">
<span className="font-medium text-brand-700">
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/components/demos/ContextMenuDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Row>[] = [
{ id: 'name', name: 'Name', selector: row => row.name, sortable: true, width: '180px', filterable: true },
Expand All @@ -87,7 +87,7 @@ const columns: TableColumn<Row>[] = [
];

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 (
<div>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/ControlledSelectionDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default function ControlledSelectionDemo() {
<div className="mt-1 font-mono text-xs text-brand-600 min-h-[1.25rem]">
{selected.length > 0 ? `[${selected.map(r => r.id).join(', ')}]` : '[]'}
{selected.length > 0 && (
<span className="text-gray-400"> — {selected.map(r => r.name).join(', ')}</span>
<span className="text-gray-400">: {selected.map(r => r.name).join(', ')}</span>
)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/HeadlessDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export default function HeadlessDemo() {
return (
<div style={{ fontFamily: 'system-ui, sans-serif', fontSize: 13 }}>
<p style={{ marginBottom: 8, fontSize: 11, color: '#6b7280', fontStyle: 'italic' }}>
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.
</p>

{/* Filter inputs */}
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/components/demos/IconsDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => (
<svg
width="14"
Expand Down Expand Up @@ -217,7 +217,7 @@ const modes: { key: Mode; label: string }[] = [

const ExpandedRow = ({ data: row }: { data: Employee }) => (
<div style={{ padding: '12px 24px', background: '#f9fafb', fontSize: 13, color: '#4b5563' }}>
<strong>{row.name}</strong> — {row.bio}
<strong>{row.name}</strong>: {row.bio}
</div>
);

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function KeyboardArrowNavDemo() {
<p className="text-xs text-gray-400">
A plain read-only table. Click a cell, then use <kbd>←</kbd> <kbd>→</kbd> <kbd>↑</kbd> <kbd>↓</kbd> to move,{' '}
<kbd>Home</kbd>/<kbd>End</kbd> to jump to the row edges, and <kbd>Ctrl</kbd>+<kbd>Home</kbd>/<kbd>Ctrl</kbd>+
<kbd>End</kbd> to jump to the grid corners. No editing, sorting, selection, or expansion here — just movement,
<kbd>End</kbd> to jump to the grid corners. No editing, sorting, selection, or expansion here, just movement,
which works on any table.
</p>
<DataTable columns={columns} data={data} highlightOnHover cellNavigation />
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/KeyboardEditDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default function KeyboardEditDemo() {
<div className="space-y-2">
<p className="text-xs text-gray-400">
Arrow to Name or Salary, then press <kbd>Enter</kbd> or <kbd>F2</kbd> to open the editor. <kbd>Enter</kbd>{' '}
commits and <kbd>Escape</kbd> cancels — either way, focus returns to the cell so arrow-key navigation continues
commits and <kbd>Escape</kbd> cancels. Either way, focus returns to the cell so arrow-key navigation continues
immediately. Department has no editor, so Enter/F2 do nothing there.
</p>
<DataTable columns={columns} data={data} highlightOnHover cellNavigation />
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/src/components/demos/LoadingDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ export default function LoadingDemo() {
<span className="text-xs text-gray-400 italic">
{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'}
</span>
)}
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/MobileDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export default function MobileDemo() {

{isNarrow && (
<p className="text-xs text-gray-400 text-center">
Department and Salary are hidden at this width — use <code className="bg-gray-100 px-1 rounded">omit</code> or{' '}
Department and Salary are hidden at this width. Use <code className="bg-gray-100 px-1 rounded">omit</code> or{' '}
<code className="bg-gray-100 px-1 rounded">hide: &quot;sm&quot;</code> to drop columns on small screens.
</p>
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/MultiSortDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/PersistColumnWidthsDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function PersistColumnWidthsDemo() {
return (
<div className="space-y-3">
<div className="flex items-center gap-3 text-sm text-gray-500">
<span className="text-xs">Drag column edges to resize — widths persist across sessions.</span>
<span className="text-xs">Drag column edges to resize. Widths persist across sessions.</span>
<button
onClick={handleReset}
className="ml-auto px-2.5 py-1 text-xs border border-gray-200 rounded-md hover:border-gray-300 text-gray-500"
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/src/components/demos/RowContextMenuDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ const columns: TableColumn<Task>[] = [

export default function RowContextMenuDemo(): JSX.Element {
const [rows, setRows] = useState<Task[]>(initialData);
const [lastAction, setLastAction] = useState('none yet — open the ⋮ menu on any row');
const [lastAction, setLastAction] = useState('none yet (open the ⋮ menu on any row)');
const [menuPosition, setMenuPosition] = useState<'start' | 'end'>('end');

// Row menu items are a function of the row, so they can vary per row —
// Row menu items are a function of the row, so they can vary per row:
// here "Mark done" disables itself once a task is already Done.
const rowActions = (row: Task): ContextMenuAction[] => [
{ id: 'mark-done', label: 'Mark done', disabled: row.status === 'Done' },
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/RowGroupingDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const ORDERS: Order[] = [
{ id: 9, region: 'Central', customer: 'Ironclad Freight', amount: 3050 },
];

// Group flat rows into per-region summary rows — a plain reduce, no groupBy API required.
// Group flat rows into per-region summary rows: a plain reduce, no groupBy API required.
const groups: RegionGroup[] = Object.values(
ORDERS.reduce<Record<string, RegionGroup>>((acc, order) => {
const g = (acc[order.region] ??= { region: order.region, count: 0, total: 0, orders: [] });
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/RowInteractionsDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function RowClickDemo() {
fontSize: 13,
}}
>
<strong>{selected.name}</strong> — {selected.role}, {selected.department}
<strong>{selected.name}</strong>: {selected.role}, {selected.department}
<span style={{ marginLeft: 12, color: '#0369a1' }}>{selected.email}</span>
</div>
)}
Expand Down
Loading
Loading