diff --git a/CHANGELOG.md b/CHANGELOG.md index b01162fe..eccbdfef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ A summary of notable changes per release. For the full commit history see the [repository on GitHub](https://github.com/jbetancur/react-data-table-component/commits/master). +## 8.6.2 + +### Bug fixes + +- A numeric pixel value for `TableColumn.hide` (e.g. `hide: 480`) was a no-op — the column stayed visible at every width. Numeric breakpoints stopped working in the v8 refactor; the type now accepts only `Media` (`'sm' | 'md' | 'lg'`) to match. For a custom breakpoint, track viewport width yourself and toggle the column's `omit` prop instead. → [Mobile](/docs/mobile) ([#1346](https://github.com/jbetancur/react-data-table-component/issues/1346)) +- Fixed `highlightOnHover` not showing when a row's background color was set via `customStyles.rows.style`, since the inline style was winning over the hover class. → [Styling](/docs/custom-styles) ([#1351](https://github.com/jbetancur/react-data-table-component/pull/1351)) + +--- + ## 8.6.1 ### Bug fixes diff --git a/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx b/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx index f7783497..01d99089 100644 --- a/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx +++ b/apps/docs/src/components/demos/ApprovalWorkflowDemo.tsx @@ -14,27 +14,83 @@ interface Request { } const STATUS_LABEL: Record = { - 'pending': 'Pending', - 'approved': 'Approved', - 'rejected': 'Rejected', + pending: 'Pending', + approved: 'Approved', + rejected: 'Rejected', 'needs-info': 'Needs info', }; const STATUS_CLASS: Record = { - 'pending': 'bg-yellow-50 text-yellow-700 border border-yellow-200', - 'approved': 'bg-green-50 text-green-700 border border-green-200', - 'rejected': 'bg-red-50 text-red-700 border border-red-200', + pending: 'bg-yellow-50 text-yellow-700 border border-yellow-200', + approved: 'bg-green-50 text-green-700 border border-green-200', + rejected: 'bg-red-50 text-red-700 border border-red-200', 'needs-info': 'bg-blue-50 text-blue-700 border border-blue-200', }; const initialData: Request[] = [ - { id: 1, title: 'New MacBook Pro', requester: 'Sam Rivera', department: 'Engineering', amount: 2499, status: 'pending', submittedAt: '2024-05-14' }, - { id: 2, title: 'Figma Teams plan', requester: 'Priya Kapoor', department: 'Design', amount: 720, status: 'pending', submittedAt: '2024-05-14' }, - { id: 3, title: 'AWS reserved instance', requester: 'Aria Chen', department: 'Engineering', amount: 8400, status: 'needs-info', submittedAt: '2024-05-13' }, - { id: 4, title: 'Office chairs (x4)', requester: 'Marcus Webb', department: 'Product', amount: 1200, status: 'pending', submittedAt: '2024-05-12' }, - { id: 5, title: 'Tableau license', requester: 'Jordan Ellis', department: 'Analytics', amount: 1800, status: 'approved', submittedAt: '2024-05-10' }, - { id: 6, title: 'Conference travel', requester: 'Taylor Brooks', department: 'Sales', amount: 950, status: 'rejected', submittedAt: '2024-05-09' }, - { id: 7, title: 'Slack Enterprise', requester: 'Casey Morgan', department: 'Engineering', amount: 3600, status: 'pending', submittedAt: '2024-05-08' }, + { + id: 1, + title: 'New MacBook Pro', + requester: 'Sam Rivera', + department: 'Engineering', + amount: 2499, + status: 'pending', + submittedAt: '2024-05-14', + }, + { + id: 2, + title: 'Figma Teams plan', + requester: 'Priya Kapoor', + department: 'Design', + amount: 720, + status: 'pending', + submittedAt: '2024-05-14', + }, + { + id: 3, + title: 'AWS reserved instance', + requester: 'Aria Chen', + department: 'Engineering', + amount: 8400, + status: 'needs-info', + submittedAt: '2024-05-13', + }, + { + id: 4, + title: 'Office chairs (x4)', + requester: 'Marcus Webb', + department: 'Product', + amount: 1200, + status: 'pending', + submittedAt: '2024-05-12', + }, + { + id: 5, + title: 'Tableau license', + requester: 'Jordan Ellis', + department: 'Analytics', + amount: 1800, + status: 'approved', + submittedAt: '2024-05-10', + }, + { + id: 6, + title: 'Conference travel', + requester: 'Taylor Brooks', + department: 'Sales', + amount: 950, + status: 'rejected', + submittedAt: '2024-05-09', + }, + { + id: 7, + title: 'Slack Enterprise', + requester: 'Casey Morgan', + department: 'Engineering', + amount: 3600, + status: 'pending', + submittedAt: '2024-05-08', + }, ]; const ACTIONABLE: Status[] = ['pending', 'needs-info']; @@ -51,7 +107,7 @@ export default function ApprovalWorkflowDemo() { } function applyStatus(ids: number[], next: Status) { - setData(prev => prev.map(r => ids.includes(r.id) ? { ...r, status: next } : r)); + setData(prev => prev.map(r => (ids.includes(r.id) ? { ...r, status: next } : r))); ref.current?.clearSelectedRows(); showToast(`${ids.length} request${ids.length !== 1 ? 's' : ''} marked as "${STATUS_LABEL[next]}"`); } @@ -60,9 +116,9 @@ export default function ApprovalWorkflowDemo() { const selectedIds = actionable.map(r => r.id); const columns: TableColumn[] = [ - { id: 'title', name: 'Request', selector: r => r.title, grow: 2 }, - { id: 'requester', name: 'Requester', selector: r => r.requester, sortable: true }, - { id: 'department', name: 'Dept', selector: r => r.department, sortable: true, width: '120px' }, + { id: 'title', name: 'Request', selector: r => r.title, grow: 2 }, + { id: 'requester', name: 'Requester', selector: r => r.requester, sortable: true }, + { id: 'department', name: 'Dept', selector: r => r.department, sortable: true, width: '120px' }, { id: 'amount', name: 'Amount', @@ -79,9 +135,7 @@ export default function ApprovalWorkflowDemo() { sortable: true, width: '130px', cell: r => ( - - {STATUS_LABEL[r.status]} - + {STATUS_LABEL[r.status]} ), }, { id: 'submittedAt', name: 'Submitted', selector: r => r.submittedAt, sortable: true, width: '115px' }, diff --git a/apps/docs/src/components/demos/AuditLogDemo.tsx b/apps/docs/src/components/demos/AuditLogDemo.tsx index e5a2ed61..9d780e2a 100644 --- a/apps/docs/src/components/demos/AuditLogDemo.tsx +++ b/apps/docs/src/components/demos/AuditLogDemo.tsx @@ -14,35 +14,155 @@ interface LogEntry { } const SEVERITY_STYLE: Record = { - info: 'bg-blue-50 text-blue-700 border border-blue-100', - warning: 'bg-yellow-50 text-yellow-700 border border-yellow-100', - error: 'bg-red-50 text-red-700 border border-red-100', + info: 'bg-blue-50 text-blue-700 border border-blue-100', + warning: 'bg-yellow-50 text-yellow-700 border border-yellow-100', + error: 'bg-red-50 text-red-700 border border-red-100', critical: 'bg-red-100 text-red-900 border border-red-300 font-bold', }; const ALL_LOGS: LogEntry[] = [ - { id: 1, timestamp: '2024-05-16 09:01:12', severity: 'info', user: 'aria.chen', action: 'LOGIN', resource: '/dashboard', ip: '10.0.1.42' }, - { id: 2, timestamp: '2024-05-16 09:03:44', severity: 'info', user: 'marcus.webb', action: 'VIEW', resource: '/reports/q1', ip: '10.0.1.18' }, - { id: 3, timestamp: '2024-05-16 09:12:05', severity: 'warning', user: 'aria.chen', action: 'EXPORT', resource: '/users/all', ip: '10.0.1.42' }, - { id: 4, timestamp: '2024-05-16 09:15:30', severity: 'error', user: 'unknown', action: 'LOGIN_FAILED', resource: '/auth', ip: '203.0.113.7' }, - { id: 5, timestamp: '2024-05-16 09:15:31', severity: 'error', user: 'unknown', action: 'LOGIN_FAILED', resource: '/auth', ip: '203.0.113.7' }, - { id: 6, timestamp: '2024-05-16 09:15:32', severity: 'critical', user: 'unknown', action: 'BRUTE_FORCE', resource: '/auth', ip: '203.0.113.7' }, - { id: 7, timestamp: '2024-05-16 09:22:18', severity: 'info', user: 'priya.kapoor', action: 'UPDATE', resource: '/settings/theme', ip: '10.0.1.55' }, - { id: 8, timestamp: '2024-05-16 09:31:00', severity: 'warning', user: 'jordan.ellis', action: 'DELETE', resource: '/reports/draft-4', ip: '10.0.2.11' }, - { id: 9, timestamp: '2024-05-16 09:45:09', severity: 'info', user: 'sam.rivera', action: 'DEPLOY', resource: '/infra/staging', ip: '10.0.1.99' }, - { id: 10, timestamp: '2024-05-16 10:02:44', severity: 'critical', user: 'system', action: 'DB_CONN_LOST', resource: '/db/primary', ip: '10.0.0.1' }, - { id: 11, timestamp: '2024-05-16 10:03:01', severity: 'critical', user: 'system', action: 'FAILOVER', resource: '/db/replica', ip: '10.0.0.2' }, - { id: 12, timestamp: '2024-05-16 10:15:22', severity: 'info', user: 'aria.chen', action: 'LOGOUT', resource: '/auth', ip: '10.0.1.42' }, - { id: 13, timestamp: '2024-05-16 10:28:33', severity: 'warning', user: 'taylor.brooks',action: 'PERMISSION_DENY', resource: '/admin/users', ip: '10.0.1.77' }, - { id: 14, timestamp: '2024-05-16 10:55:50', severity: 'error', user: 'system', action: 'DISK_FULL', resource: '/storage/logs', ip: '10.0.0.5' }, - { id: 15, timestamp: '2024-05-16 11:10:04', severity: 'info', user: 'marcus.webb', action: 'LOGIN', resource: '/dashboard', ip: '10.0.1.18' }, + { + id: 1, + timestamp: '2024-05-16 09:01:12', + severity: 'info', + user: 'aria.chen', + action: 'LOGIN', + resource: '/dashboard', + ip: '10.0.1.42', + }, + { + id: 2, + timestamp: '2024-05-16 09:03:44', + severity: 'info', + user: 'marcus.webb', + action: 'VIEW', + resource: '/reports/q1', + ip: '10.0.1.18', + }, + { + id: 3, + timestamp: '2024-05-16 09:12:05', + severity: 'warning', + user: 'aria.chen', + action: 'EXPORT', + resource: '/users/all', + ip: '10.0.1.42', + }, + { + id: 4, + timestamp: '2024-05-16 09:15:30', + severity: 'error', + user: 'unknown', + action: 'LOGIN_FAILED', + resource: '/auth', + ip: '203.0.113.7', + }, + { + id: 5, + timestamp: '2024-05-16 09:15:31', + severity: 'error', + user: 'unknown', + action: 'LOGIN_FAILED', + resource: '/auth', + ip: '203.0.113.7', + }, + { + id: 6, + timestamp: '2024-05-16 09:15:32', + severity: 'critical', + user: 'unknown', + action: 'BRUTE_FORCE', + resource: '/auth', + ip: '203.0.113.7', + }, + { + id: 7, + timestamp: '2024-05-16 09:22:18', + severity: 'info', + user: 'priya.kapoor', + action: 'UPDATE', + resource: '/settings/theme', + ip: '10.0.1.55', + }, + { + id: 8, + timestamp: '2024-05-16 09:31:00', + severity: 'warning', + user: 'jordan.ellis', + action: 'DELETE', + resource: '/reports/draft-4', + ip: '10.0.2.11', + }, + { + id: 9, + timestamp: '2024-05-16 09:45:09', + severity: 'info', + user: 'sam.rivera', + action: 'DEPLOY', + resource: '/infra/staging', + ip: '10.0.1.99', + }, + { + id: 10, + timestamp: '2024-05-16 10:02:44', + severity: 'critical', + user: 'system', + action: 'DB_CONN_LOST', + resource: '/db/primary', + ip: '10.0.0.1', + }, + { + id: 11, + timestamp: '2024-05-16 10:03:01', + severity: 'critical', + user: 'system', + action: 'FAILOVER', + resource: '/db/replica', + ip: '10.0.0.2', + }, + { + id: 12, + timestamp: '2024-05-16 10:15:22', + severity: 'info', + user: 'aria.chen', + action: 'LOGOUT', + resource: '/auth', + ip: '10.0.1.42', + }, + { + id: 13, + timestamp: '2024-05-16 10:28:33', + severity: 'warning', + user: 'taylor.brooks', + action: 'PERMISSION_DENY', + resource: '/admin/users', + ip: '10.0.1.77', + }, + { + id: 14, + timestamp: '2024-05-16 10:55:50', + severity: 'error', + user: 'system', + action: 'DISK_FULL', + resource: '/storage/logs', + ip: '10.0.0.5', + }, + { + id: 15, + timestamp: '2024-05-16 11:10:04', + severity: 'info', + user: 'marcus.webb', + action: 'LOGIN', + resource: '/dashboard', + ip: '10.0.1.18', + }, ]; const SEVERITIES: ('all' | Severity)[] = ['all', 'info', 'warning', 'error', 'critical']; const conditionalRowStyles: ConditionalStyles[] = [ { when: r => r.severity === 'critical', style: { backgroundColor: '#fef2f2' } }, - { when: r => r.severity === 'error', style: { backgroundColor: '#fff7f7' } }, + { when: r => r.severity === 'error', style: { backgroundColor: '#fff7f7' } }, ]; const columns: TableColumn[] = [ @@ -53,16 +173,25 @@ const columns: TableColumn[] = [ selector: r => r.severity, sortable: true, width: '110px', - cell: r => ( - - {r.severity} - - ), - }, - { id: 'user', name: 'User', selector: r => r.user, sortable: true, width: '145px' }, - { id: 'action', name: 'Action', selector: r => r.action, sortable: true, width: '145px', style: { fontFamily: 'monospace', fontSize: 12 } }, - { id: 'resource', name: 'Resource', selector: r => r.resource, grow: 1, style: { fontFamily: 'monospace', fontSize: 12 } }, - { id: 'ip', name: 'IP', selector: r => r.ip, width: '130px', style: { fontFamily: 'monospace', fontSize: 12 } }, + cell: r => {r.severity}, + }, + { id: 'user', name: 'User', selector: r => r.user, sortable: true, width: '145px' }, + { + id: 'action', + name: 'Action', + selector: r => r.action, + sortable: true, + width: '145px', + style: { fontFamily: 'monospace', fontSize: 12 }, + }, + { + id: 'resource', + name: 'Resource', + selector: r => r.resource, + grow: 1, + style: { fontFamily: 'monospace', fontSize: 12 }, + }, + { id: 'ip', name: 'IP', selector: r => r.ip, width: '130px', style: { fontFamily: 'monospace', fontSize: 12 } }, ]; export default function AuditLogDemo() { diff --git a/apps/docs/src/components/demos/ColumnPinningDemo.tsx b/apps/docs/src/components/demos/ColumnPinningDemo.tsx index 8e525f62..31dc6a83 100644 --- a/apps/docs/src/components/demos/ColumnPinningDemo.tsx +++ b/apps/docs/src/components/demos/ColumnPinningDemo.tsx @@ -143,7 +143,9 @@ function PinPicker({ }) { const btn = (active: boolean) => `px-2.5 py-1 rounded-md text-xs font-medium border transition-colors ${ - active ? 'bg-brand-600 text-white border-brand-600' : 'bg-white text-gray-600 border-gray-200 hover:border-gray-300' + active + ? 'bg-brand-600 text-white border-brand-600' + : 'bg-white text-gray-600 border-gray-200 hover:border-gray-300' }`; return (
diff --git a/apps/docs/src/components/demos/ContextMenuDemo.tsx b/apps/docs/src/components/demos/ContextMenuDemo.tsx index 878e74e9..22a2bbd4 100644 --- a/apps/docs/src/components/demos/ContextMenuDemo.tsx +++ b/apps/docs/src/components/demos/ContextMenuDemo.tsx @@ -14,11 +14,56 @@ interface Row { } const initialData: Row[] = [ - { id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', location: 'San Francisco', salary: 155000, startDate: '2019-03-11', status: 'Active' }, - { id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', location: 'New York', salary: 132000, startDate: '2021-07-02', status: 'Active' }, - { id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', location: 'Austin', salary: 118000, startDate: '2020-01-20', status: 'Active' }, - { id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', location: 'Seattle', salary: 143000, startDate: '2018-10-05', status: 'On Leave' }, - { id: 5, name: 'Sam Rivera', role: 'DevOps Engineer', department: 'Engineering', location: 'Remote', salary: 128000, startDate: '2022-04-14', status: 'Active' }, + { + id: 1, + name: 'Aria Chen', + role: 'Engineering Lead', + department: 'Engineering', + location: 'San Francisco', + salary: 155000, + startDate: '2019-03-11', + status: 'Active', + }, + { + id: 2, + name: 'Marcus Webb', + role: 'Product Manager', + department: 'Product', + location: 'New York', + salary: 132000, + startDate: '2021-07-02', + status: 'Active', + }, + { + id: 3, + name: 'Priya Kapoor', + role: 'Senior Designer', + department: 'Design', + location: 'Austin', + salary: 118000, + startDate: '2020-01-20', + status: 'Active', + }, + { + id: 4, + name: 'Jordan Ellis', + role: 'Data Scientist', + department: 'Analytics', + location: 'Seattle', + salary: 143000, + startDate: '2018-10-05', + status: 'On Leave', + }, + { + id: 5, + name: 'Sam Rivera', + role: 'DevOps Engineer', + department: 'Engineering', + location: 'Remote', + salary: 128000, + startDate: '2022-04-14', + status: 'Active', + }, ]; // Fixed widths so the table overflows horizontally — otherwise pinning a column @@ -28,7 +73,15 @@ const columns: TableColumn[] = [ { id: 'role', name: 'Role', selector: row => row.role, sortable: true, width: '220px' }, { id: 'department', name: 'Department', selector: row => row.department, sortable: true, width: '180px' }, { id: 'location', name: 'Location', selector: row => row.location, sortable: true, width: '200px' }, - { id: 'salary', name: 'Salary', selector: row => row.salary, sortable: true, right: true, width: '140px', format: row => `$${row.salary.toLocaleString()}` }, + { + id: 'salary', + name: 'Salary', + selector: row => row.salary, + sortable: true, + right: true, + width: '140px', + format: row => `$${row.salary.toLocaleString()}`, + }, { id: 'startDate', name: 'Start Date', selector: row => row.startDate, sortable: true, width: '150px' }, { id: 'status', name: 'Status', selector: row => row.status, width: '140px' }, ]; @@ -51,8 +104,8 @@ export default function ContextMenuDemo(): JSX.Element { highlightOnHover />

- Last action: {lastAction} · Right-click a header, or use its ⋮ button, to sort, pin, hide, - or reset columns. + Last action: {lastAction} · Right-click a header, or use its ⋮ button, to sort, pin, hide, or + reset columns.

); diff --git a/apps/docs/src/components/demos/CsvImportDemo.tsx b/apps/docs/src/components/demos/CsvImportDemo.tsx index af0cd727..a3845b57 100644 --- a/apps/docs/src/components/demos/CsvImportDemo.tsx +++ b/apps/docs/src/components/demos/CsvImportDemo.tsx @@ -24,8 +24,8 @@ function parseCsv(text: string): Contact[] { } const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, - { id: 'email', name: 'Email', selector: r => r.email, sortable: true, grow: 1 }, + { 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 }, ]; @@ -59,7 +59,10 @@ export default function CsvImportDemo() { /> ))} - + ); } diff --git a/apps/docs/src/components/demos/FooterCustomDemo.tsx b/apps/docs/src/components/demos/FooterCustomDemo.tsx index 89267bb4..27cf28ea 100644 --- a/apps/docs/src/components/demos/FooterCustomDemo.tsx +++ b/apps/docs/src/components/demos/FooterCustomDemo.tsx @@ -11,22 +11,22 @@ interface Row { } const ALL_DATA: Row[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, bonus: 12000 }, - { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, bonus: 9500 }, - { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, bonus: 7800 }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, bonus: 11200 }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, bonus: 9000 }, - { id: 6, name: 'Taylor Brooks', department: 'Engineering', salary: 122000, bonus: 8400 }, - { id: 7, name: 'Casey Morgan', department: 'Product', salary: 108000, bonus: 6600 }, - { id: 8, name: 'Alex Kim', department: 'Analytics', salary: 137000, bonus: 10100 }, - { id: 9, name: 'Morgan Lee', department: 'Design', salary: 114000, bonus: 7200 }, - { id: 10, name: 'Drew Park', department: 'Engineering', salary: 141000, bonus: 10800 }, + { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, bonus: 12000 }, + { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, bonus: 9500 }, + { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, bonus: 7800 }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, bonus: 11200 }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, bonus: 9000 }, + { id: 6, name: 'Taylor Brooks', department: 'Engineering', salary: 122000, bonus: 8400 }, + { id: 7, name: 'Casey Morgan', department: 'Product', salary: 108000, bonus: 6600 }, + { id: 8, name: 'Alex Kim', department: 'Analytics', salary: 137000, bonus: 10100 }, + { id: 9, name: 'Morgan Lee', department: 'Design', salary: 114000, bonus: 7200 }, + { id: 10, name: 'Drew Park', department: 'Engineering', salary: 141000, bonus: 10800 }, ]; function SummaryFooter({ rows }: FooterComponentProps) { const totalSalary = rows.reduce((s, r) => s + r.salary, 0); - const totalBonus = rows.reduce((s, r) => s + r.bonus, 0); - const avgSalary = rows.length ? Math.round(totalSalary / rows.length) : 0; + const totalBonus = rows.reduce((s, r) => s + r.bonus, 0); + const avgSalary = rows.length ? Math.round(totalSalary / rows.length) : 0; return (
) { } const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, + { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, { id: 'department', name: 'Department', selector: r => r.department, sortable: true }, { id: 'salary', @@ -109,13 +109,7 @@ export default function FooterCustomDemo() { ))}
- + ); } diff --git a/apps/docs/src/components/demos/HeadlessDemo.tsx b/apps/docs/src/components/demos/HeadlessDemo.tsx index f11900b2..f474c374 100644 --- a/apps/docs/src/components/demos/HeadlessDemo.tsx +++ b/apps/docs/src/components/demos/HeadlessDemo.tsx @@ -1,5 +1,12 @@ import React from 'react'; -import { useColumns, useTableState, useTableData, useColumnFilter, type TableColumn, type FilterState } from 'react-data-table-component'; +import { + useColumns, + useTableState, + useTableData, + useColumnFilter, + type TableColumn, + type FilterState, +} from 'react-data-table-component'; interface Employee { id: number; diff --git a/apps/docs/src/components/demos/IconsDemo.tsx b/apps/docs/src/components/demos/IconsDemo.tsx index b9c38c88..954efc13 100644 --- a/apps/docs/src/components/demos/IconsDemo.tsx +++ b/apps/docs/src/components/demos/IconsDemo.tsx @@ -4,39 +4,102 @@ import { type TableColumn } from 'react-data-table-component'; // Inline SVG icon primitives — no external dependency const ChevronLeft = () => ( - + ); const ChevronRight = () => ( - + ); const ChevronsLeft = () => ( - + ); const ChevronsRight = () => ( - + ); const ChevronDown = () => ( - + ); const ChevronRightSmall = () => ( - + ); const ArrowUp = () => ( - + @@ -51,33 +114,105 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', bio: 'Leads the platform team with a focus on reliability.' }, - { id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', bio: 'Drives roadmap strategy across three product lines.' }, - { id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', bio: 'Owns the design system and mobile experience.' }, - { id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', bio: 'Builds ML pipelines for churn and revenue forecasting.' }, - { id: 5, name: 'Sam Rivera', role: 'SRE', department: 'Engineering', bio: 'Manages uptime, incidents, and deployment pipelines.' }, - { id: 6, name: 'Taylor Brooks',role: 'Sales Lead', department: 'Sales', bio: 'Responsible for enterprise accounts in EMEA.' }, - { id: 7, name: 'Casey Morgan', role: 'HR Manager', department: 'HR', bio: 'Owns hiring, onboarding, and culture initiatives.' }, - { id: 8, name: 'Alex Kim', role: 'Staff Engineer', department: 'Engineering', bio: 'Architect for the data platform and internal APIs.' }, - { id: 9, name: 'Morgan Lee', role: 'Designer', department: 'Design', bio: 'Works on user research and prototype validation.' }, - { id:10, name: 'Drew Park', role: 'Analyst', department: 'Analytics', bio: 'Produces dashboards and weekly business metrics.' }, - { id:11, name: 'Riley Nguyen', role: 'Frontend Eng', department: 'Engineering', bio: 'Builds customer-facing product surfaces in React.' }, - { id:12, name: 'Jamie Okafor', role: 'Backend Eng', department: 'Engineering', bio: 'Maintains the microservices and event bus.' }, + { + id: 1, + name: 'Aria Chen', + role: 'Engineering Lead', + department: 'Engineering', + bio: 'Leads the platform team with a focus on reliability.', + }, + { + id: 2, + name: 'Marcus Webb', + role: 'Product Manager', + department: 'Product', + bio: 'Drives roadmap strategy across three product lines.', + }, + { + id: 3, + name: 'Priya Kapoor', + role: 'Senior Designer', + department: 'Design', + bio: 'Owns the design system and mobile experience.', + }, + { + id: 4, + name: 'Jordan Ellis', + role: 'Data Scientist', + department: 'Analytics', + bio: 'Builds ML pipelines for churn and revenue forecasting.', + }, + { + id: 5, + name: 'Sam Rivera', + role: 'SRE', + department: 'Engineering', + bio: 'Manages uptime, incidents, and deployment pipelines.', + }, + { + id: 6, + name: 'Taylor Brooks', + role: 'Sales Lead', + department: 'Sales', + bio: 'Responsible for enterprise accounts in EMEA.', + }, + { + id: 7, + name: 'Casey Morgan', + role: 'HR Manager', + department: 'HR', + bio: 'Owns hiring, onboarding, and culture initiatives.', + }, + { + id: 8, + name: 'Alex Kim', + role: 'Staff Engineer', + department: 'Engineering', + bio: 'Architect for the data platform and internal APIs.', + }, + { + id: 9, + name: 'Morgan Lee', + role: 'Designer', + department: 'Design', + bio: 'Works on user research and prototype validation.', + }, + { + id: 10, + name: 'Drew Park', + role: 'Analyst', + department: 'Analytics', + bio: 'Produces dashboards and weekly business metrics.', + }, + { + id: 11, + name: 'Riley Nguyen', + role: 'Frontend Eng', + department: 'Engineering', + bio: 'Builds customer-facing product surfaces in React.', + }, + { + id: 12, + name: 'Jamie Okafor', + role: 'Backend Eng', + department: 'Engineering', + bio: 'Maintains the microservices and event bus.', + }, ]; const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, - { name: 'Role', selector: r => r.role }, + { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Role', selector: r => r.role }, { name: 'Department', selector: r => r.department, sortable: true }, ]; type Mode = 'default' | 'custom-pagination' | 'custom-expander' | 'custom-sort'; const modes: { key: Mode; label: string }[] = [ - { key: 'default', label: 'Default icons' }, + { key: 'default', label: 'Default icons' }, { key: 'custom-pagination', label: 'Custom pagination' }, - { key: 'custom-expander', label: 'Custom expander' }, - { key: 'custom-sort', label: 'Custom sort' }, + { key: 'custom-expander', label: 'Custom expander' }, + { key: 'custom-sort', label: 'Custom sort' }, ]; const ExpandedRow = ({ data: row }: { data: Employee }) => ( @@ -90,8 +225,8 @@ export default function IconsDemo() { const [mode, setMode] = useState('default'); const btnBase = 'px-2.5 py-1 rounded-md text-xs font-medium border transition-colors cursor-pointer'; - const btnOn = 'bg-brand-600 text-white border-brand-600'; - const btnOff = 'bg-white text-gray-600 border-gray-200 hover:border-gray-300'; + const btnOn = 'bg-brand-600 text-white border-brand-600'; + const btnOff = 'bg-white text-gray-600 border-gray-200 hover:border-gray-300'; const showExpander = mode === 'custom-expander' || mode === 'default'; @@ -99,7 +234,11 @@ export default function IconsDemo() {
{modes.map(m => ( - ))} @@ -113,27 +252,33 @@ export default function IconsDemo() { highlightOnHover // Pagination icons - {...(mode === 'custom-pagination' ? { - paginationIconFirstPage: , - paginationIconLastPage: , - paginationIconPrevious: , - paginationIconNext: , - } : {})} + {...(mode === 'custom-pagination' + ? { + paginationIconFirstPage: , + paginationIconLastPage: , + paginationIconPrevious: , + paginationIconNext: , + } + : {})} // Sort icon - {...(mode === 'custom-sort' ? { - sortIcon: , - } : {})} + {...(mode === 'custom-sort' + ? { + sortIcon: , + } + : {})} // Expandable rows expandableRows={showExpander} expandableRowsComponent={ExpandedRow} - {...(mode === 'custom-expander' ? { - expandableIcon: { - collapsed: , - expanded: , - }, - } : {})} + {...(mode === 'custom-expander' + ? { + expandableIcon: { + collapsed: , + expanded: , + }, + } + : {})} /> {mode === 'custom-pagination' && ( @@ -143,7 +288,9 @@ export default function IconsDemo() {

Expander icons replaced with inline SVG chevrons.

)} {mode === 'custom-sort' && ( -

Sort icon replaced with an up-arrow. Click a sortable column header to see it.

+

+ Sort icon replaced with an up-arrow. Click a sortable column header to see it. +

)}
); diff --git a/apps/docs/src/components/demos/InlineRowActionsDemo.tsx b/apps/docs/src/components/demos/InlineRowActionsDemo.tsx index 1d721adf..f07fb08e 100644 --- a/apps/docs/src/components/demos/InlineRowActionsDemo.tsx +++ b/apps/docs/src/components/demos/InlineRowActionsDemo.tsx @@ -10,12 +10,12 @@ interface Employee { } const seed: Employee[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', role: 'Engineering Lead', email: 'aria@example.com' }, - { id: 2, name: 'Marcus Webb', department: 'Product', role: 'Product Manager', email: 'marcus@example.com' }, - { id: 3, name: 'Priya Kapoor', department: 'Design', role: 'Senior Designer', email: 'priya@example.com' }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', role: 'Data Scientist', email: 'jordan@example.com' }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', role: 'DevOps Engineer', email: 'sam@example.com' }, - { id: 6, name: 'Taylor Brooks', department: 'Sales', role: 'Account Manager', email: 'taylor@example.com' }, + { id: 1, name: 'Aria Chen', department: 'Engineering', role: 'Engineering Lead', email: 'aria@example.com' }, + { id: 2, name: 'Marcus Webb', department: 'Product', role: 'Product Manager', email: 'marcus@example.com' }, + { id: 3, name: 'Priya Kapoor', department: 'Design', role: 'Senior Designer', email: 'priya@example.com' }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', role: 'Data Scientist', email: 'jordan@example.com' }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', role: 'DevOps Engineer', email: 'sam@example.com' }, + { id: 6, name: 'Taylor Brooks', department: 'Sales', role: 'Account Manager', email: 'taylor@example.com' }, ]; type ModalState = @@ -24,7 +24,12 @@ type ModalState = | { type: 'delete'; row: Employee } | { type: 'duplicate'; row: Employee }; -function DropdownMenu({ row, onEdit, onDuplicate, onDelete }: { +function DropdownMenu({ + row, + onEdit, + onDuplicate, + onDelete, +}: { row: Employee; onEdit: (r: Employee) => void; onDuplicate: (r: Employee) => void; @@ -45,31 +50,48 @@ function DropdownMenu({ row, onEdit, onDuplicate, onDelete }: { return (
{open && (
- + +
@@ -172,13 +210,31 @@ export default function InlineRowActionsDemo() { {/* Duplicate confirmation */} {modal.type === 'duplicate' && ( -
setModal({ type: 'none' })}> -
e.stopPropagation()}> +
setModal({ type: 'none' })} + > +
e.stopPropagation()} + >

Duplicate row?

-

A copy of {modal.row.name} will be added to the list.

+

+ A copy of {modal.row.name} will be added to the list. +

- - + +
@@ -186,13 +242,31 @@ export default function InlineRowActionsDemo() { {/* Delete confirmation */} {modal.type === 'delete' && ( -
setModal({ type: 'none' })}> -
e.stopPropagation()}> +
setModal({ type: 'none' })} + > +
e.stopPropagation()} + >

