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
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ jobs:
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)
# GitHub Releases resolve root-relative links against github.com, so absolutize the docs links
NOTES=$(echo "$NOTES" | sed -E 's#\]\(/#](https://reactdatatable.com/#g')
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
Expand Down
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

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

### New features

- **`column.filterOptions`** — per-column set filter settings. `values` supplies the checklist instead of deriving it from the loaded rows, so a column can offer values the current page does not hold. Takes an array or a function of the rows. → [Filtering](/docs/filtering#supplying-set-values) ([#1372](https://github.com/jbetancur/react-data-table-component/issues/1372))
- **`column.filterOptions.separator`** — splits a set filter cell holding several values, so a column formatted as "React, TypeScript" offers each tag in the checklist rather than the whole string. A row matches when any of its parts is selected. → [Filtering](/docs/filtering#multi-value-cells)

### Behavior changes

- Context menu items now show icons for the built-in actions. Custom actions without an `icon` still align with the ones that have one. → [Context menu](/docs/context-menu)
- The column filter panel and the context menu now fade in rather than appearing instantly, and share one set of popup styles so they stay visually consistent. Respects `prefers-reduced-motion`. → [Context menu](/docs/context-menu)
- Both popups' drop shadow reads from the new `--rdt-shadow-popup` variable instead of a hardcoded value, so dark themes can set their own. The filter panel previously had no variable at all. → [Theming](/docs/themes)
- The column filter button now reports `aria-expanded` and `aria-haspopup="dialog"` instead of `aria-pressed`, which described it as a toggle rather than something that opens a panel. → [Filtering](/docs/filtering)

### Bug fixes

- Set filter checkboxes now use the table's own checkbox styling instead of the browser's native control, so they match the row selection checkboxes and follow the theme. → [Filtering](/docs/filtering)
- Keyboard focus in the context menu is now distinguishable from mouse hover. Both painted the same background, so arrowing through the menu while the pointer rested on another item highlighted two items at once. → [Context menu](/docs/context-menu)
- Checkboxes now show a focus ring when tabbed to. The styled checkbox hides the real input and draws its own box, which had no focus style, so keyboard users tabbing through row selection or a set filter checklist could not see where they were. → [Row selection](/docs/selection)
- Tab no longer escapes an open column filter panel. Focus now cycles within it, so a keyboard user could not end up behind an open panel with no way back. → [Filtering](/docs/filtering)
- A disabled context menu item's icon is no longer nearly invisible. The icon was muted with an opacity that multiplied with the already-dimmed disabled color. → [Context menu](/docs/context-menu)
- A set filter's `(Select all)` now announces that it acts on the search results while a search narrows the list. New `selectAllFilteredAriaLabel` localization string. → [Filtering](/docs/filtering)
- The column filter popup now aligns to the right edge of its button in RTL, matching the context menu. It always aligned its left edge to the button, so in RTL the panel opened across the wrong side of the column it belongs to. → [RTL](/docs/rtl) ([#1388](https://github.com/jbetancur/react-data-table-component/issues/1388))

---

## 8.9.1

### Bug fixes
Expand Down Expand Up @@ -131,7 +157,7 @@ A summary of notable changes per release. For the full commit history see the [r
### Bug fixes

- Fixed RTL support across resizing, pinning, and separators: resize handles sit on the correct edge and widen as in LTR, pinned columns stick to logical edges with mirrored shadows, the pinned horizontal scrollbar accounts for RTL scroll coordinates, and column/group-header separators plus small chrome (filter dot, select arrows, pagination chevron) mirror correctly. Applies to `direction={Direction.RTL}` and inherited `dir="rtl"`. → [RTL support](/docs/rtl)
- Fixed row and table backgrounds cutting off at the initial container width when the table scrolls horizontally, leaving scrolled-into columns unpainted. → [Theming](/docs/theming)
- Fixed row and table backgrounds cutting off at the initial container width when the table scrolls horizontally, leaving scrolled-into columns unpainted. → [Theming](/docs/themes)
- Fixed `fixedHeader` with `responsive={false}` not creating a scroll container, letting rows spill over content below the table. `fixedHeader` now always creates one.
- Fixed the pinned-columns scrollbar failing to appear or going stale when content width changed without a container resize (async rows, resizing or toggling columns), and when columns were pinned with `responsive={false}`. → [Column pinning](/docs/column-pinning)
- Fixed resize handles being clipped for right-pinned columns and the last column, which cut their hit areas down to a sliver. → [Resizable columns](/docs/resizable)
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/public/llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const columns = [
- [Column Resizing](https://reactdatatable.com/docs/resizable): drag column edges by mouse or touch, persist widths
- [Column Pinning](https://reactdatatable.com/docs/column-pinning): pin columns left or right
- [Column Separators](https://reactdatatable.com/docs/column-separators): vertical divider lines between header and body columns
- [Column Filtering](https://reactdatatable.com/docs/filtering): per-column text, number, date, datetime, and time-of-day filters with operator selection and two-condition AND/OR logic, plus set filters that pick from a checklist of a column's distinct values, and a filterServer option for backend filtering
- [Column Filtering](https://reactdatatable.com/docs/filtering): per-column text, number, date, datetime, and time-of-day filters with operator selection and two-condition AND/OR logic, plus set filters that pick from a checklist of a column's distinct values or a supplied list, optionally splitting cells that hold several values, and a filterServer option for backend filtering
- [Sorting](https://reactdatatable.com/docs/sorting): client-side and server-side sorting

## Rows
Expand Down
5 changes: 5 additions & 0 deletions apps/docs/src/components/demos/RTLDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export default function RTLDemo() {
name: isRTL ? 'الاسم' : 'Name',
selector: r => (isRTL ? r.nameAr : r.name),
sortable: true,
filterable: true,
width: '200px',
minWidth: '120px',
reorder: true,
Expand All @@ -70,6 +71,7 @@ export default function RTLDemo() {
name: isRTL ? 'القسم' : 'Department',
selector: r => (isRTL ? r.departmentAr : r.department),
sortable: true,
filterable: true,
minWidth: '140px',
reorder: true,
},
Expand All @@ -79,6 +81,8 @@ export default function RTLDemo() {
format: r => `$${r.salary.toLocaleString()}`,
right: true,
sortable: true,
filterable: true,
filterType: 'number',
width: '140px',
reorder: true,
},
Expand Down Expand Up @@ -115,6 +119,7 @@ export default function RTLDemo() {
highlightOnHover
striped
resizable
contextMenu
/>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/demos/RowPinningDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export default function RowPinningDemo() {
<div className="space-y-3">
<p className="text-xs text-gray-500">
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.
to see the behavior.
</p>
<DataTable
columns={columns}
Expand Down
77 changes: 77 additions & 0 deletions apps/docs/src/components/demos/SetFilterOptionsDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import DataTable from '../ThemedDataTable';
import { type TableColumn, type FilterState } from 'react-data-table-component';

interface Ticket {
id: number;
subject: string;
status: string;
}

const STATUSES = ['Active', 'Pending', 'Closed', 'Archived'];

// Stands in for the server. Only Active and Pending tickets are on the first page,
// so deriving the checklist from the loaded rows would never offer the other two.
const everything: Ticket[] = [
{ id: 1, subject: 'Login loop on SSO', status: 'Active' },
{ id: 2, subject: 'Invoice PDF is blank', status: 'Active' },
{ id: 3, subject: 'Webhook retries stall', status: 'Pending' },
{ id: 4, subject: 'Export times out', status: 'Pending' },
{ id: 5, subject: 'Password reset email', status: 'Closed' },
{ id: 6, subject: 'Duplicate charge', status: 'Closed' },
{ id: 7, subject: 'Legacy API sunset', status: 'Archived' },
];

const firstPage = everything.filter(t => t.status === 'Active' || t.status === 'Pending');

const columns: TableColumn<Ticket>[] = [
{ id: 'subject', name: 'Subject', selector: r => r.subject, grow: 2 },
{
id: 'status',
name: 'Status',
selector: r => r.status,
filterable: true,
filterType: 'set',
filterOptions: { values: STATUSES },
},
];

export default function SetFilterOptionsDemo() {
const [rows, setRows] = React.useState<Ticket[]>(firstPage);
const [filters, setFilters] = React.useState<Record<string | number, FilterState>>({});
const [loading, setLoading] = React.useState(false);

function handleFilterChange(columnId: string | number, next: FilterState) {
setFilters(prev => ({ ...prev, [columnId]: next }));

// What a real fetch would do: send the selection, render whatever comes back.
setLoading(true);
const selected = next.values;
window.setTimeout(() => {
setRows(selected === undefined ? firstPage : everything.filter(t => selected.includes(t.status)));
setLoading(false);
}, 300);
}

const selected = filters.status?.values;

return (
<div className="flex flex-col gap-3">
<div className="text-xs text-gray-500 font-mono bg-gray-50 border border-gray-100 rounded px-3 py-2">
{selected === undefined
? 'no filter applied. Open the Status filter: all four statuses are listed, though only Active and Pending are loaded'
: `requested [${selected.join(', ')}] from the server`}
</div>

<DataTable
columns={columns}
data={rows}
filterServer
filterValues={filters}
onFilterChange={handleFilterChange}
progressPending={loading}
highlightOnHover
/>
</div>
);
}
57 changes: 57 additions & 0 deletions apps/docs/src/components/demos/SetFilterSeparatorDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import DataTable from '../ThemedDataTable';
import { type TableColumn, type FilterState } from 'react-data-table-component';

interface Project {
id: number;
name: string;
stack: string;
}

// The stack is stored the way it reads in the cell: one string, comma separated.
const projects: Project[] = [
{ id: 1, name: 'Checkout rewrite', stack: 'React, TypeScript' },
{ id: 2, name: 'Billing service', stack: 'Go, Postgres' },
{ id: 3, name: 'Design system', stack: 'React, Storybook, TypeScript' },
{ id: 4, name: 'Data pipeline', stack: 'Python, Postgres' },
{ id: 5, name: 'Marketing site', stack: 'Astro, TypeScript' },
{ id: 6, name: 'Internal scripts', stack: '' },
];

const columns: TableColumn<Project>[] = [
{ id: 'name', name: 'Project', selector: r => r.name, grow: 2 },
{
id: 'stack',
name: 'Stack',
selector: r => r.stack,
filterable: true,
filterType: 'set',
filterOptions: { separator: ',' },
},
];

export default function SetFilterSeparatorDemo() {
const [filters, setFilters] = React.useState<Record<string | number, FilterState>>({});

const selected = filters.stack?.values;

return (
<div className="flex flex-col gap-3">
<div className="text-xs text-gray-500 font-mono bg-gray-50 border border-gray-100 rounded px-3 py-2">
{selected === undefined
? 'no filter applied. Open the Stack filter: each technology is listed on its own, not as "React, TypeScript"'
: selected.length === 0
? 'nothing selected, so no rows match'
: `showing projects using any of [${selected.join(', ')}]`}
</div>

<DataTable
columns={columns}
data={projects}
filterValues={filters}
onFilterChange={(columnId, next) => setFilters(prev => ({ ...prev, [columnId]: next }))}
highlightOnHover
/>
</div>
);
}
12 changes: 11 additions & 1 deletion apps/docs/src/data/props.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ export const dataTableProps: PropDef[] = [
{
name: 'onRowMiddleClicked',
type: c('(row, event) =&gt; void'),
description: `Called when a row is middle-clicked (scroll-click). Use with ${c('onRowClicked')} to implement open-in-new-tab behaviour.`,
description: `Called when a row is middle-clicked (scroll-click). Use with ${c('onRowClicked')} to implement open-in-new-tab behavior.`,
group: 'Row events',
features: ['row-events'],
},
Expand Down Expand Up @@ -926,6 +926,16 @@ export const dataTableProps: PropDef[] = [
group: 'Column features',
features: ['filtering'],
},
{
name: 'column.filterOptions',
on: 'column',
type: c('{ values?: string[] | ((rows: T[]) =&gt; string[]); separator?: string | RegExp }'),
default: '-',
description: `Per-column filter settings, only read by ${c("filterType: 'set'")}. ${c('values')} supplies the checklist instead of deriving it from the rows: use it when the column has a known domain the loaded rows may not cover, such as a status the server holds but the current page does not. Order is kept as given, and blanks are only offered if the list contains an empty string. ${c('separator')} splits a cell holding several values, so a column formatted as "React, TypeScript" lists each tag separately and a row matches when any of its parts is selected. See <a href="/docs/filtering#supplying-set-values">Filtering</a> and <a href="/docs/filtering#multi-value-cells">multi-value cells</a>.`,
brief: `Per-column set filter settings. ${c('values')} supplies the checklist, ${c('separator')} splits multi-value cells.`,
group: 'Column features',
features: ['filtering'],
},
{
name: 'onColumnOrderChange',
type: c('(columns: TableColumn&lt;T&gt;[]) =&gt; void'),
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/pages/docs/api.astro
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ interface CustomCellEditorContext<T> {
rows={[
['<code>--rdt-color-cell-edit-bg</code>', '8% primary on bg', 'Background of the cell while editing.'],
['<code>--rdt-color-cell-edit-hover</code>', '6% primary', 'Background of an editable cell on hover.'],
['<code>--rdt-color-cell-edit-hover-border</code>', '40% primary', 'Dashed underline colour on editable cell hover.'],
['<code>--rdt-color-cell-edit-hover-border</code>', '40% primary', 'Dashed underline color on editable cell hover.'],
]}
/>

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/pages/docs/column-groups.astro
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default function App() {

<p>
Group label cells carry the CSS class <code>rdt_groupCell</code>. Target it directly
to override colours, font, or spacing without needing <code>customStyles</code>:
to override colors, font, or spacing without needing <code>customStyles</code>:
</p>

<CodeBlock code={`.rdt_groupCell {
Expand Down
6 changes: 3 additions & 3 deletions apps/docs/src/pages/docs/column-separators.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import ColumnSeparatorsDemo from '../../components/demos/ColumnSeparatorsDemo.ts
// Default: header separators on (subtle), no body separators
<DataTable columns={columns} data={data} />

// Body separators — subtle inset (60% height, centred)
// Body separators — subtle inset (60% height, centered)
<DataTable columns={columns} data={data} columnSeparator />

// Body separators — full-height
Expand All @@ -45,8 +45,8 @@ import ColumnSeparatorsDemo from '../../components/demos/ColumnSeparatorsDemo.ts
headers={['Value', 'Effect']}
rows={[
['<code>false</code>', 'No separators'],
['<code>true</code> / <code>"subtle"</code>', '60%-height inset line, centred vertically'],
['<code>"full"</code>', "Full-height line using the theme's divider colour"],
['<code>true</code> / <code>"subtle"</code>', '60%-height inset line, centered vertically'],
['<code>"full"</code>', "Full-height line using the theme's divider color"],
]}
/>

Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/pages/docs/columns.astro
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import ColumnsDemo from '../../components/demos/ColumnsDemo.tsx';

<p>
Columns are defined as an array of <code>TableColumn&lt;T&gt;</code> objects.
Each column controls its header label, data accessor, sort behaviour, rendering, and layout.
Each column controls its header label, data accessor, sort behavior, rendering, and layout.
</p>

<Demo
Expand Down
6 changes: 4 additions & 2 deletions apps/docs/src/pages/docs/context-menu.astro
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,14 @@ const rowActions = (row: Task): ContextMenuAction[] => [
<code>hide-column</code>, <code>reset</code>. Use different ids for custom actions.
</p>

<h2>Accessibility and behaviour</h2>
<h2>Accessibility and behavior</h2>
<ul>
<li>The menu renders with <code>role="menu"</code> / <code>role="menuitem"</code>; Arrow keys, Home, and End move focus, Escape closes and returns focus to the trigger.</li>
<li>Menu buttons carry <code>aria-haspopup="menu"</code> and localizable labels; row menu buttons appear on hover/focus and are always visible on touch devices.</li>
<li>On <a href="/docs/filtering">filterable</a> columns the header menu button renders after the filter icon, so it is always the outermost control — mirrored automatically in RTL.</li>
<li>The menu flips above the pointer near the bottom edge and clamps within the viewport; it closes on outside click, scroll, and resize.</li>
<li>The built-in actions carry icons. If any item in a menu has one, items without an <code>icon</code> still reserve the space, so every label starts at the same offset.</li>
<li>The menu fades in on open, and holds still when <code>prefers-reduced-motion: reduce</code> is set.</li>
<li>In RTL the button-anchored menu aligns to the button's opposite edge automatically.</li>
<li>All labels can be translated via <code>localization.contextMenu</code> — see <a href="/docs/localization">Localization</a>. The bundled locales include translations.</li>
</ul>
Expand All @@ -162,7 +164,7 @@ const rowActions = (row: Task): ContextMenuAction[] => [
['id', 'string', 'Identifier passed to onContextMenuAction. Built-in ids are reserved.'],
['label', 'ReactNode', 'Item content.'],
['disabled', 'boolean', 'Renders the item disabled.'],
['icon', 'ReactNode', 'Optional icon rendered before the label.'],
['icon', 'ReactNode', 'Optional icon rendered before the label. Built-in actions supply their own; omit it and the item still aligns with those that have one.'],
]}
/>
</DocsLayout>
Loading