Skip to content

Commit 52277a1

Browse files
authored
feat: implement multi-column sorting functionality (jbetancur#1325)
- Added support for multi-column sorting in the DataTable component. - Updated the sorting logic to handle multiple sort columns and their respective directions. - Enhanced the table reducer to manage sort states for multiple columns. - Introduced a new `SortColumn` type to represent individual sort configurations. - Modified the `onSort` callback to include the current sort configuration. - Updated tests to cover new multi-sort behavior and edge cases. - Enhanced styling for sort priority indicators in the table headers.
1 parent 9cb5053 commit 52277a1

28 files changed

Lines changed: 957 additions & 558 deletions

.github/workflows/release.yml

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -77,20 +77,17 @@ jobs:
7777

7878
- name: Commit and tag
7979
run: |
80-
git add package.json
80+
git add package.json CHANGELOG.md
8181
git commit -m "chore: release v${{ steps.version.outputs.value }} [skip ci]"
8282
git tag "v${{ steps.version.outputs.value }}"
8383
git push origin HEAD:master --follow-tags
8484
85-
- name: Generate release notes
85+
- name: Extract release notes from CHANGELOG.md
8686
id: notes
8787
run: |
88-
PREV_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")
89-
if [ -z "$PREV_TAG" ]; then
90-
NOTES=$(git log --pretty=format:"- %s" | head -30)
91-
else
92-
NOTES=$(git log "$PREV_TAG"..HEAD~1 --pretty=format:"- %s")
93-
fi
88+
VERSION="${{ steps.version.outputs.value }}"
89+
# Extract everything between the ## X.Y.Z heading and the next --- or ## heading
90+
NOTES=$(awk "/^## ${VERSION}$/{found=1; next} found && /^(---|## )/{exit} found{print}" CHANGELOG.md)
9491
echo "content<<EOF" >> $GITHUB_OUTPUT
9592
echo "$NOTES" >> $GITHUB_OUTPUT
9693
echo "EOF" >> $GITHUB_OUTPUT
@@ -101,9 +98,7 @@ jobs:
10198
tag_name: v${{ steps.version.outputs.value }}
10299
name: v${{ steps.version.outputs.value }}
103100
body: |
104-
## What's Changed
105-
106101
${{ steps.notes.outputs.content }}
107102
108-
**Full changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version.outputs.value }}...v${{ steps.version.outputs.value }}
103+
**Full changelog**: https://reactdatatable.com/docs/changelog
109104
generate_release_notes: false

.markdownlint.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"MD013": false,
3+
"MD024": { "siblings_only": true }
4+
}

