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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,38 @@ jobs:

- name: Get new version
id: version
run: echo "value=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
run: |
VERSION=$(node -p "require('./package.json').version")
echo "value=$VERSION" >> $GITHUB_OUTPUT
echo "Bumped to $VERSION"

- name: Build
run: npm run build

- name: Check version not already published
run: |
VERSION="${{ steps.version.outputs.value }}"
if npm view react-data-table-component@$VERSION version 2>/dev/null; then
echo "Error: v$VERSION is already published to npm"
exit 1
fi

- name: Publish to npm
run: npm publish --provenance --access public --ignore-scripts

- name: Commit and tag
run: |
git add package.json
git add package.json CHANGELOG.md
git commit -m "chore: release v${{ steps.version.outputs.value }} [skip ci]"
git tag "v${{ steps.version.outputs.value }}"
git push origin HEAD:master --follow-tags

- name: Generate release notes
- name: Extract release notes from CHANGELOG.md
id: notes
run: |
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
if [ -z "$PREV_TAG" ]; then
NOTES=$(git log --pretty=format:"- %s" | head -30)
else
NOTES=$(git log "$PREV_TAG"..HEAD~1 --pretty=format:"- %s")
fi
VERSION="${{ steps.version.outputs.value }}"
# Extract everything between the ## X.Y.Z heading and the next --- or ## heading
NOTES=$(awk "/^## ${VERSION}$/{found=1; next} found && /^(---|## )/{exit} found{print}" CHANGELOG.md)
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
Expand All @@ -101,9 +109,7 @@ jobs:
tag_name: v${{ steps.version.outputs.value }}
name: v${{ steps.version.outputs.value }}
body: |
## What's Changed

${{ steps.notes.outputs.content }}

**Full changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version.outputs.value }}...v${{ steps.version.outputs.value }}
**Full changelog**: https://reactdatatable.com/docs/changelog
generate_release_notes: false
4 changes: 4 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"MD013": false,
"MD024": { "siblings_only": true }
}
120 changes: 120 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Changelog

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

### New features