Delete employee?

-

This will permanently remove {modal.row.name}.

+

+ This will permanently remove {modal.row.name}. +

- - + +
diff --git a/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx b/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx index f86c607b..d4f3caf7 100644 --- a/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx +++ b/apps/docs/src/components/demos/KeyboardArrowNavDemo.tsx @@ -27,9 +27,9 @@ 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, which works on any table. + 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, + which works on any table.

diff --git a/apps/docs/src/components/demos/KeyboardEditDemo.tsx b/apps/docs/src/components/demos/KeyboardEditDemo.tsx index 020109ab..4858dcd4 100644 --- a/apps/docs/src/components/demos/KeyboardEditDemo.tsx +++ b/apps/docs/src/components/demos/KeyboardEditDemo.tsx @@ -44,8 +44,8 @@ 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 immediately. Department has no editor, so Enter/F2 do nothing there. + 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/KeyboardSortDemo.tsx b/apps/docs/src/components/demos/KeyboardSortDemo.tsx index 19d3afc1..4162d349 100644 --- a/apps/docs/src/components/demos/KeyboardSortDemo.tsx +++ b/apps/docs/src/components/demos/KeyboardSortDemo.tsx @@ -33,9 +33,9 @@ export default function KeyboardSortDemo() { return (

- from the first body row moves into the header. Enter or Space on a - sortable header cycles ascending → descending → unsorted, same as a click. from the header - returns to the body in the same column. + from the first body row moves into the header. Enter or Space on a sortable + header cycles ascending → descending → unsorted, same as a click. from the header returns to the + body in the same column.

diff --git a/apps/docs/src/components/demos/LiveUpdatesDemo.tsx b/apps/docs/src/components/demos/LiveUpdatesDemo.tsx index 204c0b63..a5d0d7e3 100644 --- a/apps/docs/src/components/demos/LiveUpdatesDemo.tsx +++ b/apps/docs/src/components/demos/LiveUpdatesDemo.tsx @@ -9,16 +9,16 @@ interface Ticker { } 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 }, + { 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[] = [ { 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: 'name', name: 'Name', selector: r => r.name, grow: 1 }, { id: 'price', name: 'Price', @@ -50,20 +50,29 @@ export default function LiveUpdatesDemo() { 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))); + 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); + setTimeout( + () => + setFlashed(prev => { + const next = new Set(prev); + next.delete(symbol); + return next; + }), + 600, + ); }, 1200); return () => clearInterval(tick); }, []); const conditionalRowStyles: ConditionalStyles[] = [ - { when: r => flashed.has(r.symbol), style: { backgroundColor: '#fefce8', transition: 'background-color 0.6s ease' } }, + { + when: r => flashed.has(r.symbol), + style: { backgroundColor: '#fefce8', transition: 'background-color 0.6s ease' }, + }, ]; return ( diff --git a/apps/docs/src/components/demos/LoadingDemo.tsx b/apps/docs/src/components/demos/LoadingDemo.tsx index 22676b15..1d2a4f01 100644 --- a/apps/docs/src/components/demos/LoadingDemo.tsx +++ b/apps/docs/src/components/demos/LoadingDemo.tsx @@ -11,18 +11,18 @@ interface Row { } const ROWS: Row[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, status: 'Active' }, - { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, status: 'Remote' }, - { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, status: 'Active' }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, status: 'On Leave' }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, status: 'Contractor' }, + { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, status: 'Active' }, + { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, status: 'Remote' }, + { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, status: 'Active' }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, status: 'On Leave' }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, status: 'Contractor' }, ]; const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Salary', selector: r => r.salary, right: true, format: r => `$${r.salary.toLocaleString()}` }, - { name: 'Status', selector: r => r.status }, + { name: 'Salary', selector: r => r.salary, right: true, format: r => `$${r.salary.toLocaleString()}` }, + { name: 'Status', selector: r => r.status }, ]; function delay(ms: number) { @@ -63,7 +63,8 @@ export default function LoadingDemo() { setMode('idle'); } - const btnBase = 'px-3 py-1.5 rounded-md text-xs font-medium border transition-colors disabled:opacity-40 disabled:cursor-not-allowed'; + const btnBase = + 'px-3 py-1.5 rounded-md text-xs font-medium border transition-colors disabled:opacity-40 disabled:cursor-not-allowed'; const btnPrimary = `${btnBase} bg-brand-600 text-white border-brand-600 hover:bg-brand-700`; const btnSecondary = `${btnBase} bg-white text-gray-600 border-gray-200 hover:border-gray-400`; @@ -86,12 +87,7 @@ export default function LoadingDemo() { Use custom loader {mode !== 'idle' && ( diff --git a/apps/docs/src/components/demos/MobileDemo.tsx b/apps/docs/src/components/demos/MobileDemo.tsx index b464d322..d4330e98 100644 --- a/apps/docs/src/components/demos/MobileDemo.tsx +++ b/apps/docs/src/components/demos/MobileDemo.tsx @@ -12,7 +12,19 @@ interface Employee { const data: Employee[] = Array.from({ length: 20 }, (_, i) => ({ id: i + 1, - name: ['Aria Chen', 'Marcus Webb', 'Priya Kapoor', 'Jordan Ellis', 'Sam Rivera', 'Taylor Brooks', 'Casey Morgan', 'Alex Kim', 'Morgan Lee', 'Drew Park'][i % 10] + (i >= 10 ? ` ${Math.floor(i / 10) + 1}` : ''), + name: + [ + 'Aria Chen', + 'Marcus Webb', + 'Priya Kapoor', + 'Jordan Ellis', + 'Sam Rivera', + 'Taylor Brooks', + 'Casey Morgan', + 'Alex Kim', + 'Morgan Lee', + 'Drew Park', + ][i % 10] + (i >= 10 ? ` ${Math.floor(i / 10) + 1}` : ''), department: ['Engineering', 'Product', 'Design', 'Analytics', 'Sales', 'HR'][i % 6], salary: 80000 + i * 4200, status: (['Active', 'Remote', 'Contractor', 'On Leave'] as const)[i % 4], @@ -57,9 +69,7 @@ export default function MobileDemo() { selector: r => r.status, center: true, cell: r => ( - - {r.status} - + {r.status} ), }, ]; @@ -119,7 +129,8 @@ export default function MobileDemo() { {isNarrow && (

