Skip to content

Commit abfdd63

Browse files
authored
bug: align column filter popup to the right edge in RTL mode and enhance related documentation (jbetancur#1391)
1 parent 3eec7ef commit abfdd63

5 files changed

Lines changed: 47 additions & 5 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ A summary of notable changes per release. For the full commit history see the [r
2424
- 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)
2525
- 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)
2626
- 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)
27+
- 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))
2728

2829
---
2930

‎apps/docs/src/components/demos/RTLDemo.tsx‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export default function RTLDemo() {
6262
name: isRTL ? 'الاسم' : 'Name',
6363
selector: r => (isRTL ? r.nameAr : r.name),
6464
sortable: true,
65+
filterable: true,
6566
width: '200px',
6667
minWidth: '120px',
6768
reorder: true,
@@ -70,6 +71,7 @@ export default function RTLDemo() {
7071
name: isRTL ? 'القسم' : 'Department',
7172
selector: r => (isRTL ? r.departmentAr : r.department),
7273
sortable: true,
74+
filterable: true,
7375
minWidth: '140px',
7476
reorder: true,
7577
},
@@ -79,6 +81,8 @@ export default function RTLDemo() {
7981
format: r => `$${r.salary.toLocaleString()}`,
8082
right: true,
8183
sortable: true,
84+
filterable: true,
85+
filterType: 'number',
8286
width: '140px',
8387
reorder: true,
8488
},
@@ -115,6 +119,7 @@ export default function RTLDemo() {
115119
highlightOnHover
116120
striped
117121
resizable
122+
contextMenu
118123
/>
119124
</div>
120125
);

‎apps/docs/src/pages/docs/rtl.astro‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ import PropsTable from '../../components/PropsTable.astro';
1717

1818
<Demo
1919
title="Direction: LTR / RTL / AUTO"
20-
description="Toggle between LTR, RTL, and AUTO. In RTL mode the pagination arrows reverse, column text aligns right-to-left, and the resize handles mirror to each column's left edge. Drag a column boundary to try it."
20+
description="Toggle between LTR, RTL, and AUTO. In RTL mode the pagination arrows reverse, column text aligns right-to-left, and the resize handles mirror to each column's left edge. Drag a column boundary to try it, or open a filter panel or right-click a header to see the popups align to the right edge of their button."
2121
code={`import DataTable, { Direction } from 'react-data-table-component';
2222
23-
<DataTable columns={columns} data={data} direction={Direction.RTL} pagination />;`}
23+
<DataTable columns={columns} data={data} direction={Direction.RTL} pagination contextMenu />;`}
2424
>
2525
<RTLDemo client:load />
2626
</Demo>
@@ -66,6 +66,7 @@ import PropsTable from '../../components/PropsTable.astro';
6666
<li>Pagination first/prev/next/last buttons have their icons reversed via <code>.rdt_paginationButtonRTL</code>.</li>
6767
<li>Column <code>right: true</code> alignment continues to work as expected. Align numbers relative to the reading direction of the cell content, not the page.</li>
6868
<li>With <code>resizable</code>, the resize handle moves to each column's left (end) edge and drag direction mirrors, so dragging away from the column widens it just like in LTR.</li>
69+
<li>Popups anchored to a button, the column filter panel and the context menu, align their right edge to the button instead of their left, so they open across the column they belong to. Both are still clamped to stay inside the viewport.</li>
6970
</ul>
7071

7172
<h2>Pairing with localization</h2>

‎src/__tests__/ColumnFilter.position.test.tsx‎

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as React from 'react';
22
import { render, fireEvent } from '@testing-library/react';
33
import ColumnFilter from '../components/ColumnFilter';
4+
import DataTable from '../components/DataTable';
5+
import { Direction } from '../constants';
46
import { emptyFilterState } from '../hooks/useColumnFilter';
57

68
describe('ColumnFilter:viewport positioning', () => {
@@ -27,9 +29,11 @@ describe('ColumnFilter:viewport positioning', () => {
2729
vi.unstubAllGlobals();
2830
});
2931

30-
function openPanel() {
32+
function openPanel(dir?: 'rtl') {
3133
const result = render(
32-
<ColumnFilter columnId="name" filterValue={emptyFilterState('text')} onFilterChange={vi.fn()} />,
34+
<div dir={dir}>
35+
<ColumnFilter columnId="name" filterValue={emptyFilterState('text')} onFilterChange={vi.fn()} />
36+
</div>,
3337
);
3438
fireEvent.click(result.getByRole('button', { name: 'Filter column' }));
3539
return { ...result, panel: result.getByRole('dialog') };
@@ -66,6 +70,33 @@ describe('ColumnFilter:viewport positioning', () => {
6670
expect(panel.style.top).toBe('192px');
6771
});
6872

73+
test('aligns the panel to the anchor right edge in RTL', () => {
74+
anchor = new DOMRect(400, 40, 22, 22);
75+
const { panel } = openPanel('rtl');
76+
expect(panel.style.left).toBe('82px');
77+
});
78+
79+
test('keeps an RTL panel inside the left viewport edge', () => {
80+
anchor = new DOMRect(40, 40, 22, 22);
81+
const { panel } = openPanel('rtl');
82+
expect(panel.style.left).toBe('8px');
83+
});
84+
85+
// The panel is not portaled, so it inherits `dir` from the table wrapper that
86+
// `direction` sets. Guards the plumbing the standalone renders above skip.
87+
test('picks up RTL from the table wrapper', () => {
88+
anchor = new DOMRect(400, 40, 22, 22);
89+
const { getAllByRole, getByRole } = render(
90+
<DataTable
91+
data={[{ name: 'Amir' }]}
92+
columns={[{ name: 'Name', selector: (r: { name: string }) => r.name, filterable: true }]}
93+
direction={Direction.RTL}
94+
/>,
95+
);
96+
fireEvent.click(getAllByRole('button', { name: 'Filter column' })[0]);
97+
expect(getByRole('dialog').style.left).toBe('82px');
98+
});
99+
69100
test('repositions when adding a condition makes the panel taller and wider', () => {
70101
anchor = new DOMRect(450, 380, 22, 22);
71102
const { panel, getByRole } = openPanel();

‎src/components/ColumnFilter.tsx‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,11 @@ export default function ColumnFilter({
316316
const rect = panel.getBoundingClientRect();
317317
const anchor = button.getBoundingClientRect();
318318
const margin = 8;
319-
const left = Math.max(margin, Math.min(anchor.left, window.innerWidth - rect.width - margin));
319+
// Read the direction off the panel rather than take it as a prop: it picks up `dir`
320+
// set anywhere up the tree, and ColumnFilter renders too far down to reach useRTL.
321+
const isRTL = getComputedStyle(panel).direction === 'rtl';
322+
const anchored = isRTL ? anchor.right - rect.width : anchor.left;
323+
const left = Math.max(margin, Math.min(anchored, window.innerWidth - rect.width - margin));
320324
let top = anchor.bottom + 4;
321325
if (top + rect.height > window.innerHeight - margin) {
322326
const flipped = anchor.top - rect.height - 4;

0 commit comments

Comments
 (0)