- **Removable sorting** — clicking a sorted header now cycles asc → desc → unsorted, so a sort can be cleared without reloading the page. → [Sorting docs](/docs/sorting#removable-sorting)
- **Multi-column sorting** — new `sortMulti` prop. Ctrl/⌘-click headers to build a sort stack; priority follows click order and a numbered badge marks each sorted column. → [Sorting docs](/docs/sorting#multi-column-sorting) ([#1325](https://github.com/jbetancur/react-data-table-component/pulls/1325))
- `SortColumn<T>` type exported — represents a single entry in the sort stack (`{ column, sortDirection }`).
- `onSort` gains a fourth `sortColumns: SortColumn<T>[]` argument with the full sort config. Existing three-argument handlers are unaffected.

### Behavior changes

- A third click on a sorted header now removes the sort (previously it stayed on descending). Server-side `onSort` handlers should treat an empty `sortColumns` array as "no sort" and drop their `ORDER BY`.

---

## 8.3.0

### New features

- **Localization** — new `localization` prop replaces the three separate option props (`columnFilterOptions`, `expandableRowsOptions`, and pagination aria-label fields on `paginationComponentOptions`). Pass a single object to translate every string and aria-label in the table — filter panel, pagination navigation, and expand/collapse buttons. → [Localization docs](/docs/localization)
- **Built-in locales** — import pre-built translations from the `react-data-table-component/locales` subpath. Ships with: English (`en`), French (`fr`), Spanish (`es`), German (`de`), Brazilian Portuguese (`ptBR`), Arabic — Modern Standard (`ar`), Egyptian (`arEG`), Levantine (`arLV`), Hebrew (`he`), Chinese Simplified (`zhCN`), Chinese Traditional (`zhTW`), Japanese (`ja`), Korean (`ko`), Ukrainian (`uk`). Each locale is individually tree-shakeable.
- New utility exports: `emptyFilterState(type)` and `isFilterActive(filter)`. → [Filtering docs](/docs/filtering#utility-exports)
- **Removable sorting** — clicking a sortable header now cycles ascending → descending → unsorted, so a sort can be cleared directly from the header. → [Sorting docs](/docs/sorting#removable-sorting)
- **Multi-column sorting** — new `sortMulti` prop. Ctrl/⌘-click a header to add it to the existing sort; priority follows click order and a numbered badge marks each sorted column. `onSort` gains a fourth `sortColumns` argument with the full sort config, and the new `SortColumn<T>` type is exported. → [Sorting docs](/docs/sorting#multi-column-sorting)

### Behavior changes

- A third click on a sorted header now *removes* the sort (previously it stayed descending). When the sort is cleared, `onSort` fires with an empty primary column and an empty `sortColumns` array — server-side handlers should treat this as "no sort" and drop their `ORDER BY`. The existing three-argument `onSort` usage is unaffected; the fourth argument is additive.

### Deprecations

The following will continue to work in 8.x but will be removed in v9. TypeScript will show a deprecation hint.

- `columnFilterOptions` prop — use `localization` with a `filter` key instead.
- `expandableRowsOptions` prop — use `localization` with an `expandable` key instead.
- Pagination aria-label fields on `paginationComponentOptions` (`navigationAriaLabel`, `firstPageAriaLabel`, `previousPageAriaLabel`, `nextPageAriaLabel`, `lastPageAriaLabel`) — use `localization` with a `pagination` key instead.
- `ColumnFilterOptions` and `ExpandableRowsOptions` types — use `Localization['filter']` and `Localization['expandable']` instead.

---

## 8.2.0

### New features

- `paginationPosition` — controls where the pagination bar renders. Accepts `'bottom'` (default), `'top'`, or `'both'`. → [Pagination docs](/docs/pagination#pagination-position)
- `paginationPage` — controlled active-page prop. Set it to navigate programmatically (e.g. reset to page 1 after a filter change). Use with `onChangePage` to keep in sync. → [API reference](/docs/api#pagination)
- Built-in **footer row** for totals, averages, and other summary cells. Declare per-column with the new `footer` field (`ReactNode` or `(rows) => ReactNode`) or replace the whole row with `footerComponent`. Footer cells respect column widths, alignment, and pinning automatically. → [Footer docs](/docs/footer)
- **Pagination button aria-labels** — "First Page", "Previous Page", "Next Page", and "Last Page" are now configurable via `paginationComponentOptions`, enabling proper i18n for screen readers.
- `ref.clearSort()` — new `DataTableHandle` method to programmatically reset sort back to its default state, or unsorted if no defaults are set. → [Sorting docs](/docs/sorting#resetting-sort-programmatically)
- **Sortable column indicator** — sortable columns now show a faint sort icon at reduced opacity so users can discover which columns are sortable before clicking. The inactive opacity is themable via `--rdt-sort-icon-inactive-opacity` (default `0.3`).
- `onScroll` — new prop that fires whenever the table's scroll wrapper scrolls. Receives the native `React.UIEvent<HTMLDivElement>`.

### Bug fixes

- Fixed column reordering bypassing `reorder={false}` when a cell's text was selected via double-click and then dragged.

---

## 8.1.0

### New features

- Inline editing now supports `number`, `date`, `checkbox`, and `custom` editor types. New column-level `validate` hook gates the edit before `onCellEdit` fires. → [Inline editing](/docs/inline-editing)
- Shift-click range selection on row checkboxes. Enabled by default — opt out with `selectableRowsRange={false}`. New `selectedRows` prop drives controlled selection. → [Row selection](/docs/selection)
- New headless export hook `useTableExport`: build CSV/JSON, trigger a download, or copy to clipboard. → [Export](/docs/export)

### Bug fixes & polish

- Expandable row open/close animation now works correctly. Switched from a `max-height` tween to the CSS grid `grid-template-rows: 0fr → 1fr` trick. Close animation added — the row stays mounted while animating out, then unmounts. Both directions respect `animateRows` and `prefers-reduced-motion`.
- Fixed `useLayoutEffect` SSR warning in Next.js App Router and Astro SSR modes.
- Inline editing: added CSS for `checkbox` and `custom` editor types, and validation error tooltip styles (`.rdt_cellEditError`, `.rdt_editErrorTip`).

---

## v8

v8 is a full rewrite around a headless hook architecture. Every major feature is composable and usable independently of `<DataTable>`. See the [migration guide](/docs/migration) for breaking changes from v7.

### Headline features

- Column pinning (`pinned: 'left' | 'right'`) with cascading sticky offsets, custom pinned scrollbar, and full compatibility with resize/reorder/fixed-header.
- Inline cell editing (`editable` + `onCellEdit`).
- Per-column filtering with structured operators (`filterable`, `filterType`, controlled `filterValues`).
- Column groups (`columnGroups`): span labels across adjacent columns. Drag-to-reorder entire groups as a unit.
- Drag-to-reorder columns and column groups (`reorder: true`, `onColumnOrderChange`).
- Column visibility hook (`useColumnVisibility`) for show/hide pickers.
- Resizable columns (`resizable`): handle straddles the column boundary, 6 px hit area, 40 px hard floor.
- Column separators: `columnSeparator` and `headerSeparator` with `"subtle"` / `"full"` variants.
- Row animations (`animateRows`): staggered entrance + sort transitions, respects `prefers-reduced-motion`.
- Improved loading state: skeleton on first load, dimmed overlay + spinner on refetch.
- Imperative ref API: `ref.current?.clearSelectedRows()`. The `clearSelectedRows` prop is deprecated.
- New row events: `onRowMiddleClicked`, `onRowMouseEnter`, `onRowMouseLeave`.
- Dark mode support via `colorMode` ("light", "dark", "auto").
- Headless hooks exported: `useColumns`, `useTableState`, `useTableData`, `useColumnFilter`, `useColumnVisibility`.
- New theme: `crisp`. Refactored theme system around CSS variables (`createTheme`).

### Architecture changes

- Replaced styled-components with CSS variables and a single stylesheet. No more runtime CS -in-JS. Smaller bundle, faster first render.
- All visual defaults live in `DataTable.css` as `--rdt-*` custom properties.
- Row separators are drawn at the cell level (`.rdt_cellBase`) instead of the row container. Fixes a long-standing issue where the separator scrolled with content past pinned columns.
- System column width (checkbox/expander) is now controlled by `--rdt-system-col-width`. Themes can override it and pinning offsets stay aligned.

### Breaking changes from v7

- v8 ships its own CSS. No `import 'react-data-table-component/dist/index.css'` required, and styled-components overrides will not apply. Use the new theme system or `customStyles`.
- `clearSelectedRows` prop deprecated in favor of `ref.current.clearSelectedRows()`.
- Several renamed props and removed legacy options. See [migration guide](/docs/migration).

---

## v7 (legacy)

v7 is no longer actively developed. Docs remain at [v7.reactdatatable.com](https://v7.reactdatatable.com).

If you're upgrading, the [migration guide](/docs/migration) covers the breaking changes and rename mappings.
116 changes: 116 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# react-data-table-component — Claude instructions

## Project layout

- `src/` — library source (TypeScript). Built with `tsup` → `dist/`.
- `apps/docs/` — Astro docs site served at reactdatatable.com. Imports the library directly from `src/` in dev via a Vite alias.
- `CHANGELOG.md` — single source of truth for release notes. Feeds both the docs changelog page and GitHub Release bodies.

## Commands

```sh
npm run lint # eslint src/
npm run typecheck # tsc --noEmit
npm test # vitest run
npm run build # tsup (library only)
npm run docs # tsup --watch + astro dev (concurrent)
npm run docs:build # astro build + pagefind index
```

Always run `npm run lint && npm run typecheck && npm test` before considering a change complete.

---

## Adding a new feature

1. Implement in `src/`. Export any new public types from `src/index.ts`.
2. Add or update unit tests — reducer logic in `src/__tests__/tableReducer.test.ts`, util functions in `src/__tests__/util.test.ts`, component behaviour in `src/__tests__/DataTable.test.tsx`.
3. Update the docs (see below).
4. Add a changelog entry (see below).

---

## Updating the docs

The docs live in `apps/docs/src/pages/docs/`. Each feature has its own `.astro` page.

**Structure of a docs page:**

- Start with a plain-language explanation of what the feature does and when to use it.
- Show a live demo via a `<Demo>` component wrapping a `.tsx` island in `apps/docs/src/components/demos/`.
- Follow with reference code blocks for common patterns.
- End with a prop reference table.

**Demo components** (`apps/docs/src/components/demos/`):

- Import `DataTable` from `../ThemedDataTable` (not directly from the library) so the demo respects the docs theme switcher.
- Keep demo data self-contained in the file.
- Show a live readout of relevant state (e.g. current sort, selected rows) so the demo is self-explanatory without running it.

**When adding a new prop or changing an existing one:**

- Update the prop reference table on the relevant docs page.
- Update the type signature in the API reference at `apps/docs/src/pages/docs/api.md`.

**Nav and routing:**

- Add new pages to the sidebar in `apps/docs/src/layouts/DocsLayout.astro`.
- If removing a page, add a `[[redirects]]` entry in `netlify.toml` pointing the old URL to its replacement.

---

## Updating CHANGELOG.md

`CHANGELOG.md` at the repo root is the single source of truth. Do not edit `apps/docs/src/pages/docs/changelog.astro` — it just renders the root file.

**Format for a new release section** (add above the previous release, before the `---` divider):

```markdown
## X.Y.Z

### New features

- **Feature name** — one-sentence description. → [Relevant docs](/docs/page)

### Behavior changes

- Description of anything that changes existing behaviour, and what consumers need to do.

### Bug fixes

- Description of what was broken and what changed.

### Deprecations

- `propName` — what to use instead.

---
```

Rules:

- One `## X.Y.Z` heading per release. The release workflow's `awk` extractor keys on this exact format — `##` followed by the bare version number, nothing else on the line.
- Keep entries terse — one bullet per item. Link to the relevant docs page where useful.
- Add the section **before** triggering the release workflow. The workflow reads it at release time; if the section is missing the GitHub Release body will be empty.

---

## Release process

Releases are triggered manually via the **Release** GitHub Actions workflow (`workflow_dispatch`). Before triggering:

1. Make sure `CHANGELOG.md` has a `## X.Y.Z` section for the new version (the version the bump will produce — patch/minor/major of the current `package.json` version).
2. Ensure all changes are committed and CI is green on master.
3. Go to **Actions → Release → Run workflow**, choose the bump type (patch / minor / major), and run.

The workflow will:

- Lint, typecheck, test, and build the library.
- Bump `package.json` version.
- Build the library dist.
- Publish to npm.
- Commit `package.json` + `CHANGELOG.md` and push a version tag to master.
- Extract the matching `## X.Y.Z` section from `CHANGELOG.md` and post it as the GitHub Release body.
- The master push triggers Netlify, which rebuilds and redeploys the docs automatically.

**Common mistake:** triggering the release before writing the changelog entry. The workflow does not write changelog entries — that is always a human (or AI-assisted) step done on master beforehand.
8 changes: 8 additions & 0 deletions apps/docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ export default defineConfig({
alias: {
// In dev, resolve the library from local source so edits are instant
'react-data-table-component': new URL('../../src/index.ts', import.meta.url).pathname,
// Allow importing the root CHANGELOG.md as an Astro markdown module
'~changelog': new URL('../../CHANGELOG.md', import.meta.url).pathname,
},
},
server: {
fs: {
// Allow Vite to serve files from the monorepo root
allow: [new URL('../..', import.meta.url).pathname],
},
},
},
Expand Down
63 changes: 63 additions & 0 deletions apps/docs/src/components/demos/MultiSortDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useState } from 'react';
import DataTable from '../ThemedDataTable';
import { type TableColumn, type SortColumn, SortOrder } from 'react-data-table-component';

interface Employee {
id: number;
name: string;
department: string;
salary: number;
hired: string;
}

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: 'Engineering', 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' },
];

const columns: TableColumn<Employee>[] = [
{ 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: 'hired', name: 'Hired', selector: r => r.hired, sortable: true },
];

export default function MultiSortDemo() {
const [sortColumns, setSortColumns] = useState<SortColumn<Employee>[]>([]);

const summary =
sortColumns.length === 0
? 'No sort — click a header to sort, click a third time to remove it'
: sortColumns.map((s, i) => `${i + 1}. ${String(s.column.id)} ${s.sortDirection}`).join(' · ');

return (
<div className="space-y-3">
<p className="text-xs text-gray-500">
Ctrl-click (⌘-click on macOS) a second column header to add it to the sort. Cycle each column
ascending → descending → off.
</p>
<DataTable
columns={columns}
data={data}
sortMulti
defaultSortFieldId="department"
onSort={(_col, _dir, _rows, next) => setSortColumns(next as SortColumn<Employee>[])}
highlightOnHover
/>
<span className="block text-xs text-gray-500 font-mono">{summary}</span>
</div>
);
}
3 changes: 1 addition & 2 deletions apps/docs/src/layouts/DocsLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const nav = [
links: [
{ label: 'Getting Started', href: '/docs/getting-started' },
{ label: 'Installation', href: '/docs/installation' },
{ label: "What's New in v8", href: '/docs/whats-new' },
{ label: 'Changelog', href: '/docs/changelog' },
],
},
{
Expand Down Expand Up @@ -103,7 +103,6 @@ const nav = [
links: [
{ label: 'API Reference', href: '/docs/api' },
{ label: 'Migration Guide', href: '/docs/migration' },
{ label: 'Changelog', href: '/docs/changelog' },
],
},
];
Expand Down
Loading
Loading