- Department and Salary are hidden at this width — use omit or hide: "sm" to drop columns on small screens. + 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 9071d507..4916e2ba 100644 --- a/apps/docs/src/components/demos/MultiSortDemo.tsx +++ b/apps/docs/src/components/demos/MultiSortDemo.tsx @@ -46,8 +46,8 @@ export default function MultiSortDemo() { return (

- Ctrl-click (⌘-click on macOS) a second column header to add it to the sort. Cycle each column - ascending → descending → off. + Ctrl-click (⌘-click on macOS) a second column header to add it to the sort. Cycle each column ascending → + descending → off.

{ - try { return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}'); } - catch { return {}; } + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '{}'); + } catch { + return {}; + } } const columnDefs: TableColumn[] = [ diff --git a/apps/docs/src/components/demos/RTLDemo.tsx b/apps/docs/src/components/demos/RTLDemo.tsx index d153f6f6..19ac8e69 100644 --- a/apps/docs/src/components/demos/RTLDemo.tsx +++ b/apps/docs/src/components/demos/RTLDemo.tsx @@ -12,18 +12,39 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', nameAr: 'أريا تشن', department: 'Engineering', departmentAr: 'هندسة', salary: 155000 }, - { id: 2, name: 'Marcus Webb', nameAr: 'ماركوس ويب', department: 'Product', departmentAr: 'منتج', salary: 132000 }, - { id: 3, name: 'Priya Kapoor', nameAr: 'بريا كابور', department: 'Design', departmentAr: 'تصميم', salary: 118000 }, - { id: 4, name: 'Jordan Ellis', nameAr: 'جوردان إليس', department: 'Analytics', departmentAr: 'تحليلات', salary: 143000 }, - { id: 5, name: 'Sam Rivera', nameAr: 'سام ريفيرا', department: 'Engineering', departmentAr: 'هندسة', salary: 128000 }, - { id: 6, name: 'Taylor Brooks', nameAr: 'تايلور بروكس', department: 'Sales', departmentAr: 'مبيعات', salary: 97000 }, - { id: 7, name: 'Morgan Lee', nameAr: 'مورغان لي', department: 'Engineering', departmentAr: 'هندسة', salary: 162000 }, - { id: 8, name: 'Casey Park', nameAr: 'كيسي بارك', department: 'Design', departmentAr: 'تصميم', salary: 109000 }, - { id: 9, name: 'Drew Santos', nameAr: 'درو سانتوس', department: 'Product', departmentAr: 'منتج', salary: 138000 }, - { id: 10, name: 'Avery Johnson', nameAr: 'أفيري جونسون', department: 'Sales', departmentAr: 'مبيعات', salary: 104000 }, - { id: 11, name: 'Quinn Torres', nameAr: 'كوين توريس', department: 'Design', departmentAr: 'تصميم', salary: 109000 }, - { id: 12, name: 'Devon Walsh', nameAr: 'ديفون والش', department: 'Engineering', departmentAr: 'هندسة', salary: 134000 }, + { id: 1, name: 'Aria Chen', nameAr: 'أريا تشن', department: 'Engineering', departmentAr: 'هندسة', salary: 155000 }, + { id: 2, name: 'Marcus Webb', nameAr: 'ماركوس ويب', department: 'Product', departmentAr: 'منتج', salary: 132000 }, + { id: 3, name: 'Priya Kapoor', nameAr: 'بريا كابور', department: 'Design', departmentAr: 'تصميم', salary: 118000 }, + { + id: 4, + name: 'Jordan Ellis', + nameAr: 'جوردان إليس', + department: 'Analytics', + departmentAr: 'تحليلات', + salary: 143000, + }, + { id: 5, name: 'Sam Rivera', nameAr: 'سام ريفيرا', department: 'Engineering', departmentAr: 'هندسة', salary: 128000 }, + { id: 6, name: 'Taylor Brooks', nameAr: 'تايلور بروكس', department: 'Sales', departmentAr: 'مبيعات', salary: 97000 }, + { id: 7, name: 'Morgan Lee', nameAr: 'مورغان لي', department: 'Engineering', departmentAr: 'هندسة', salary: 162000 }, + { id: 8, name: 'Casey Park', nameAr: 'كيسي بارك', department: 'Design', departmentAr: 'تصميم', salary: 109000 }, + { id: 9, name: 'Drew Santos', nameAr: 'درو سانتوس', department: 'Product', departmentAr: 'منتج', salary: 138000 }, + { + id: 10, + name: 'Avery Johnson', + nameAr: 'أفيري جونسون', + department: 'Sales', + departmentAr: 'مبيعات', + salary: 104000, + }, + { id: 11, name: 'Quinn Torres', nameAr: 'كوين توريس', department: 'Design', departmentAr: 'تصميم', salary: 109000 }, + { + id: 12, + name: 'Devon Walsh', + nameAr: 'ديفون والش', + department: 'Engineering', + departmentAr: 'هندسة', + salary: 134000, + }, ]; const DIRECTIONS = [ @@ -39,7 +60,7 @@ export default function RTLDemo() { const columns: TableColumn[] = [ { name: isRTL ? 'الاسم' : 'Name', - selector: r => isRTL ? r.nameAr : r.name, + selector: r => (isRTL ? r.nameAr : r.name), sortable: true, width: '200px', minWidth: '120px', @@ -47,7 +68,7 @@ export default function RTLDemo() { }, { name: isRTL ? 'القسم' : 'Department', - selector: r => isRTL ? r.departmentAr : r.department, + selector: r => (isRTL ? r.departmentAr : r.department), sortable: true, minWidth: '140px', reorder: true, diff --git a/apps/docs/src/components/demos/RowGroupingDemo.tsx b/apps/docs/src/components/demos/RowGroupingDemo.tsx index 046f3a9c..4c3a12bc 100644 --- a/apps/docs/src/components/demos/RowGroupingDemo.tsx +++ b/apps/docs/src/components/demos/RowGroupingDemo.tsx @@ -16,13 +16,13 @@ interface RegionGroup { } 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: 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 }, ]; @@ -40,7 +40,7 @@ const groups: RegionGroup[] = Object.values( const columns: TableColumn[] = [ { 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: 'count', name: 'Orders', selector: r => r.count, sortable: true, right: true, width: '110px' }, { id: 'total', name: 'Total', diff --git a/apps/docs/src/components/demos/RowInteractionsDemo.tsx b/apps/docs/src/components/demos/RowInteractionsDemo.tsx index aab4f65e..9472d51f 100644 --- a/apps/docs/src/components/demos/RowInteractionsDemo.tsx +++ b/apps/docs/src/components/demos/RowInteractionsDemo.tsx @@ -11,34 +11,37 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', role: 'Engineering Lead', email: 'aria@example.com' }, - { id: 2, name: 'Marcus Webb', department: 'Product', role: 'Product Manager', email: 'marcus@example.com' }, - { id: 3, name: 'Priya Kapoor', department: 'Design', role: 'Senior Designer', email: 'priya@example.com' }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', role: 'Data Scientist', email: 'jordan@example.com' }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', role: 'DevOps Engineer', email: 'sam@example.com' }, - { id: 6, name: 'Taylor Brooks', department: 'Sales', role: 'Account Manager', email: 'taylor@example.com' }, + { id: 1, name: 'Aria Chen', department: 'Engineering', role: 'Engineering Lead', email: 'aria@example.com' }, + { id: 2, name: 'Marcus Webb', department: 'Product', role: 'Product Manager', email: 'marcus@example.com' }, + { id: 3, name: 'Priya Kapoor', department: 'Design', role: 'Senior Designer', email: 'priya@example.com' }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', role: 'Data Scientist', email: 'jordan@example.com' }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', role: 'DevOps Engineer', email: 'sam@example.com' }, + { id: 6, name: 'Taylor Brooks', department: 'Sales', role: 'Account Manager', email: 'taylor@example.com' }, ]; export function RowClickDemo() { const [selected, setSelected] = useState(null); const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Role', selector: r => r.role }, + { name: 'Role', selector: r => r.role }, ]; return (
- setSelected(row)} - /> + setSelected(row)} /> {selected && ( -
+
{selected.name} — {selected.role}, {selected.department} {selected.email}
@@ -53,9 +56,9 @@ export function RowLinkDemo() { const addLog = (msg: string) => setLog(prev => [msg, ...prev].slice(0, 4)); const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department }, - { name: 'Role', selector: r => r.role }, + { name: 'Role', selector: r => r.role }, { name: 'Email', ignoreRowClick: true, @@ -84,7 +87,9 @@ export function RowLinkDemo() { /> {log.length > 0 && (
- {log.map((l, i) =>
{l}
)} + {log.map((l, i) => ( +
{l}
+ ))}
)}

@@ -100,22 +105,37 @@ export function ActionCellDemo() { const addLog = (msg: string) => setLog(prev => [msg, ...prev].slice(0, 4)); const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Role', selector: r => r.role }, + { name: 'Role', selector: r => r.role }, { name: 'Actions', button: true, cell: row => (

- {r.name} - {pinnedIds.has(r.id) && ( - - pinned - - )} -
- ), - }, - { id: 'department', name: 'Department', selector: r => r.department, sortable: true }, - { - id: 'status', - name: 'Status', - selector: r => r.status, - cell: r => {r.status}, - sortable: true, - }, - { - id: 'salary', - name: 'Salary', - selector: r => r.salary, - format: r => `$${r.salary.toLocaleString()}`, - right: true, - sortable: true, - }, - ], [pinnedIds]); + const columns = useMemo( + (): TableColumn[] => [ + { + id: 'name', + name: 'Name', + selector: r => r.name, + sortable: true, + cell: r => ( +
+ + {r.name} + {pinnedIds.has(r.id) && ( + + pinned + + )} +
+ ), + }, + { id: 'department', name: 'Department', selector: r => r.department, sortable: true }, + { + id: 'status', + name: 'Status', + selector: r => r.status, + cell: r => {r.status}, + sortable: true, + }, + { + id: 'salary', + name: 'Salary', + selector: r => r.salary, + format: r => `$${r.salary.toLocaleString()}`, + right: true, + sortable: true, + }, + ], + [pinnedIds], + ); const sortFunction = useMemo( - () => - (rows: Employee[], selector: (r: Employee) => string | number | boolean, direction: SortOrder) => { - const pinned = rows.filter(r => pinnedIds.has(r.id)); - const rest = rows.filter(r => !pinnedIds.has(r.id)); - const sorted = [...rest].sort((a, b) => { - const av = selector(a); - const bv = selector(b); - if (av === bv) return 0; - const cmp = av > bv ? 1 : -1; - return direction === SortOrder.ASC ? cmp : -cmp; - }); - return [...pinned, ...sorted]; - }, + () => (rows: Employee[], selector: (r: Employee) => string | number | boolean, direction: SortOrder) => { + const pinned = rows.filter(r => pinnedIds.has(r.id)); + const rest = rows.filter(r => !pinnedIds.has(r.id)); + const sorted = [...rest].sort((a, b) => { + const av = selector(a); + const bv = selector(b); + if (av === bv) return 0; + const cmp = av > bv ? 1 : -1; + return direction === SortOrder.ASC ? cmp : -cmp; + }); + return [...pinned, ...sorted]; + }, [pinnedIds], ); return (

- Click the 📌 icon on any row to pin it. Pinned rows stay at the top regardless of how you sort. - Sort any column to see the behaviour. + Click the 📌 icon on any row to pin it. Pinned rows stay at the top regardless of how you sort. Sort any column + to see the behaviour.

[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, - { name: 'Role', selector: r => r.role }, + { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Role', selector: r => r.role }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Status', selector: r => r.status, + { + name: 'Status', + selector: r => r.status, cell: r => ( - {r.status} + + {r.status} + ), }, ]; @@ -48,7 +57,12 @@ export default function SelectionDemo() { Single select
diff --git a/apps/docs/src/components/demos/ServerSideSortPaginationDemo.tsx b/apps/docs/src/components/demos/ServerSideSortPaginationDemo.tsx index 0a8253c4..5ac72388 100644 --- a/apps/docs/src/components/demos/ServerSideSortPaginationDemo.tsx +++ b/apps/docs/src/components/demos/ServerSideSortPaginationDemo.tsx @@ -79,16 +79,13 @@ export default function ServerSideSortPaginationDemo() { const [sortDir, setSortDir] = useState(SortOrder.ASC); const [resetPage, setResetPage] = useState(false); - const load = useCallback( - async (params: { page: number; perPage: number; sortField: string; sortDir: SortOrder }) => { - setLoading(true); - const result = await fakeFetch(params); - setData(result.rows); - setTotal(result.total); - setLoading(false); - }, - [], - ); + const load = useCallback(async (params: { page: number; perPage: number; sortField: string; sortDir: SortOrder }) => { + setLoading(true); + const result = await fakeFetch(params); + setData(result.rows); + setTotal(result.total); + setLoading(false); + }, []); useEffect(() => { load({ page, perPage, sortField, sortDir }); diff --git a/apps/docs/src/components/demos/SortResetDemo.tsx b/apps/docs/src/components/demos/SortResetDemo.tsx index 8d57f01d..1d3524ff 100644 --- a/apps/docs/src/components/demos/SortResetDemo.tsx +++ b/apps/docs/src/components/demos/SortResetDemo.tsx @@ -24,7 +24,14 @@ const data: Employee[] = [ const columns: TableColumn[] = [ { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, { id: 'department', name: 'Department', selector: r => r.department, sortable: true }, - { id: 'salary', name: 'Salary', selector: r => r.salary, format: r => `$${r.salary.toLocaleString()}`, right: true, sortable: true }, + { + id: 'salary', + name: 'Salary', + selector: r => r.salary, + format: r => `$${r.salary.toLocaleString()}`, + right: true, + sortable: true, + }, { id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true }, ]; @@ -44,9 +51,7 @@ export default function SortResetDemo() { > Reset sort - - {lastSort ?? 'Sort by any column, then reset'} - + {lastSort ?? 'Sort by any column, then reset'}
state helpers --- -function readParams(): { sortId: string; sortDir: SortOrder; page: number; perPage: number; filters: Record } { +function readParams(): { + sortId: string; + sortDir: SortOrder; + page: number; + perPage: number; + filters: Record; +} { const p = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : ''); return { - sortId: p.get('sort') ?? '', + sortId: p.get('sort') ?? '', sortDir: p.get('dir') === 'desc' ? SortOrder.DESC : SortOrder.ASC, - page: parseInt(p.get('page') ?? '1', 10), + page: parseInt(p.get('page') ?? '1', 10), perPage: parseInt(p.get('per') ?? '5', 10), - filters: Object.fromEntries( - [...p.entries()] - .filter(([k]) => k.startsWith('f_')) - .map(([k, v]) => [k.slice(2), v]), - ), + filters: Object.fromEntries([...p.entries()].filter(([k]) => k.startsWith('f_')).map(([k, v]) => [k.slice(2), v])), }; } -function writeParams(state: { sortId: string; sortDir: SortOrder; page: number; perPage: number; filters: Record }) { +function writeParams(state: { + sortId: string; + sortDir: SortOrder; + page: number; + perPage: number; + filters: Record; +}) { const p = new URLSearchParams(); - if (state.sortId) { p.set('sort', state.sortId); p.set('dir', state.sortDir === SortOrder.DESC ? 'desc' : 'asc'); } + if (state.sortId) { + p.set('sort', state.sortId); + p.set('dir', state.sortDir === SortOrder.DESC ? 'desc' : 'asc'); + } if (state.page > 1) p.set('page', String(state.page)); if (state.perPage !== 5) p.set('per', String(state.perPage)); - Object.entries(state.filters).forEach(([k, v]) => { if (v) p.set(`f_${k}`, v); }); + Object.entries(state.filters).forEach(([k, v]) => { + if (v) p.set(`f_${k}`, v); + }); const qs = p.toString(); history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname); } @@ -56,22 +69,30 @@ function writeParams(state: { sortId: string; sortDir: SortOrder; page: number; export default function UrlSyncDemo() { const init = useMemo(() => readParams(), []); - const [sortId, setSortId] = useState(init.sortId); + const [sortId, setSortId] = useState(init.sortId); const [sortDir, setSortDir] = useState(init.sortDir); - const [page, setPage] = useState(init.page); + const [page, setPage] = useState(init.page); const [perPage, setPerPage] = useState(init.perPage); const [filters, setFilters] = useState>(() => Object.fromEntries( - Object.entries(init.filters).map(([k, v]) => [k, { value: v, operator: 'contains' as const, condition2: null, join: 'and' as const }]), + Object.entries(init.filters).map(([k, v]) => [ + k, + { value: v, operator: 'contains' as const, condition2: null, join: 'and' as const }, + ]), ), ); // Sync every state change to the URL useEffect(() => { writeParams({ - sortId, sortDir, page, perPage, + sortId, + sortDir, + page, + perPage, filters: Object.fromEntries( - Object.entries(filters).map(([k, f]) => [k, String(f.value ?? '')]).filter(([, v]) => v), + Object.entries(filters) + .map(([k, f]) => [k, String(f.value ?? '')]) + .filter(([, v]) => v), ), }); }, [sortId, sortDir, page, perPage, filters]); @@ -88,11 +109,18 @@ export default function UrlSyncDemo() { }, []); const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true, filterable: true, grow: 1 }, + { id: 'name', name: 'Name', selector: r => r.name, sortable: true, filterable: true, grow: 1 }, { id: 'department', name: 'Department', selector: r => r.department, sortable: true, filterable: true }, - { id: 'role', name: 'Role', selector: r => r.role, sortable: true, grow: 1 }, - { id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true, right: true, width: '110px', - format: r => `$${r.salary.toLocaleString()}` }, + { id: 'role', name: 'Role', selector: r => r.role, sortable: true, grow: 1 }, + { + id: 'salary', + name: 'Salary', + selector: r => r.salary, + sortable: true, + right: true, + width: '110px', + format: r => `$${r.salary.toLocaleString()}`, + }, ]; // Show current URL params so the demo is self-explanatory @@ -120,7 +148,10 @@ export default function UrlSyncDemo() { paginationDefaultPage={page} paginationPerPage={perPage} onChangePage={p => setPage(p)} - onChangeRowsPerPage={(pp, p) => { setPerPage(pp); setPage(p); }} + onChangeRowsPerPage={(pp, p) => { + setPerPage(pp); + setPage(p); + }} highlightOnHover />
diff --git a/apps/docs/src/pages/docs/animations.astro b/apps/docs/src/pages/docs/animations.astro index 4ef0db4c..416260eb 100644 --- a/apps/docs/src/pages/docs/animations.astro +++ b/apps/docs/src/pages/docs/animations.astro @@ -21,22 +21,32 @@ import AnimationsDemo from '../../components/demos/AnimationsDemo.tsx'; code={`import { useState } from 'react'; import DataTable, { type TableColumn } from 'react-data-table-component'; -interface Employee { id: number; name: string; department: string; salary: number } +interface Employee { + id: number; + name: string; + department: string; + salary: number; +} const data: Employee[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000 }, - { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000 }, - { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000 }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000 }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000 }, - { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000 }, + { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000 }, + { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000 }, + { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000 }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000 }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000 }, + { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000 }, ]; const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Salary', selector: r => r.salary, sortable: true, - format: r => \`$\${r.salary.toLocaleString()}\`, right: true }, + { + name: 'Salary', + selector: r => r.salary, + sortable: true, + format: r => \`$\${r.salary.toLocaleString()}\`, + right: true, + }, ]; export default function App() { @@ -46,17 +56,10 @@ export default function App() { return (
- +
); }`} @@ -110,7 +113,7 @@ export default function App() {

Prop reference

diff --git a/apps/docs/src/pages/docs/api.astro b/apps/docs/src/pages/docs/api.astro index 6677604f..e501f389 100644 --- a/apps/docs/src/pages/docs/api.astro +++ b/apps/docs/src/pages/docs/api.astro @@ -90,7 +90,7 @@ import PropsTable from '../../components/PropsTable.astro'; @@ -145,7 +145,7 @@ const columns: TableColumn[] = [ ['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 | number', 'Hide the column below the given breakpoint (Media.SM = 599px, MD = 959px, LG = 1280px) or a custom pixel value.'], + ['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.'], @@ -386,17 +386,17 @@ const customStyles: TableStyles = { row.status, // sort key - cell: row => , // display + selector: row => row.status, // sort key + cell: row => , // display sortable: true, } // ✅ Currency column — format preserves sort order { name: 'Salary', - selector: row => row.salary, // sort on raw number + selector: row => row.salary, // sort on raw number format: row => \`$\${row.salary.toLocaleString()}\`, // display formatted sortable: true, right: true, @@ -109,10 +109,23 @@ const columns: TableColumn[] = [ name: 'Employee', cell: row => (
-
- {row.name.split(' ').map(n => n[0]).join('')} +
+ {row.name + .split(' ') + .map(n => n[0]) + .join('')}
{row.name}
@@ -126,17 +139,21 @@ const columns: TableColumn[] = [ { name: 'Salary', selector: row => row.salary, - format: row => new Intl.NumberFormat('en-US', { - style: 'currency', currency: 'USD', maximumFractionDigits: 0, - }).format(row.salary), + format: row => + new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + maximumFractionDigits: 0, + }).format(row.salary), sortable: true, right: true, }, { name: 'Status', cell: row => ( - + {row.status} ), @@ -145,9 +162,7 @@ const columns: TableColumn[] = [ }, { name: '', - cell: row => ( - - ), + cell: row => , button: true, width: '120px', }, diff --git a/apps/docs/src/pages/docs/column-groups.astro b/apps/docs/src/pages/docs/column-groups.astro index a3dab6a4..879695b3 100644 --- a/apps/docs/src/pages/docs/column-groups.astro +++ b/apps/docs/src/pages/docs/column-groups.astro @@ -29,37 +29,42 @@ interface Employee { } const data: Employee[] = [ - { id: 1, firstName: 'Aria', lastName: 'Chen', department: 'Engineering', baseSalary: 140000, bonus: 15000 }, - { id: 2, firstName: 'Marcus', lastName: 'Webb', department: 'Product', baseSalary: 125000, bonus: 7000 }, - { id: 3, firstName: 'Priya', lastName: 'Kapoor', department: 'Design', baseSalary: 110000, bonus: 8000 }, - { id: 4, firstName: 'Jordan', lastName: 'Ellis', department: 'Analytics', baseSalary: 135000, bonus: 12000 }, + { id: 1, firstName: 'Aria', lastName: 'Chen', department: 'Engineering', baseSalary: 140000, bonus: 15000 }, + { id: 2, firstName: 'Marcus', lastName: 'Webb', department: 'Product', baseSalary: 125000, bonus: 7000 }, + { id: 3, firstName: 'Priya', lastName: 'Kapoor', department: 'Design', baseSalary: 110000, bonus: 8000 }, + { id: 4, firstName: 'Jordan', lastName: 'Ellis', department: 'Analytics', baseSalary: 135000, bonus: 12000 }, ]; // Every column that belongs to a group must have a stable \`id\` const columns: TableColumn[] = [ - { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true }, - { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true }, - { id: 'dept', name: 'Department', selector: r => r.department, sortable: true }, - { id: 'base', name: 'Base salary', selector: r => r.baseSalary, sortable: true, right: true, - format: r => \`$\${r.baseSalary.toLocaleString()}\` }, - { id: 'bonus', name: 'Bonus', selector: r => r.bonus, sortable: true, right: true, - format: r => \`$\${r.bonus.toLocaleString()}\` }, + { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true }, + { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true }, + { id: 'dept', name: 'Department', selector: r => r.department, sortable: true }, + { + id: 'base', + name: 'Base salary', + selector: r => r.baseSalary, + sortable: true, + right: true, + format: r => \`$\${r.baseSalary.toLocaleString()}\`, + }, + { + id: 'bonus', + name: 'Bonus', + selector: r => r.bonus, + sortable: true, + right: true, + format: r => \`$\${r.bonus.toLocaleString()}\`, + }, ]; const columnGroups: ColumnGroup[] = [ - { name: 'Employee', columnIds: ['first', 'last', 'dept'] }, + { name: 'Employee', columnIds: ['first', 'last', 'dept'] }, { name: 'Compensation', columnIds: ['base', 'bonus'] }, ]; export default function App() { - return ( - - ); + return ; }`} > @@ -74,8 +79,8 @@ export default function App() {

`} /> +/>;`} />

Rules

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

ColumnGroup type

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

diff --git a/apps/docs/src/pages/docs/column-pinning.astro b/apps/docs/src/pages/docs/column-pinning.astro index a2aa66ec..56df1b13 100644 --- a/apps/docs/src/pages/docs/column-pinning.astro +++ b/apps/docs/src/pages/docs/column-pinning.astro @@ -23,15 +23,15 @@ import ColumnPinningDemo from '../../components/demos/ColumnPinningDemo.tsx'; code={`import DataTable, { type TableColumn } from 'react-data-table-component'; const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, width: '180px', pinned: 'left' }, - { id: 'role', name: 'Role', selector: r => r.role, width: '220px' }, - { id: 'dept', name: 'Department', selector: r => r.department, width: '180px' }, - { id: 'loc', name: 'Location', selector: r => r.location, width: '200px' }, - { id: 'salary', name: 'Salary', selector: r => r.salary, width: '140px', right: true }, - { id: 'status', name: 'Status', selector: r => r.status, width: '120px', pinned: 'right' }, + { id: 'name', name: 'Name', selector: r => r.name, width: '180px', pinned: 'left' }, + { id: 'role', name: 'Role', selector: r => r.role, width: '220px' }, + { id: 'dept', name: 'Department', selector: r => r.department, width: '180px' }, + { id: 'loc', name: 'Location', selector: r => r.location, width: '200px' }, + { id: 'salary', name: 'Salary', selector: r => r.salary, width: '140px', right: true }, + { id: 'status', name: 'Status', selector: r => r.status, width: '120px', pinned: 'right' }, ]; -`} +;`} > @@ -84,11 +84,11 @@ const columns: TableColumn[] = [

[] = [ - { id: 'id', name: 'ID', width: '80px', pinned: 'left' }, - { id: 'name', name: 'Name', width: '180px', pinned: 'left' }, + { id: 'id', name: 'ID', width: '80px', pinned: 'left' }, + { id: 'name', name: 'Name', width: '180px', pinned: 'left' }, // ... scrolling columns ... { id: 'status', name: 'Status', width: '120px', pinned: 'right' }, - { id: 'action', name: '', width: '64px', pinned: 'right' }, + { id: 'action', name: '', width: '64px', pinned: 'right' }, ];`} />

@@ -106,11 +106,11 @@ const columns: TableColumn[] = [

+ selectableRows // checkbox column pins at 0 + expandableRows // expander column pins after the checkbox +/>; // → Name sticks at left: 96px (48px checkbox + 48px expander)`} />

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

+.rdt_table { + --rdt-system-col-width: 56px; +}`} lang="css" />

With resizable columns

@@ -129,7 +131,7 @@ const columns: TableColumn[] = [ triggers a re-render with new offsets so columns pinned further from the edge shift to match.

- `} /> + ;`} />

With fixed header

@@ -138,12 +140,7 @@ const columns: TableColumn[] = [ corner) stays above scrolling body content.

- `} /> + ;`} />

Not supported with column groups

diff --git a/apps/docs/src/pages/docs/column-reorder.astro b/apps/docs/src/pages/docs/column-reorder.astro index 833a8bd4..f62270ad 100644 --- a/apps/docs/src/pages/docs/column-reorder.astro +++ b/apps/docs/src/pages/docs/column-reorder.astro @@ -38,32 +38,32 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', salary: 155000 }, - { id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', salary: 132000 }, - { id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', salary: 118000 }, - { id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', salary: 143000 }, - { id: 5, name: 'Sam Rivera', role: 'DevOps Engineer', department: 'Engineering', salary: 128000 }, + { id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', salary: 155000 }, + { id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', salary: 132000 }, + { id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', salary: 118000 }, + { id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', salary: 143000 }, + { id: 5, name: 'Sam Rivera', role: 'DevOps Engineer', department: 'Engineering', salary: 128000 }, ]; const initialColumns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true, reorder: true }, - { id: 'role', name: 'Role', selector: r => r.role, reorder: true }, + { id: 'name', name: 'Name', selector: r => r.name, sortable: true, reorder: true }, + { id: 'role', name: 'Role', selector: r => r.role, reorder: true }, { id: 'department', name: 'Department', selector: r => r.department, sortable: true, reorder: true }, - { id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true, reorder: true, - format: r => \`$\${r.salary.toLocaleString()}\`, right: true }, + { + id: 'salary', + name: 'Salary', + selector: r => r.salary, + sortable: true, + reorder: true, + format: r => \`$\${r.salary.toLocaleString()}\`, + right: true, + }, ]; export default function App() { const [columns, setColumns] = useState(initialColumns); - return ( - - ); + return ; }`} > @@ -78,9 +78,9 @@ export default function App() {

[] = [ - { 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: '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 }, ];`} /> @@ -99,8 +99,11 @@ function applyOrder(cols: TableColumn[], order: string[]) { } function loadOrder(): string[] { - try { return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]'); } - catch { return []; } + try { + return JSON.parse(localStorage.getItem(STORAGE_KEY) ?? '[]'); + } catch { + return []; + } } export default function App() { @@ -111,9 +114,7 @@ export default function App() { localStorage.setItem(STORAGE_KEY, JSON.stringify(next.map(c => String(c.id)))); } - return ( - - ); + return ; }`} />

Reordering column groups

@@ -132,22 +133,34 @@ export default function App() { import DataTable, { type TableColumn, type ColumnGroup } from 'react-data-table-component'; const columns: TableColumn[] = [ - { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true }, - { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true }, - { id: 'dept', name: 'Department', selector: r => r.department, sortable: true }, - { id: 'base', name: 'Base salary', selector: r => r.baseSalary, sortable: true, right: true, - format: r => \`$\${r.baseSalary.toLocaleString()}\` }, - { id: 'bonus', name: 'Bonus', selector: r => r.bonus, sortable: true, right: true, - format: r => \`$\${r.bonus.toLocaleString()}\` }, + { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true }, + { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true }, + { id: 'dept', name: 'Department', selector: r => r.department, sortable: true }, + { + id: 'base', + name: 'Base salary', + selector: r => r.baseSalary, + sortable: true, + right: true, + format: r => \`$\${r.baseSalary.toLocaleString()}\`, + }, + { + id: 'bonus', + name: 'Bonus', + selector: r => r.bonus, + sortable: true, + right: true, + format: r => \`$\${r.bonus.toLocaleString()}\`, + }, ]; const initialGroups: ColumnGroup[] = [ - { name: 'Employee', columnIds: ['first', 'last', 'dept'], reorder: true }, - { name: 'Compensation', columnIds: ['base', 'bonus'], reorder: true }, + { name: 'Employee', columnIds: ['first', 'last', 'dept'], reorder: true }, + { name: 'Compensation', columnIds: ['base', 'bonus'], reorder: true }, ]; export default function App() { - const [cols, setCols] = useState(columns); + const [cols, setCols] = useState(columns); const [groups, setGroups] = useState(initialGroups); return ( @@ -175,16 +188,16 @@ export default function App() {

[] = [ - { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true, reorder: true }, - { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true, reorder: true }, - { id: 'dept', name: 'Department', selector: r => r.department, sortable: true, reorder: true }, - { id: 'base', name: 'Base salary', selector: r => r.baseSalary, sortable: true, reorder: true }, - { id: 'bonus', name: 'Bonus', selector: r => r.bonus, sortable: true, reorder: true }, + { id: 'first', name: 'First name', selector: r => r.firstName, sortable: true, reorder: true }, + { id: 'last', name: 'Last name', selector: r => r.lastName, sortable: true, reorder: true }, + { id: 'dept', name: 'Department', selector: r => r.department, sortable: true, reorder: true }, + { id: 'base', name: 'Base salary', selector: r => r.baseSalary, sortable: true, reorder: true }, + { id: 'bonus', name: 'Bonus', selector: r => r.bonus, sortable: true, reorder: true }, ]; const columnGroups: ColumnGroup[] = [ - { name: 'Employee', columnIds: ['first', 'last', 'dept'], reorder: true }, - { name: 'Compensation', columnIds: ['base', 'bonus'], reorder: true }, + { name: 'Employee', columnIds: ['first', 'last', 'dept'], reorder: true }, + { name: 'Compensation', columnIds: ['base', 'bonus'], reorder: true }, ];`} />

Prop reference

diff --git a/apps/docs/src/pages/docs/column-visibility.astro b/apps/docs/src/pages/docs/column-visibility.astro index 0d0e8372..5b79d4d7 100644 --- a/apps/docs/src/pages/docs/column-visibility.astro +++ b/apps/docs/src/pages/docs/column-visibility.astro @@ -21,11 +21,11 @@ import ColumnVisibilityDemo from '../../components/demos/ColumnVisibilityDemo.ts code={`import DataTable, { useColumnVisibility, type TableColumn } from 'react-data-table-component'; const initialColumns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, + { id: 'name', name: 'Name', selector: r => r.name, sortable: true }, { id: 'department', name: 'Department', selector: r => r.department, sortable: true }, - { id: 'role', name: 'Role', selector: r => r.role }, - { id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true }, - { id: 'location', name: 'Location', selector: r => r.location }, + { id: 'role', name: 'Role', selector: r => r.role }, + { id: 'salary', name: 'Salary', selector: r => r.salary, sortable: true }, + { id: 'location', name: 'Location', selector: r => r.location }, ]; export default function App() { @@ -47,12 +47,12 @@ export default function App() {

Hook API

[] — pass directly to DataTable, omit is managed for you - entries, // { column, visible }[] — drive your toggle UI from this + 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 + isVisible, // (columnId) => boolean — check a specific column + showAll, // () => void — make all columns visible + hideAll, // () => void — hide all columns } = useColumnVisibility(initialColumns);`} />

Column IDs are required

@@ -75,7 +75,7 @@ export default function App() { [] = [ - { id: 'name', name: 'Name', selector: r => r.name }, + { id: 'name', name: 'Name', selector: r => r.name }, { id: 'salary', name: 'Salary', selector: r => r.salary, omit: saved.includes('salary') }, ]; @@ -83,9 +83,7 @@ const { columns, entries, toggleColumn } = useColumnVisibility(initialColumns); const handleToggle = (id: string | number) => { toggleColumn(id); - const next = entries - .filter(e => (e.column.id === id ? e.visible : !e.visible)) - .map(e => e.column.id as string); + const next = entries.filter(e => (e.column.id === id ? e.visible : !e.visible)).map(e => e.column.id as string); localStorage.setItem('hiddenCols', JSON.stringify(next)); };`} /> diff --git a/apps/docs/src/pages/docs/columns.astro b/apps/docs/src/pages/docs/columns.astro index ec2b120c..f7c6a27b 100644 --- a/apps/docs/src/pages/docs/columns.astro +++ b/apps/docs/src/pages/docs/columns.astro @@ -30,42 +30,61 @@ interface Employee { } const StatusBadge = ({ status }: { status: Employee['status'] }) => ( - + {status} ); export default function App() { - const [showDept, setShowDept] = useState(true); + const [showDept, setShowDept] = useState(true); const [showSalary, setShowSalary] = useState(true); const [filterable, setFilterable] = useState(false); const columns: TableColumn[] = [ { - id: 'name', name: 'Name', selector: r => r.name, - sortable: true, filterable, // filterable toggles a filter input in the header + id: 'name', + name: 'Name', + selector: r => r.name, + sortable: true, + filterable, // filterable toggles a filter input in the header }, { - id: 'department', name: 'Department', selector: r => r.department, - sortable: true, omit: !showDept, + id: 'department', + name: 'Department', + selector: r => r.department, + sortable: true, + omit: !showDept, }, { - id: 'salary', name: 'Salary', selector: r => r.salary, - sortable: true, right: true, + id: 'salary', + name: 'Salary', + selector: r => r.salary, + sortable: true, + right: true, format: r => \`$\${r.salary.toLocaleString()}\`, omit: !showSalary, - conditionalCellStyles: [ - { when: r => r.salary > 150000, style: { fontWeight: 700 } }, - ], + conditionalCellStyles: [{ when: r => r.salary > 150000, style: { fontWeight: 700 } }], }, { - id: 'status', name: 'Status', selector: r => r.status, - center: true, cell: r => , + id: 'status', + name: 'Status', + selector: r => r.status, + center: true, + cell: r => , }, { - id: 'start', name: 'Start date', selector: r => r.startDate, + id: 'start', + name: 'Start date', + selector: r => r.startDate, sortable: true, format: r => new Date(r.startDate).toLocaleDateString(), }, @@ -136,7 +155,7 @@ const columns: TableColumn[] = [ ['button', 'boolean', 'Center content and suppress row click propagation'], ['allowOverflow', 'boolean', 'Allow content to overflow the cell (for dropdowns/popovers)'], ['ignoreRowClick', 'boolean', 'Prevent cell clicks from triggering onRowClicked'], - ['hide', 'number | "sm" | "md" | "lg"', 'Hide the column below this breakpoint'], + ['hide', '"sm" | "md" | "lg"', 'Hide the column below this breakpoint'], ['omit', 'boolean', 'Exclude column entirely (useful for toggling visibility)'], ['reorder', 'boolean', 'Allow drag-to-reorder for this column'], ['style', 'CSSProperties', 'Inline styles applied to every cell in this column'], @@ -183,11 +202,7 @@ const columns: TableColumn[] = [ `} /> +;`} />

Hiding columns

diff --git a/apps/docs/src/pages/docs/conditional-styles.astro b/apps/docs/src/pages/docs/conditional-styles.astro index 63c07a76..f29b5cea 100644 --- a/apps/docs/src/pages/docs/conditional-styles.astro +++ b/apps/docs/src/pages/docs/conditional-styles.astro @@ -32,7 +32,7 @@ const conditionalRowStyles: ConditionalStyles[] = [ const columns: TableColumn[] = [ { name: 'Company', selector: r => r.company, sortable: true, grow: 2 }, - { name: 'Rep', selector: r => r.rep, sortable: true }, + { name: 'Rep', selector: r => r.rep, sortable: true }, { name: 'Value', selector: r => r.value, @@ -41,7 +41,7 @@ const columns: TableColumn[] = [ right: true, conditionalCellStyles: [ { when: r => r.value >= 200000, style: { fontWeight: 700, color: '#15803d' } }, - { when: r => r.value < 20000, style: { color: '#9ca3af' } }, + { when: r => r.value < 20000, style: { color: '#9ca3af' } }, ], }, { @@ -56,7 +56,7 @@ const columns: TableColumn[] = [ }, ]; -`} +;`} > @@ -82,7 +82,7 @@ const conditionalRowStyles: ConditionalStyles[] = [ }, ]; -`} /> +;`} />

Style as a function

@@ -125,7 +125,7 @@ const conditionalRowStyles: ConditionalStyles[] = [ right: true, conditionalCellStyles: [ { when: r => r.value >= 200000, style: { fontWeight: 700, color: '#15803d' } }, - { when: r => r.value < 20000, style: { color: '#9ca3af' } }, + { when: r => r.value < 20000, style: { color: '#9ca3af' } }, ], }, { diff --git a/apps/docs/src/pages/docs/context-menu.astro b/apps/docs/src/pages/docs/context-menu.astro index cc52c754..770656f4 100644 --- a/apps/docs/src/pages/docs/context-menu.astro +++ b/apps/docs/src/pages/docs/context-menu.astro @@ -34,7 +34,7 @@ import RowContextMenuDemo from '../../components/demos/RowContextMenuDemo.tsx'; if (ctx.type === 'header') console.log(action.id, ctx.column); }} resizable -/>`} +/>;`} > @@ -97,9 +97,9 @@ const rowActions = (row: Task): ContextMenuAction[] => [ onContextMenuAction={(action, ctx) => { if (ctx.type !== 'row') return; if (action.id === 'delete') setRows(prev => prev.filter(r => r.id !== ctx.row.id)); - if (action.id === 'mark-done') setRows(prev => prev.map(r => r.id === ctx.row.id ? { ...r, status: 'Done' } : r)); + if (action.id === 'mark-done') setRows(prev => prev.map(r => (r.id === ctx.row.id ? { ...r, status: 'Done' } : r))); }} -/>`} +/>;`} > @@ -134,7 +134,7 @@ const rowActions = (row: Task): ContextMenuAction[] => [ if (ctx.type === 'header') console.log(action.id, ctx.column); else console.log(action.id, ctx.row, ctx.rowIndex); }} -/>`} /> +/>;`} />

The built-in ids are reserved: sort-asc, sort-desc, diff --git a/apps/docs/src/pages/docs/custom-styles.astro b/apps/docs/src/pages/docs/custom-styles.astro index b1cc7e19..904d50c5 100644 --- a/apps/docs/src/pages/docs/custom-styles.astro +++ b/apps/docs/src/pages/docs/custom-styles.astro @@ -29,26 +29,38 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, active: true }, - { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, active: true }, - { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, active: false }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, active: true }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, active: false }, - { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000, active: true }, + { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, active: true }, + { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, active: true }, + { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, active: false }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, active: true }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, active: false }, + { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000, active: true }, ]; const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Salary', selector: r => r.salary, sortable: true, - format: r => \`$\${r.salary.toLocaleString()}\`, right: true }, + { + name: 'Salary', + selector: r => r.salary, + sortable: true, + format: r => \`$\${r.salary.toLocaleString()}\`, + right: true, + }, { name: 'Status', selector: r => r.active, cell: r => ( - + {r.active ? 'Active' : 'Inactive'} ), @@ -128,7 +140,7 @@ const customStyles: TableStyles = { }, }; -`} /> +;`} />

Available style targets

@@ -179,11 +191,7 @@ const conditionalRowStyles: ConditionalStyles[] = [ }, ]; -`} /> +;`} />

CSS custom properties

diff --git a/apps/docs/src/pages/docs/expandable.astro b/apps/docs/src/pages/docs/expandable.astro index 2f4b025a..d04993e3 100644 --- a/apps/docs/src/pages/docs/expandable.astro +++ b/apps/docs/src/pages/docs/expandable.astro @@ -32,32 +32,48 @@ interface Employee { const data: Employee[] = [ { - id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', - salary: 155000, locked: false, + id: 1, + name: 'Aria Chen', + role: 'Engineering Lead', + department: 'Engineering', + salary: 155000, + locked: false, bio: 'Aria leads the platform team, focusing on reliability and developer experience.', }, { - id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', - salary: 132000, locked: true, + id: 2, + name: 'Marcus Webb', + role: 'Product Manager', + department: 'Product', + salary: 132000, + locked: true, bio: 'Marcus drives roadmap strategy across three product lines.', }, { - id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', - salary: 118000, locked: false, + id: 3, + name: 'Priya Kapoor', + role: 'Senior Designer', + department: 'Design', + salary: 118000, + locked: false, bio: 'Priya leads the design system and owns the mobile experience.', }, { - id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', - salary: 143000, locked: true, + id: 4, + name: 'Jordan Ellis', + role: 'Data Scientist', + department: 'Analytics', + salary: 143000, + locked: true, bio: 'Jordan builds ML pipelines for churn prediction and revenue forecasting.', }, ]; const columns: TableColumn[] = [ - { name: 'Name', selector: r => r.name, sortable: true }, - { name: 'Role', selector: r => r.role }, + { name: 'Name', selector: r => r.name, sortable: true }, + { name: 'Role', selector: r => r.role }, { name: 'Department', selector: r => r.department }, - { name: 'Salary', selector: r => r.salary, format: r => \`$\${r.salary.toLocaleString()}\`, right: true }, + { name: 'Salary', selector: r => r.salary, format: r => \`$\${r.salary.toLocaleString()}\`, right: true }, ]; // The expandable component receives the row as the \`data\` prop @@ -75,19 +91,18 @@ export default function App() { return (
r.locked) : undefined} + expandableRowDisabled={disableLocked ? r => r.locked : undefined} animateRows={animate} highlightOnHover /> @@ -121,14 +136,7 @@ const ExpandedRow = ({ data: row }: ExpanderComponentProps) => ( ); export default function App() { - return ( - - ); + return ; }`} />

Passing extra props to the expander

@@ -149,7 +157,7 @@ export default function App() { expandableRows expandableRowsComponent={DetailPanel} expandableRowsComponentProps={{ onDelete: handleDelete }} -/>`} /> +/>;`} />

Controlling which rows are expanded

@@ -226,7 +234,7 @@ export default function App() { onRowExpandToggled={(expanded: boolean, row: Employee) => { console.log(expanded ? 'opened' : 'closed', row.name); }} -/>`} /> +/>;`} />

Custom expander icons

@@ -237,9 +245,9 @@ export default function App() { expandableRowsComponent={ExpandedRow} expandableIcon={{ collapsed: , - expanded: , + expanded: , }} -/>`} /> +/>;`} />

Animation

@@ -247,11 +255,7 @@ export default function App() { The animation is automatically disabled when the user's OS has prefers-reduced-motion set.

- `} /> + ;`} />

Localization

@@ -263,31 +267,19 @@ export default function App() { import DataTable from 'react-data-table-component'; import { fr } from 'react-data-table-component/locales'; -`} /> +;`} /> `} /> +;`} />

Prop reference

diff --git a/apps/docs/src/pages/docs/export.astro b/apps/docs/src/pages/docs/export.astro index ebabade4..90664ece 100644 --- a/apps/docs/src/pages/docs/export.astro +++ b/apps/docs/src/pages/docs/export.astro @@ -27,13 +27,17 @@ import DocsTable from '../../components/DocsTable.astro'; code={`import { useState } from 'react'; import DataTable, { useTableExport, type TableColumn } from 'react-data-table-component'; -interface Employee { id: number; name: string; department: string; salary: number; } +interface Employee { + id: number; + name: string; + department: string; + salary: number; +} const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name }, + { id: 'name', name: 'Name', selector: r => r.name }, { id: 'department', name: 'Department', selector: r => r.department }, - { id: 'salary', name: 'Salary', selector: r => r.salary, right: true, - format: r => \`$\${r.salary.toLocaleString()}\` }, + { id: 'salary', name: 'Salary', selector: r => r.salary, right: true, format: r => \`$\${r.salary.toLocaleString()}\` }, ]; export default function App() { @@ -68,10 +72,9 @@ export default function App() { type Employee = { id: number; name: string; salary: number }; const columns: TableColumn[] = [ - { id: 'id', name: 'ID', selector: r => r.id }, - { id: 'name', name: 'Name', selector: r => r.name }, - { id: 'salary', name: 'Salary', selector: r => r.salary, - format: r => \`$\${r.salary.toLocaleString()}\` }, + { id: 'id', name: 'ID', selector: r => r.id }, + { id: 'name', name: 'Name', selector: r => r.name }, + { id: 'salary', name: 'Salary', selector: r => r.salary, format: r => \`$\${r.salary.toLocaleString()}\` }, ]; function EmployeesTable({ data }: { data: Employee[] }) { @@ -107,19 +110,14 @@ function EmployeesTable({ data }: { data: Employee[] }) { DataTable from the same hooks that feed useTableExport:

-

Override header labels

diff --git a/apps/docs/src/pages/docs/filtering.astro b/apps/docs/src/pages/docs/filtering.astro index 60440d05..e708d8ea 100644 --- a/apps/docs/src/pages/docs/filtering.astro +++ b/apps/docs/src/pages/docs/filtering.astro @@ -29,27 +29,32 @@ interface Employee { } const data: Employee[] = [ - { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, hired: '2019-03-12' }, - { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, hired: '2020-07-01' }, - { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, hired: '2021-01-15' }, - { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, hired: '2018-11-30' }, - { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, hired: '2022-04-22' }, - { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000, hired: '2023-02-08' }, - { id: 7, name: 'Morgan Lee', department: 'Engineering', salary: 162000, hired: '2017-09-05' }, - { id: 8, name: 'Casey Park', department: 'Design', salary: 109000, hired: '2022-11-19' }, - { id: 9, name: 'Drew Santos', department: 'Product', salary: 138000, hired: '2020-03-30' }, - { id: 10, name: 'Avery Johnson', department: 'Sales', salary: 104000, hired: '2021-08-14' }, + { id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, hired: '2019-03-12' }, + { id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, hired: '2020-07-01' }, + { id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, hired: '2021-01-15' }, + { id: 4, name: 'Jordan Ellis', department: 'Analytics', salary: 143000, hired: '2018-11-30' }, + { id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, hired: '2022-04-22' }, + { id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000, hired: '2023-02-08' }, + { id: 7, name: 'Morgan Lee', department: 'Engineering', salary: 162000, hired: '2017-09-05' }, + { id: 8, name: 'Casey Park', department: 'Design', salary: 109000, hired: '2022-11-19' }, + { id: 9, name: 'Drew Santos', department: 'Product', salary: 138000, hired: '2020-03-30' }, + { id: 10, name: 'Avery Johnson', department: 'Sales', salary: 104000, hired: '2021-08-14' }, ]; const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: r => r.name, sortable: true, filterable: true }, - { id: 'dept', name: 'Department', selector: r => r.department, filterable: true }, + { id: 'name', name: 'Name', selector: r => r.name, sortable: true, filterable: true }, + { id: 'dept', name: 'Department', selector: r => r.department, filterable: true }, { - id: 'salary', name: 'Salary', selector: r => r.salary, + id: 'salary', + name: 'Salary', + selector: r => r.salary, format: r => \`$\${r.salary.toLocaleString()}\`, - right: true, sortable: true, filterable: true, filterType: 'number', + right: true, + sortable: true, + filterable: true, + filterType: 'number', }, - { id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true, filterable: true, filterType: 'date' }, + { id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true, filterable: true, filterType: 'date' }, ]; export default function App() { @@ -66,11 +71,9 @@ export default function App() { pass every active filter to appear.

- [] = [ - { id: 'name', name: 'Name', selector: r => r.name, filterable: true }, -]; + [] = [{ id: 'name', name: 'Name', selector: r => r.name, filterable: true }]; -`} /> +;`} />

Filter types

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

[] = [ - { id: 'name', name: 'Name', selector: r => r.name, filterable: true }, - { id: 'score', name: 'Score', selector: r => r.score, filterable: true, filterType: 'number' }, - { id: 'dob', name: 'Birth date', selector: r => r.dob, filterable: true, filterType: 'date' }, + { id: 'name', name: 'Name', selector: r => r.name, filterable: true }, + { id: 'score', name: 'Score', selector: r => r.score, filterable: true, filterType: 'number' }, + { id: 'dob', name: 'Birth date', selector: r => r.dob, filterable: true, filterType: 'date' }, ];`} />

Apply / Clear behaviour

@@ -157,7 +160,7 @@ function App() { function handleFilterChange(columnId: string | number, filter: FilterState) { setFilterValues(prev => ({ ...prev, [columnId]: filter })); - setResetPage(v => !v); // jump back to page 1 after each filter + setResetPage(v => !v); // jump back to page 1 after each filter } return ( @@ -177,13 +180,13 @@ function App() { +isFilterActive({ condition1: { operator: 'blank' } }); // true — no value needed`} />

Localization

@@ -196,7 +199,7 @@ isFilterActive({ condition1: { operator: 'blank' } }); // true — import DataTable from 'react-data-table-component'; import { fr } from 'react-data-table-component/locales'; -`} /> +;`} /> `} /> +;`} /> `} /> +;`} />

Headless usage

diff --git a/apps/docs/src/pages/docs/fixed-header.astro b/apps/docs/src/pages/docs/fixed-header.astro index 6e242a45..b004719e 100644 --- a/apps/docs/src/pages/docs/fixed-header.astro +++ b/apps/docs/src/pages/docs/fixed-header.astro @@ -22,21 +22,26 @@ import PropsTable from '../../components/PropsTable.astro'; code={`import DataTable, { type TableColumn } from 'react-data-table-component'; const columns: TableColumn[] = [ - { name: '#', selector: r => r.id, width: '60px' }, - { name: 'Name', selector: r => r.name, sortable: true }, + { name: '#', selector: r => r.id, width: '60px' }, + { name: 'Name', selector: r => r.name, sortable: true }, { name: 'Department', selector: r => r.department, sortable: true }, - { name: 'Salary', selector: r => r.salary, sortable: true, - right: true, format: r => \`$\${r.salary.toLocaleString()}\` }, - { name: 'Status', selector: r => r.status }, + { + name: 'Salary', + selector: r => r.salary, + sortable: true, + right: true, + format: r => \`$\${r.salary.toLocaleString()}\`, + }, + { name: 'Status', selector: r => r.status }, ]; `} +/>;`} > @@ -81,7 +86,7 @@ const columns: TableColumn[] = [ -

`} /> +
;`} />

Trade-offs: features that rely on the table's own scrollport degrade. Pinned @@ -106,7 +111,7 @@ const columns: TableColumn[] = [ fixedHeaderScrollHeight="400px" pagination paginationPerPage={20} -/>`} /> +/>;`} />

With selection

@@ -114,13 +119,7 @@ const columns: TableColumn[] = [ checkbox remains accessible at the top of the scrollable area.

- `} /> + ;`} />

Tracking scroll position with onScroll

@@ -140,7 +139,7 @@ const columns: TableColumn[] = [ fixedHeader fixedHeaderScrollHeight="300px" onScroll={e => setScrollTop(Math.round((e.target as HTMLDivElement).scrollTop))} -/>`} +/>;`} > diff --git a/apps/docs/src/pages/docs/footer.astro b/apps/docs/src/pages/docs/footer.astro index 552fcd54..aa60aedb 100644 --- a/apps/docs/src/pages/docs/footer.astro +++ b/apps/docs/src/pages/docs/footer.astro @@ -80,7 +80,7 @@ const columns: TableColumn[] = [ }, ]; -`} +;`} > @@ -112,25 +112,41 @@ const columns: TableColumn[] = [ function SummaryFooter({ rows }: FooterComponentProps) { const totalSalary = rows.reduce((s, r) => s + r.salary, 0); - const totalBonus = rows.reduce((s, r) => s + r.bonus, 0); - const avgSalary = rows.length ? Math.round(totalSalary / rows.length) : 0; + const totalBonus = rows.reduce((s, r) => s + r.bonus, 0); + const avgSalary = rows.length ? Math.round(totalSalary / rows.length) : 0; return ( -

- {rows.length} employees - Salary total: \${totalSalary.toLocaleString()} - Salary avg: \${avgSalary.toLocaleString()} - Bonus total: \${totalBonus.toLocaleString()} - Total comp: \${(totalSalary + totalBonus).toLocaleString()} +
+ + {rows.length} employees + + + Salary total: \${totalSalary.toLocaleString()} + + + Salary avg: \${avgSalary.toLocaleString()} + + + Bonus total: \${totalBonus.toLocaleString()} + + + Total comp: \${(totalSalary + totalBonus).toLocaleString()} +
); } -`} +;`} > @@ -177,7 +193,7 @@ function SummaryFooter({ rows }: FooterComponentProps) { }, }; -`} /> +;`} />

All built-in themes (including their dark modes) set a background.footer value @@ -185,13 +201,17 @@ function SummaryFooter({ rows }: FooterComponentProps) { theme with createTheme, set background.footer to control it:

- + 'default', +);`} />