CHANGELOG.md

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Changelog
2+
3+
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).
4+
5+
## 8.4.0
6+
7+
### New features
8+
9+
- **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)
10+
- **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))
11+
- `SortColumn<T>` type exported — represents a single entry in the sort stack (`{ column, sortDirection }`).
12+
- `onSort` gains a fourth `sortColumns: SortColumn<T>[]` argument with the full sort config. Existing three-argument handlers are unaffected.
13+
14+
### Behavior changes
15+
16+
- 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`.
17+
18+
---
19+
20+
## 8.3.0
21+
22+
### New features
23+
24+
- **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)
25+
- **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.
26+
- New utility exports: `emptyFilterState(type)` and `isFilterActive(filter)`. → [Filtering docs](/docs/filtering#utility-exports)
27+
- **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)
28+
- **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)
29+
30+
### Behavior changes
31+
32+
- 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.
33+
34+
### Deprecations
35+
36+
The following will continue to work in 8.x but will be removed in v9. TypeScript will show a deprecation hint.
37+
38+
- `columnFilterOptions` prop — use `localization` with a `filter` key instead.
39+
- `expandableRowsOptions` prop — use `localization` with an `expandable` key instead.
40+
- Pagination aria-label fields on `paginationComponentOptions` (`navigationAriaLabel`, `firstPageAriaLabel`, `previousPageAriaLabel`, `nextPageAriaLabel`, `lastPageAriaLabel`) — use `localization` with a `pagination` key instead.
41+
- `ColumnFilterOptions` and `ExpandableRowsOptions` types — use `Localization['filter']` and `Localization['expandable']` instead.
42+
43+
---
44+
45+
## 8.2.0
46+
47+
### New features
48+
49+
- `paginationPosition` — controls where the pagination bar renders. Accepts `'bottom'` (default), `'top'`, or `'both'`. → [Pagination docs](/docs/pagination#pagination-position)
50+
- `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)
51+
- 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)
52+
- **Pagination button aria-labels** — "First Page", "Previous Page", "Next Page", and "Last Page" are now configurable via `paginationComponentOptions`, enabling proper i18n for screen readers.
53+
- `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)
54+
- **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`).
55+
- `onScroll` — new prop that fires whenever the table's scroll wrapper scrolls. Receives the native `React.UIEvent<HTMLDivElement>`.
56+
57+
### Bug fixes
58+
59+
- Fixed column reordering bypassing `reorder={false}` when a cell's text was selected via double-click and then dragged.
60+
61+
---
62+
63+
## 8.1.0
64+
65+
### New features
66+
67+
- 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)
68+
- 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)
69+
- New headless export hook `useTableExport`: build CSV/JSON, trigger a download, or copy to clipboard. → [Export](/docs/export)
70+
71+
### Bug fixes & polish
72+
73+
- 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`.
74+
- Fixed `useLayoutEffect` SSR warning in Next.js App Router and Astro SSR modes.
75+
- Inline editing: added CSS for `checkbox` and `custom` editor types, and validation error tooltip styles (`.rdt_cellEditError`, `.rdt_editErrorTip`).
76+
77+
---
78+
79+
## v8
80+
81+
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.
82+
83+
### Headline features
84+
85+
- Column pinning (`pinned: 'left' | 'right'`) with cascading sticky offsets, custom pinned scrollbar, and full compatibility with resize/reorder/fixed-header.
86+
- Inline cell editing (`editable` + `onCellEdit`).
87+
- Per-column filtering with structured operators (`filterable`, `filterType`, controlled `filterValues`).
88+
- Column groups (`columnGroups`): span labels across adjacent columns. Drag-to-reorder entire groups as a unit.
89+
- Drag-to-reorder columns and column groups (`reorder: true`, `onColumnOrderChange`).
90+
- Column visibility hook (`useColumnVisibility`) for show/hide pickers.
91+
- Resizable columns (`resizable`): handle straddles the column boundary, 6 px hit area, 40 px hard floor.
92+
- Column separators: `columnSeparator` and `headerSeparator` with `"subtle"` / `"full"` variants.
93+
- Row animations (`animateRows`): staggered entrance + sort transitions, respects `prefers-reduced-motion`.
94+
- Improved loading state: skeleton on first load, dimmed overlay + spinner on refetch.
95+
- Imperative ref API: `ref.current?.clearSelectedRows()`. The `clearSelectedRows` prop is deprecated.
96+
- New row events: `onRowMiddleClicked`, `onRowMouseEnter`, `onRowMouseLeave`.
97+
- Dark mode support via `colorMode` ("light", "dark", "auto").
98+
- Headless hooks exported: `useColumns`, `useTableState`, `useTableData`, `useColumnFilter`, `useColumnVisibility`.
99+
- New theme: `crisp`. Refactored theme system around CSS variables (`createTheme`).
100+
101+
### Architecture changes
102+
103+
- Replaced styled-components with CSS variables and a single stylesheet. No more runtime CS -in-JS. Smaller bundle, faster first render.
104+
- All visual defaults live in `DataTable.css` as `--rdt-*` custom properties.
105+
- 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.
106+
- System column width (checkbox/expander) is now controlled by `--rdt-system-col-width`. Themes can override it and pinning offsets stay aligned.
107+
108+
### Breaking changes from v7
109+
110+
- 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`.
111+
- `clearSelectedRows` prop deprecated in favor of `ref.current.clearSelectedRows()`.
112+
- Several renamed props and removed legacy options. See [migration guide](/docs/migration).
113+
114+
---
115+
116+
## v7 (legacy)
117+
118+
v7 is no longer actively developed. Docs remain at [v7.reactdatatable.com](https://v7.reactdatatable.com).
119+
120+
If you're upgrading, the [migration guide](/docs/migration) covers the breaking changes and rename mappings.

CLAUDE.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# react-data-table-component — Claude instructions
2+
3+
## Project layout
4+
5+
- `src/` — library source (TypeScript). Built with `tsup``dist/`.
6+
- `apps/docs/` — Astro docs site served at reactdatatable.com. Imports the library directly from `src/` in dev via a Vite alias.
7+
- `CHANGELOG.md` — single source of truth for release notes. Feeds both the docs changelog page and GitHub Release bodies.
8+
9+
## Commands
10+
11+
```sh
12+
npm run lint # eslint src/
13+
npm run typecheck # tsc --noEmit
14+
npm test # vitest run
15+
npm run build # tsup (library only)
16+
npm run docs # tsup --watch + astro dev (concurrent)
17+
npm run docs:build # astro build + pagefind index
18+
```
19+
20+
Always run `npm run lint && npm run typecheck && npm test` before considering a change complete.
21+
22+
---
23+
24+
## Adding a new feature
25+
26+
1. Implement in `src/`. Export any new public types from `src/index.ts`.
27+
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`.
28+
3. Update the docs (see below).
29+
4. Add a changelog entry (see below).
30+
31+
---
32+
33+
## Updating the docs
34+
35+
The docs live in `apps/docs/src/pages/docs/`. Each feature has its own `.astro` page.
36+
37+
**Structure of a docs page:**
38+
39+
- Start with a plain-language explanation of what the feature does and when to use it.
40+
- Show a live demo via a `<Demo>` component wrapping a `.tsx` island in `apps/docs/src/components/demos/`.
41+
- Follow with reference code blocks for common patterns.
42+
- End with a prop reference table.
43+
44+
**Demo components** (`apps/docs/src/components/demos/`):
45+
46+
- Import `DataTable` from `../ThemedDataTable` (not directly from the library) so the demo respects the docs theme switcher.
47+
- Keep demo data self-contained in the file.
48+
- Show a live readout of relevant state (e.g. current sort, selected rows) so the demo is self-explanatory without running it.
49+
50+
**When adding a new prop or changing an existing one:**
51+
52+
- Update the prop reference table on the relevant docs page.
53+
- Update the type signature in the API reference at `apps/docs/src/pages/docs/api.md`.
54+
55+
**Nav and routing:**
56+
57+
- Add new pages to the sidebar in `apps/docs/src/layouts/DocsLayout.astro`.
58+
- If removing a page, add a `[[redirects]]` entry in `netlify.toml` pointing the old URL to its replacement.
59+
60+
---
61+
62+
## Updating CHANGELOG.md
63+
64+
`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.
65+
66+
**Format for a new release section** (add above the previous release, before the `---` divider):
67+
68+
```markdown
69+
## X.Y.Z
70+
71+
### New features
72+
73+
- **Feature name** — one-sentence description. → [Relevant docs](/docs/page)
74+
75+
### Behavior changes
76+
77+
- Description of anything that changes existing behaviour, and what consumers need to do.
78+
79+
### Bug fixes
80+
81+
- Description of what was broken and what changed.
82+
83+
### Deprecations
84+
85+
- `propName` — what to use instead.
86+
87+
---
88+
```
89+
90+
Rules:
91+
92+
- 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.
93+
- Keep entries terse — one bullet per item. Link to the relevant docs page where useful.
94+
- 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.
95+
96+
---
97+
98+
## Release process
99+
100+
Releases are triggered manually via the **Release** GitHub Actions workflow (`workflow_dispatch`). Before triggering:
101+
102+
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).
103+
2. Ensure all changes are committed and CI is green on master.
104+
3. Go to **Actions → Release → Run workflow**, choose the bump type (patch / minor / major), and run.
105+
106+
The workflow will:
107+
108+
- Lint, typecheck, test, and build the library.
109+
- Bump `package.json` version.
110+
- Build the library dist.
111+
- Publish to npm.
112+
- Commit `package.json` + `CHANGELOG.md` and push a version tag to master.
113+
- Extract the matching `## X.Y.Z` section from `CHANGELOG.md` and post it as the GitHub Release body.
114+
- The master push triggers Netlify, which rebuilds and redeploys the docs automatically.
115+
116+
**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.

