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
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ The docs live in `apps/docs/src/pages/docs/`. Each feature has its own `.astro`
- Add new pages to the sidebar in `apps/docs/src/layouts/DocsLayout.astro`.
- If removing a page, add a `[[redirects]]` entry in `netlify.toml` pointing the old URL to its replacement.

**Keeping `apps/docs/public/llms.txt` in sync:**

- 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.

---

## Updating CHANGELOG.md
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const columns = [
- [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
Expand Down Expand Up @@ -73,6 +74,10 @@ const columns = [
- [Approval workflow with bulk actions](https://reactdatatable.com/docs/recipes/bulk-action-toolbar)
- [URL-synced table state](https://reactdatatable.com/docs/recipes/editable-grid)
- [Dashboard drill-down](https://reactdatatable.com/docs/recipes/master-detail)
- [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/sticky-footer)
- [Persist column widths](https://reactdatatable.com/docs/recipes/persist-column-widths)
- [Inline row actions](https://reactdatatable.com/docs/recipes/row-grouping)
Expand Down
79 changes: 79 additions & 0 deletions apps/docs/src/components/demos/CsvImportDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState } from 'react';
import DataTable, { type TableColumn } from 'react-data-table-component';

interface Contact {
name: string;
email: string;
company: string;
}

const SAMPLE_CSV = `name,email,company
Aria Chen,aria@acme.com,Acme Corp
Marcus Webb,marcus@blueharbor.com,Blue Harbor Inc
Priya Kapoor,priya@candlesys.com,Candle Systems`;

// Naive CSV parser: no quoted-comma support. For production, use a proper CSV library.
function parseCsv(text: string): Contact[] {
const [headerLine, ...lines] = text.trim().split('\n');
const headers = headerLine.split(',').map(h => h.trim());

return lines.filter(Boolean).map(line => {
const cells = line.split(',').map(c => c.trim());
return Object.fromEntries(headers.map((h, i) => [h, cells[i] ?? ''])) as unknown as Contact;
});
}

const columns: TableColumn<Contact>[] = [
{ id: 'name', name: 'Name', selector: r => r.name, sortable: true },
{ id: 'email', name: 'Email', selector: r => r.email, sortable: true, grow: 1 },
{ id: 'company', name: 'Company', selector: r => r.company, sortable: true },
];

export default function CsvImportDemo() {
const [data, setData] = useState<Contact[]>([]);
const [error, setError] = useState<string | null>(null);

function handleFile(file: File) {
const reader = new FileReader();
reader.onload = () => {
try {
setData(parseCsv(String(reader.result)));
setError(null);
} catch {
setError('Could not parse that file as CSV.');
}
};
reader.readAsText(file);
}

return (
<div className="space-y-3">
<div className="flex items-center gap-2 flex-wrap">
<label className="px-3 py-1.5 text-xs font-medium rounded-md border border-gray-200 text-gray-600 hover:border-gray-300 cursor-pointer">
Upload CSV file…
<input
type="file"
accept=".csv,text/csv"
className="hidden"
onChange={e => e.target.files?.[0] && handleFile(e.target.files[0])}
/>
</label>
<button
onClick={() => { setData(parseCsv(SAMPLE_CSV)); setError(null); }}
className="px-3 py-1.5 text-xs font-medium rounded-md bg-brand-50 text-brand-700 hover:bg-brand-100"
>
Load sample CSV
</button>
{error && <span className="text-xs text-red-600">{error}</span>}
</div>
<div className="rounded-xl border border-gray-200 overflow-hidden">
<DataTable
columns={columns}
data={data}
highlightOnHover
noDataComponent={<div className="py-8 text-sm text-gray-400">Upload a CSV or load the sample to see it here</div>}
/>
</div>
</div>
);
}
44 changes: 33 additions & 11 deletions apps/docs/src/components/demos/DashboardDrilldownDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import DataTable, { type ConditionalStyles, type ExpanderComponentProps, type TableColumn } from 'react-data-table-component';

interface TeamMember {
Expand Down Expand Up @@ -90,20 +90,31 @@ function SpendBar({ budget, spent }: { budget: number; spent: number }) {
);
}

const memberColumns: TableColumn<TeamMember>[] = [
{ name: 'Name', selector: m => m.name, grow: 1 },
{ name: 'Role', selector: m => m.role, grow: 1 },
{ name: 'Open tickets',selector: m => m.tickets, width: '110px', right: true },
{ name: 'Utilization', selector: m => m.utilization, width: '160px',
cell: m => <UtilBar pct={m.utilization} />,
},
];
function initials(name: string) {
return name.split(' ').map(part => part[0]).join('').slice(0, 2).toUpperCase();
}

function DepartmentDetail({ data: dept }: ExpanderComponentProps<Department>) {
return (
<div className="px-8 py-4 bg-gray-50 border-b border-gray-100">
<p className="text-xs font-semibold text-gray-400 uppercase tracking-wide mb-3">Team members</p>
<DataTable columns={memberColumns} data={dept.members} dense noHeader />
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{dept.members.map(member => (
<div key={member.name} className="flex items-center gap-3 bg-white border border-gray-200 rounded-lg px-3 py-2.5">
<div className="w-9 h-9 rounded-full bg-brand-100 text-brand-700 text-xs font-semibold flex items-center justify-center shrink-0">
{initials(member.name)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between gap-2">
<p className="text-sm font-medium text-gray-900 truncate">{member.name}</p>
<span className="text-[11px] text-gray-400 shrink-0">{member.tickets} open</span>
</div>
<p className="text-xs text-gray-500 truncate mb-1">{member.role}</p>
<UtilBar pct={member.utilization} />
</div>
</div>
))}
</div>
</div>
);
}
Expand Down Expand Up @@ -145,15 +156,26 @@ const columns: TableColumn<Department>[] = [
];

export default function DashboardDrilldownDemo() {
const [allExpanded, setAllExpanded] = useState(false);

return (
<div className="space-y-2">
<p className="text-xs text-gray-400">Expand any row to see team member utilization.</p>
<div className="flex items-center justify-between">
<p className="text-xs text-gray-400">Expand any row to see team member utilization.</p>
<button
onClick={() => setAllExpanded(v => !v)}
className="px-2.5 py-1 text-xs font-medium rounded-md border border-gray-200 text-gray-600 hover:border-gray-300"
>
{allExpanded ? 'Collapse all' : 'Expand all'}
</button>
</div>
<div className="rounded-xl border border-gray-200 overflow-hidden">
<DataTable
columns={columns}
data={departments}
expandableRows
expandableRowsComponent={DepartmentDetail}
expandableRowExpanded={() => allExpanded}
conditionalRowStyles={conditionalRowStyles}
defaultSortFieldId="name"
highlightOnHover
Expand Down
81 changes: 81 additions & 0 deletions apps/docs/src/components/demos/LiveUpdatesDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect, useState } from 'react';
import DataTable, { type ConditionalStyles, type TableColumn } from 'react-data-table-component';

interface Ticker {
symbol: string;
name: string;
price: number;
change: number;
}

const INITIAL: Ticker[] = [
{ symbol: 'ACME', name: 'Acme Corp', price: 182.4, change: 0 },
{ symbol: 'BLTC', name: 'Baltic Freight', price: 64.12, change: 0 },
{ symbol: 'CNDL', name: 'Candle Systems', price: 305.9, change: 0 },
{ symbol: 'DYNM', name: 'Dynamo Energy', price: 48.77, change: 0 },
{ symbol: 'EVRG', name: 'Evergreen Foods', price: 96.3, change: 0 },
];

const columns: TableColumn<Ticker>[] = [
{ id: 'symbol', name: 'Symbol', selector: r => r.symbol, sortable: true, width: '110px', style: { fontWeight: 600 } },
{ id: 'name', name: 'Name', selector: r => r.name, grow: 1 },
{
id: 'price',
name: 'Price',
selector: r => r.price,
right: true,
width: '110px',
format: r => `$${r.price.toFixed(2)}`,
},
{
id: 'change',
name: 'Change',
selector: r => r.change,
right: true,
width: '110px',
cell: r => (
<span className={r.change === 0 ? 'text-gray-400' : r.change > 0 ? 'text-green-600' : 'text-red-600'}>
{r.change === 0 ? '—' : `${r.change > 0 ? '▲' : '▼'} ${Math.abs(r.change).toFixed(2)}`}
</span>
),
},
];

export default function LiveUpdatesDemo() {
const [data, setData] = useState<Ticker[]>(INITIAL);
const [flashed, setFlashed] = useState<Set<string>>(new Set());

useEffect(() => {
const tick = setInterval(() => {
const symbol = INITIAL[Math.floor(Math.random() * INITIAL.length)].symbol;
const delta = +(Math.random() * 4 - 2).toFixed(2);

setData(prev => prev.map(r => (r.symbol === symbol ? { ...r, price: +(r.price + delta).toFixed(2), change: delta } : r)));
setFlashed(prev => new Set(prev).add(symbol));
setTimeout(() => setFlashed(prev => {
const next = new Set(prev);
next.delete(symbol);
return next;
}), 600);
}, 1200);

return () => clearInterval(tick);
}, []);

const conditionalRowStyles: ConditionalStyles<Ticker>[] = [
{ when: r => flashed.has(r.symbol), style: { backgroundColor: '#fefce8', transition: 'background-color 0.6s ease' } },
];

return (
<div className="rounded-xl border border-gray-200 overflow-hidden">
<DataTable
columns={columns}
data={data}
keyField="symbol"
conditionalRowStyles={conditionalRowStyles}
highlightOnHover
dense
/>
</div>
);
}
101 changes: 101 additions & 0 deletions apps/docs/src/components/demos/RowGroupingDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useState } from 'react';
import DataTable, { type ExpanderComponentProps, type TableColumn } from 'react-data-table-component';

interface Order {
id: number;
region: string;
customer: string;
amount: number;
}

interface RegionGroup {
region: string;
count: number;
total: number;
orders: Order[];
}

const ORDERS: Order[] = [
{ id: 1, region: 'West', customer: 'Acme Corp', amount: 4200 },
{ id: 2, region: 'West', customer: 'Blue Harbor Inc', amount: 1850 },
{ id: 3, region: 'West', customer: 'Candle Systems', amount: 3100 },
{ id: 4, region: 'East', customer: 'Dynamo Energy', amount: 2600 },
{ id: 5, region: 'East', customer: 'Evergreen Foods', amount: 5400 },
{ id: 6, region: 'Central', customer: 'Frontier Retail', amount: 990 },
{ id: 7, region: 'Central', customer: 'Granite Works', amount: 2200 },
{ id: 8, region: 'Central', customer: 'Harbor Logistics', amount: 1475 },
{ id: 9, region: 'Central', customer: 'Ironclad Freight', amount: 3050 },
];

// 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: [] });
g.count += 1;
g.total += order.amount;
g.orders.push(order);
return acc;
}, {}),
);

const columns: TableColumn<RegionGroup>[] = [
{ id: 'region', name: 'Region', selector: r => r.region, sortable: true, style: { fontWeight: 600 } },
{ id: 'count', name: 'Orders', selector: r => r.count, sortable: true, right: true, width: '110px' },
{
id: 'total',
name: 'Total',
selector: r => r.total,
sortable: true,
right: true,
width: '130px',
format: r => `$${r.total.toLocaleString()}`,
footer: (rows: RegionGroup[]) => `$${rows.reduce((sum, r) => sum + r.total, 0).toLocaleString()}`,
},
];

function RegionOrders({ data }: ExpanderComponentProps<RegionGroup>) {
return (
<div style={{ padding: '10px 32px' }} className="bg-gray-50">
<div className="space-y-1.5">
{data.orders.map(order => (
<div
key={order.id}
className="flex items-center justify-between text-sm bg-white border border-gray-200 rounded-md px-3 py-2"
>
<span className="text-gray-700">{order.customer}</span>
<span className="font-medium text-gray-900">${order.amount.toLocaleString()}</span>
</div>
))}
</div>
</div>
);
}

export default function RowGroupingDemo() {
const [allExpanded, setAllExpanded] = useState(false);

return (
<div className="space-y-2">
<div className="flex justify-end">
<button
onClick={() => setAllExpanded(v => !v)}
className="px-2.5 py-1 text-xs font-medium rounded-md border border-gray-200 text-gray-600 hover:border-gray-300"
>
{allExpanded ? 'Collapse all' : 'Expand all'}
</button>
</div>
<div className="rounded-xl border border-gray-200 overflow-hidden">
<DataTable
columns={columns}
data={groups}
keyField="region"
expandableRows
expandableRowsComponent={RegionOrders}
expandableRowExpanded={() => allExpanded}
highlightOnHover
dense
/>
</div>
</div>
);
}
Loading
Loading