The CSS variable --rdt-color-footer-bg is emitted automatically when diff --git a/apps/docs/src/pages/docs/getting-started.astro b/apps/docs/src/pages/docs/getting-started.astro index 10d23922..05789935 100644 --- a/apps/docs/src/pages/docs/getting-started.astro +++ b/apps/docs/src/pages/docs/getting-started.astro @@ -24,14 +24,14 @@ import PropsTable from '../../components/PropsTable.astro'; row.name, sortable: true }, - { name: 'Role', selector: row => row.role }, - { name: 'Salary', selector: row => row.salary, right: true }, + { name: 'Name', selector: row => row.name, sortable: true }, + { name: 'Role', selector: row => row.role }, + { name: 'Salary', selector: row => row.salary, right: true }, ]; const data = [ - { id: 1, name: 'Aria Chen', role: 'Engineering Lead', salary: 155000 }, - { id: 2, name: 'Marcus Webb', role: 'Product Manager', salary: 132000 }, + { id: 1, name: 'Aria Chen', role: 'Engineering Lead', salary: 155000 }, + { id: 2, name: 'Marcus Webb', role: 'Product Manager', salary: 132000 }, ]; export default function App() { @@ -60,11 +60,7 @@ export default function App() {

`} /> +;`} />

For display-only tables (no row selection) this doesn't affect rendering. DataTable falls back @@ -88,7 +84,7 @@ interface Employee { } const columns: TableColumn[] = [ - { name: 'Name', selector: row => row.name }, + { name: 'Name', selector: row => row.name }, { name: 'Salary', selector: row => row.salary }, ];`} /> diff --git a/apps/docs/src/pages/docs/headless.astro b/apps/docs/src/pages/docs/headless.astro index 55e3df7f..627b3181 100644 --- a/apps/docs/src/pages/docs/headless.astro +++ b/apps/docs/src/pages/docs/headless.astro @@ -197,9 +197,9 @@ interface Person { } const columns: TableColumn[] = [ - { id: 'name', name: 'Name', selector: row => row.name, sortable: true }, + { id: 'name', name: 'Name', selector: row => row.name, sortable: true }, { id: 'email', name: 'Email', selector: row => row.email }, - { id: 'role', name: 'Role', selector: row => row.role }, + { id: 'role', name: 'Role', selector: row => row.role }, ]; function MyTable({ data }: { data: Person[] }) { @@ -208,11 +208,11 @@ function MyTable({ data }: { data: Person[] }) { // 1. Column order + drag state const { tableColumns, defaultSortColumn, defaultSortDirection } = useColumns( columns, - () => {}, // onColumnOrderChange - undefined, // onColumnGroupOrderChange - undefined, // columnGroups - null, // defaultSortFieldId - true, // defaultSortAsc + () => {}, // onColumnOrderChange + undefined, // onColumnGroupOrderChange + undefined, // columnGroups + null, // defaultSortFieldId + true, // defaultSortAsc ); // 2. Sort / page / selection state @@ -269,13 +269,16 @@ function MyTable({ data }: { data: Person[] }) { {tableColumns.map(col => ( col.sortable && handleSort({ - type: 'SORT_CHANGE', - selectedColumn: col, - clearSelectedOnSort: false, - additive: false, - defaultSortDirection: SortOrder.ASC, - })} + onClick={() => + col.sortable && + handleSort({ + type: 'SORT_CHANGE', + selectedColumn: col, + clearSelectedOnSort: false, + additive: false, + defaultSortDirection: SortOrder.ASC, + }) + } > {col.name} @@ -310,7 +313,7 @@ function MyTable({ data }: { data: Person[] }) { columnGroups: ColumnGroup[] | undefined, defaultSortFieldId: string | number | null | undefined, defaultSortAsc: boolean, -): ColumnsHook`} /> +): ColumnsHook;`} />

Pass undefined for onColumnGroupOrderChange and columnGroups @@ -338,7 +341,7 @@ function MyTable({ data }: { data: Person[] }) {

useTableState<T>

- (props: UseTableStateProps): UseTableStateReturn`} /> + (props: UseTableStateProps): UseTableStateReturn;`} />

Props:

@@ -411,7 +414,7 @@ function MyTable({ data }: { data: Person[] }) {

useTableData<T>

- (props: UseTableDataProps): UseTableDataReturn`} /> + (props: UseTableDataProps): UseTableDataReturn;`} /> ( columns: TableColumn[], controlledFilterValues?: Record, onFilterChange?: (columnId: string | number, filter: FilterState) => void, -): UseColumnFilterResult`} /> +): UseColumnFilterResult;`} />