apps/docs/astro.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ export default defineConfig({
1414
alias: {
1515
// In dev, resolve the library from local source so edits are instant
1616
'react-data-table-component': new URL('../../src/index.ts', import.meta.url).pathname,
17+
// Allow importing the root CHANGELOG.md as an Astro markdown module
18+
'~changelog': new URL('../../CHANGELOG.md', import.meta.url).pathname,
19+
},
20+
},
21+
server: {
22+
fs: {
23+
// Allow Vite to serve files from the monorepo root
24+
allow: [new URL('../..', import.meta.url).pathname],
1725
},
1826
},
1927
},
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { useState } from 'react';
2+
import DataTable from '../ThemedDataTable';
3+
import { type TableColumn, type SortColumn, SortOrder } from 'react-data-table-component';
4+
5+
interface Employee {
6+
id: number;
7+
name: string;
8+
department: string;
9+
salary: number;
10+
hired: string;
11+
}
12+
13+
const data: Employee[] = [
14+
{ id: 1, name: 'Aria Chen', department: 'Engineering', salary: 155000, hired: '2019-03-12' },
15+
{ id: 2, name: 'Marcus Webb', department: 'Product', salary: 132000, hired: '2020-07-01' },
16+
{ id: 3, name: 'Priya Kapoor', department: 'Design', salary: 118000, hired: '2021-01-15' },
17+
{ id: 4, name: 'Jordan Ellis', department: 'Engineering', salary: 143000, hired: '2018-11-30' },
18+
{ id: 5, name: 'Sam Rivera', department: 'Engineering', salary: 128000, hired: '2022-04-22' },
19+
{ id: 6, name: 'Taylor Brooks', department: 'Sales', salary: 97000, hired: '2023-02-08' },
20+
{ id: 7, name: 'Morgan Lee', department: 'Engineering', salary: 162000, hired: '2017-09-05' },
21+
{ id: 8, name: 'Casey Park', department: 'Design', salary: 109000, hired: '2022-11-19' },
22+
];
23+
24+
const columns: TableColumn<Employee>[] = [
25+
{ id: 'name', name: 'Name', selector: r => r.name, sortable: true },
26+
{ id: 'department', name: 'Department', selector: r => r.department, sortable: true },
27+
{
28+
id: 'salary',
29+
name: 'Salary',
30+
selector: r => r.salary,
31+
format: r => `$${r.salary.toLocaleString()}`,
32+
right: true,
33+
sortable: true,
34+
},
35+
{ id: 'hired', name: 'Hired', selector: r => r.hired, sortable: true },
36+
];
37+
38+
export default function MultiSortDemo() {
39+
const [sortColumns, setSortColumns] = useState<SortColumn<Employee>[]>([]);
40+
41+
const summary =
42+
sortColumns.length === 0
43+
? 'No sort — click a header to sort, click a third time to remove it'
44+
: sortColumns.map((s, i) => `${i + 1}. ${String(s.column.id)} ${s.sortDirection}`).join(' · ');
45+
46+
return (
47+
<div className="space-y-3">
48+
<p className="text-xs text-gray-500">
49+
Ctrl-click (⌘-click on macOS) a second column header to add it to the sort. Cycle each column
50+
ascending → descending → off.
51+
</p>
52+
<DataTable
53+
columns={columns}
54+
data={data}
55+
sortMulti
56+
defaultSortFieldId="department"
57+
onSort={(_col, _dir, _rows, next) => setSortColumns(next as SortColumn<Employee>[])}
58+
highlightOnHover
59+
/>
60+
<span className="block text-xs text-gray-500 font-mono">{summary}</span>
61+
</div>
62+
);
63+
}

apps/docs/src/layouts/DocsLayout.astro

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const nav = [
1616
links: [
1717
{ label: 'Getting Started', href: '/docs/getting-started' },
1818
{ label: 'Installation', href: '/docs/installation' },
19-
{ label: "What's New in v8", href: '/docs/whats-new' },
19+
{ label: 'Changelog', href: '/docs/changelog' },
2020
],
2121
},
2222
{
@@ -103,7 +103,6 @@ const nav = [
103103
links: [
104104
{ label: 'API Reference', href: '/docs/api' },
105105
{ label: 'Migration Guide', href: '/docs/migration' },
106-
{ label: 'Changelog', href: '/docs/changelog' },
107106
],
108107
},
109108
];

0 commit comments

Comments
 (0)