Omit both optional arguments to run in uncontrolled mode (internal state). @@ -490,7 +493,7 @@ function useColumnFilter( activeCell: ActiveCell | null; handleNavFocus: (e: React.FocusEvent) => void; handleNavKeyDown: (e: React.KeyboardEvent) => void; -}`} /> +};`} /> ; handleResizeStart: (columnId: string | number, e: React.MouseEvent) => void; -}`} /> +};`} /> ( columns: TableColumn[], @@ -606,19 +609,23 @@ function getPinnedTotalWidths( selectableRows: boolean, expandableRows: boolean, expandableRowsHideExpander: boolean, -): { left: number; right: number } +): { left: number; right: number }; function getPinnedCellMeta( column: TableColumn, pinnedOffsets: PinnedOffsets | undefined, zIndex?: number, -): PinnedCellMeta`} /> +): PinnedCellMeta;`} /> ; pinnedOffsets: PinnedOffsets }) { const meta = getPinnedCellMeta(column, pinnedOffsets, 1); - return {/* ... */}; + return ( + + {/* ... */} + + ); }`} />

@@ -692,7 +699,7 @@ useEffect(() => { user sees — sorted, filtered, and paginated — rather than the raw dataset.

- (options: UseTableExportOptions): UseTableExportResult`} /> + (options: UseTableExportOptions): UseTableExportResult;`} />

Options

@@ -722,20 +729,24 @@ useEffect(() => {

Example — export the current filtered view

[] = [ - { id: 'name', name: 'Name', selector: r => r.name }, - { id: 'dept', name: 'Department', selector: r => r.department }, - { id: 'salary', name: 'Salary', selector: r => r.salary }, + { id: 'name', name: 'Name', selector: r => r.name }, + { id: 'dept', name: 'Department', selector: r => r.department }, + { id: 'salary', name: 'Salary', selector: r => r.salary }, ]; function MyTable({ data }: { data: Employee[] }) { const { tableColumns } = useColumns(columns, () => {}, undefined, undefined, null, true); - const { tableState } = useTableState({ data, keyField: 'id', /* … */ }); - const { tableRows } = useTableData({ data, columns: tableColumns, /* … */ }); + const { tableState } = useTableState({ data, keyField: 'id' /* … */ }); + const { tableRows } = useTableData({ data, columns: tableColumns /* … */ }); const { filterValues, handleFilterChange, filteredData } = useColumnFilter(tableColumns); const rows = filteredData(tableRows); diff --git a/apps/docs/src/pages/docs/inline-editing.astro b/apps/docs/src/pages/docs/inline-editing.astro index 52d804c9..32570d16 100644 --- a/apps/docs/src/pages/docs/inline-editing.astro +++ b/apps/docs/src/pages/docs/inline-editing.astro @@ -48,47 +48,56 @@ export default function App() { const handleCellEdit = (row: Employee, value: string, column: TableColumn) => { const field = column.id as keyof Employee; setData(prev => - prev.map(r => - r.id === row.id - ? { ...r, [field]: field === 'salary' ? Number(value) || r.salary : value } - : r, - ), + prev.map(r => (r.id === row.id ? { ...r, [field]: field === 'salary' ? Number(value) || r.salary : value } : r)), ); }; const columns: TableColumn[] = [ // Text editor — editable: true is shorthand for { editor: { type: 'text' } } - { id: 'name', name: 'Name', selector: r => r.name, - editable: true, onCellEdit: handleCellEdit }, + { id: 'name', name: 'Name', selector: r => r.name, editable: true, onCellEdit: handleCellEdit }, // Dropdown editor - { id: 'department', name: 'Department', selector: r => r.department, + { + id: 'department', + name: 'Department', + selector: r => r.department, editor: { type: 'select', options: [ { value: 'Engineering', label: 'Engineering' }, - { value: 'Product', label: 'Product' }, - { value: 'Design', label: 'Design' }, + { value: 'Product', label: 'Product' }, + { value: 'Design', label: 'Design' }, ], }, - onCellEdit: handleCellEdit }, + onCellEdit: handleCellEdit, + }, // Custom cell renderer + dropdown editor compose freely - { id: 'status', name: 'Status', selector: r => r.status, + { + id: 'status', + name: 'Status', + selector: r => r.status, cell: row => , editor: { type: 'select', options: [ - { value: 'Active', label: 'Active' }, - { value: 'On Leave', label: 'On Leave' }, + { value: 'Active', label: 'Active' }, + { value: 'On Leave', label: 'On Leave' }, { value: 'Terminated', label: 'Terminated' }, ], }, - onCellEdit: handleCellEdit }, + onCellEdit: handleCellEdit, + }, - { id: 'salary', name: 'Salary', selector: r => r.salary, + { + id: 'salary', + name: 'Salary', + selector: r => r.salary, format: r => \`$\${r.salary.toLocaleString()}\`, - right: true, editable: true, onCellEdit: handleCellEdit }, + right: true, + editable: true, + onCellEdit: handleCellEdit, + }, ]; return ; @@ -181,7 +190,7 @@ export default function App() { editor: { type: 'select', options: [ - { value: 'active', label: 'Active' }, + { value: 'active', label: 'Active' }, { value: 'on_leave', label: 'On Leave' }, { value: 'terminated', label: 'Terminated' }, ], @@ -248,7 +257,7 @@ export default function App() { /> @@ -261,9 +270,7 @@ export default function App() {

{ - setData(prev => - prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: value } : r)) - ); + setData(prev => prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: value } : r))); };`} />

@@ -273,16 +280,14 @@ export default function App() { { // Optimistic update - setData(prev => - prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: value } : r)) - ); + setData(prev => prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: value } : r))); try { await api.updateEmployee(row.id, { [column.id as string]: value }); } catch (err) { // Roll back on failure setData(prev => - prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: row[column.id as keyof typeof row] } : r)) + prev.map(r => (r.id === row.id ? { ...r, [column.id as string]: row[column.id as keyof typeof row] } : r)), ); console.error('Save failed:', err); } @@ -328,9 +333,8 @@ export default function App() { [] = [ - { id: 'name', name: 'Name', selector: r => r.name }, // never editable - { id: 'salary', name: 'Salary', selector: r => r.salary, - editable: canEdit, onCellEdit: handleCellEdit }, // editable only for admins + { id: 'name', name: 'Name', selector: r => r.name }, // never editable + { id: 'salary', name: 'Salary', selector: r => r.salary, editable: canEdit, onCellEdit: handleCellEdit }, // editable only for admins ];`} />

Combining with custom cell renderers

@@ -347,8 +351,8 @@ const columns: TableColumn[] = [ editor: { type: 'select', options: [ - { value: 'Active', label: 'Active' }, - { value: 'On Leave', label: 'On Leave' }, + { value: 'Active', label: 'Active' }, + { value: 'On Leave', label: 'On Leave' }, { value: 'Terminated', label: 'Terminated' }, ], }, @@ -408,7 +412,7 @@ const columns: TableColumn[] = [ covers header, selection, and expander cells, and works on read-only tables.

- `} /> + ;`} />

Limitations