Skip to content

Commit c0e4a63

Browse files
Feature: RTL (adazzle#2803)
* Use css logic properties * Use :dir pseudo class to set row borders * Fix virtualization * Use block/inline size instead of height/ width * use start/end with text align * Adjust context menu styles * Fix expand icon styles * Use padding-block/inline * Fix virtualization and column resize * Add Rtl example * Revert rename * scrollTop is positive * Fix margin * Rename css variables for clarity * Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <[email protected]> * Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <[email protected]> * Update src/DataGrid.tsx Co-authored-by: Nicolas Stepien <[email protected]> * Update website/Nav.tsx Co-authored-by: Nicolas Stepien <[email protected]> * Add missing content menu style, minor cleanup * Add global Right to left checkbox * Add key to create root component when direction is changed * Update readme * Fix styling * Fix frozen cell style * Fix box shadow * Add dir prop * Add direction prop * Update examples to use the direction prop * Update README.md Co-authored-by: Nicolas Stepien <[email protected]> * Remove key * Fix resizing * Add key to recreate grid when direction is changed * Fix direction in portals * Fix arrow direction in rtl * Switch left/right keys for groups * Update README.md Co-authored-by: Nicolas Stepien <[email protected]> * Use the direction prop * Add direction prop * More details on RTL * Update type * Test direction prop * Fix tests * Update README.md Co-authored-by: Nicolas Stepien <[email protected]> * Move the direction file outside * Remove dir and use a new sign variable * Add a comment * Move rtl tests to keyboardNavigation.test * Revert "Move rtl tests to keyboardNavigation.test" This reverts commit d188c85. * Only move the navigation tests to keyboardNavigation.test file * Remove confusing comment * Key is not required Co-authored-by: Nicolas Stepien <[email protected]>
1 parent 9aa9cf6 commit c0e4a63

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+411
-207
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
- [Cell copy / pasting](https://adazzle.github.io/react-data-grid/#/all-features)
4242
- [Cell value dragging / filling](https://adazzle.github.io/react-data-grid/#/all-features)
4343
- [Customizable Components](https://adazzle.github.io/react-data-grid/#/customizable-components)
44+
- Right-to-left (RTL) support. We recommend using Firefox as Chrome has a [bug](https://bugs.chromium.org/p/chromium/issues/detail?id=1140374) with frozen columns, and the [`:dir` pseudo class](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir) is not supported
4445

4546
## Links
4647

@@ -232,6 +233,15 @@ function MyGrid() {
232233

233234
###### `rowClass?: Maybe<(row: R) => Maybe<string>>`
234235

236+
##### `direction?: Maybe<'ltr' | 'rtl'>`
237+
238+
This property sets the text direction of the grid, it defaults to `'ltr'` (left-to-right). Setting `direction` to `'rtl'` has the following effects:
239+
240+
- Columns flow from right to left
241+
- Frozen columns are pinned on the right
242+
- Column resize handle is shown on the left edge of the column
243+
- Scrollbar is moved to the left
244+
235245
###### `className?: string | undefined`
236246

237247
###### `style?: CSSProperties | undefined`

src/DataGrid.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
getColSpan,
4040
max,
4141
sign,
42+
abs,
4243
getSelectedCellColSpan
4344
} from './utils';
4445

@@ -54,7 +55,8 @@ import type {
5455
SortColumn,
5556
RowHeightArgs,
5657
Maybe,
57-
Components
58+
Components,
59+
Direction
5860
} from './types';
5961

6062
export interface SelectCellState extends Position {
@@ -169,6 +171,7 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
169171
*/
170172
components?: Maybe<Components<R, SR>>;
171173
rowClass?: Maybe<(row: R) => Maybe<string>>;
174+
direction?: Maybe<Direction>;
172175
'data-testid'?: Maybe<string>;
173176
}
174177

@@ -216,6 +219,7 @@ function DataGrid<R, SR, K extends Key>(
216219
className,
217220
style,
218221
rowClass,
222+
direction,
219223
// ARIA
220224
'aria-label': ariaLabel,
221225
'aria-labelledby': ariaLabelledBy,
@@ -238,6 +242,7 @@ function DataGrid<R, SR, K extends Key>(
238242
const noRowsFallback = components?.noRowsFallback ?? defaultComponents?.noRowsFallback;
239243
const cellNavigationMode = rawCellNavigationMode ?? 'NONE';
240244
enableVirtualization ??= true;
245+
direction ??= 'ltr';
241246

242247
/**
243248
* states
@@ -270,6 +275,9 @@ function DataGrid<R, SR, K extends Key>(
270275
const clientHeight = gridHeight - headerRowHeight - summaryRowsCount * summaryRowHeight;
271276
const isSelectable = selectedRows != null && onSelectedRowsChange != null;
272277
const isHeaderRowSelected = selectedPosition.rowIdx === -1;
278+
const isRtl = direction === 'rtl';
279+
const leftKey = isRtl ? 'ArrowRight' : 'ArrowLeft';
280+
const rightKey = isRtl ? 'ArrowLeft' : 'ArrowRight';
273281

274282
const defaultGridComponents = useMemo(
275283
() => ({
@@ -566,9 +574,9 @@ function DataGrid<R, SR, K extends Key>(
566574
isGroupRow(row) &&
567575
selectedPosition.idx === -1 &&
568576
// Collapse the current group row if it is focused and is in expanded state
569-
((key === 'ArrowLeft' && row.isExpanded) ||
577+
((key === leftKey && row.isExpanded) ||
570578
// Expand the current group row if it is focused and is in collapsed state
571-
(key === 'ArrowRight' && !row.isExpanded))
579+
(key === rightKey && !row.isExpanded))
572580
) {
573581
event.preventDefault(); // Prevents scrolling
574582
toggleGroup(row.id);
@@ -600,7 +608,8 @@ function DataGrid<R, SR, K extends Key>(
600608
function handleScroll(event: React.UIEvent<HTMLDivElement>) {
601609
const { scrollTop, scrollLeft } = event.currentTarget;
602610
setScrollTop(scrollTop);
603-
setScrollLeft(scrollLeft);
611+
// scrollLeft is nagative when direction is rtl
612+
setScrollLeft(abs(scrollLeft));
604613
onScroll?.(event);
605614
}
606615

@@ -749,10 +758,11 @@ function DataGrid<R, SR, K extends Key>(
749758

750759
const isCellAtLeftBoundary = left < scrollLeft + totalFrozenColumnWidth;
751760
const isCellAtRightBoundary = right > clientWidth + scrollLeft;
761+
const sign = isRtl ? -1 : 1;
752762
if (isCellAtLeftBoundary) {
753-
current.scrollLeft = left - totalFrozenColumnWidth;
763+
current.scrollLeft = (left - totalFrozenColumnWidth) * sign;
754764
} else if (isCellAtRightBoundary) {
755-
current.scrollLeft = right - clientWidth;
765+
current.scrollLeft = (right - clientWidth) * sign;
756766
}
757767
}
758768

@@ -775,13 +785,7 @@ function DataGrid<R, SR, K extends Key>(
775785
const isRowSelected = selectedCellIsWithinSelectionBounds && idx === -1;
776786

777787
// If a group row is focused, and it is collapsed, move to the parent group row (if there is one).
778-
if (
779-
key === 'ArrowLeft' &&
780-
isRowSelected &&
781-
isGroupRow(row) &&
782-
!row.isExpanded &&
783-
row.level !== 0
784-
) {
788+
if (key === leftKey && isRowSelected && isGroupRow(row) && !row.isExpanded && row.level !== 0) {
785789
let parentRowIdx = -1;
786790
for (let i = selectedPosition.rowIdx - 1; i >= 0; i--) {
787791
const parentRow = rows[i];
@@ -801,9 +805,9 @@ function DataGrid<R, SR, K extends Key>(
801805
case 'ArrowDown':
802806
return { idx, rowIdx: rowIdx + 1 };
803807
case 'ArrowLeft':
804-
return { idx: idx - 1, rowIdx };
808+
return isRtl ? { idx: idx + 1, rowIdx } : { idx: idx - 1, rowIdx };
805809
case 'ArrowRight':
806-
return { idx: idx + 1, rowIdx };
810+
return isRtl ? { idx: idx - 1, rowIdx } : { idx: idx + 1, rowIdx };
807811
case 'Tab':
808812
return { idx: idx + (shiftKey ? -1 : 1), rowIdx };
809813
case 'Home':
@@ -1117,16 +1121,18 @@ function DataGrid<R, SR, K extends Key>(
11171121
...style,
11181122
gridTemplateRows: templateRows,
11191123
'--rdg-header-row-height': `${headerRowHeight}px`,
1120-
'--rdg-row-width': `${totalColumnWidth}px`,
1124+
'--rdg-grid-inline-size': `${totalColumnWidth}px`,
11211125
'--rdg-summary-row-height': `${summaryRowHeight}px`,
1122-
'--rdg-grid-height': `${
1126+
'--rdg-grid-block-size': `${
11231127
max(totalRowHeight, clientHeight) +
11241128
headerRowHeight +
11251129
summaryRowsCount * summaryRowHeight
11261130
}px`,
1131+
'--rdg-sign': isRtl ? -1 : 1,
11271132
...getLayoutCssVars()
11281133
} as unknown as React.CSSProperties
11291134
}
1135+
dir={direction}
11301136
ref={gridRef}
11311137
onScroll={handleScroll}
11321138
onKeyDown={handleKeyDown}
@@ -1156,6 +1162,7 @@ function DataGrid<R, SR, K extends Key>(
11561162
selectedCellIdx={isHeaderRowSelected ? selectedPosition.idx : undefined}
11571163
selectCell={selectHeaderCellLatest}
11581164
shouldFocusGrid={!selectedCellIsWithinSelectionBounds}
1165+
direction={direction}
11591166
/>
11601167
{rows.length === 0 && noRowsFallback ? (
11611168
noRowsFallback

src/DragHandle.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import type { DataGridProps, SelectCellState } from './DataGrid';
66
const cellDragHandle = css`
77
cursor: move;
88
position: absolute;
9-
right: 0;
10-
bottom: 0;
11-
width: 8px;
12-
height: 8px;
9+
inset-inline-end: 0;
10+
inset-block-end: 0;
11+
inline-size: 8px;
12+
block-size: 8px;
1313
background-color: var(--rdg-selection-color);
1414
1515
&:hover {
16-
width: 16px;
17-
height: 16px;
16+
inline-size: 16px;
17+
block-size: 16px;
1818
border: 2px solid var(--rdg-selection-color);
1919
background-color: var(--rdg-background-color);
2020
}

src/GroupRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const groupRow = css`
3333
}
3434
3535
> .${cell}:not(:last-child):not(.${cellFrozenLast}) {
36-
border-right: none;
36+
border-inline-end: none;
3737
}
3838
`;
3939

src/HeaderCell.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ const cellResizable = css`
1313
content: '';
1414
cursor: col-resize;
1515
position: absolute;
16-
top: 0;
17-
right: 0;
18-
bottom: 0;
19-
width: 10px;
16+
inset-block-start: 0;
17+
inset-inline-end: 0;
18+
inset-block-end: 0;
19+
inline-size: 10px;
2020
}
2121
`;
2222

@@ -31,6 +31,7 @@ type SharedHeaderRowProps<R, SR> = Pick<
3131
| 'selectCell'
3232
| 'onColumnResize'
3333
| 'shouldFocusGrid'
34+
| 'direction'
3435
>;
3536

3637
export interface HeaderCellProps<R, SR> extends SharedHeaderRowProps<R, SR> {
@@ -49,8 +50,10 @@ export default function HeaderCell<R, SR>({
4950
sortColumns,
5051
onSortColumnsChange,
5152
selectCell,
52-
shouldFocusGrid
53+
shouldFocusGrid,
54+
direction
5355
}: HeaderCellProps<R, SR>) {
56+
const isRtl = direction === 'rtl';
5457
const { ref, tabIndex, onFocus } = useRovingCellRef(isCellSelected);
5558
const sortIndex = sortColumns?.findIndex((sort) => sort.columnKey === column.key);
5659
const sortColumn =
@@ -72,16 +75,17 @@ export default function HeaderCell<R, SR>({
7275
}
7376

7477
const { currentTarget, pointerId } = event;
75-
const { right } = currentTarget.getBoundingClientRect();
76-
const offset = right - event.clientX;
78+
const { right, left } = currentTarget.getBoundingClientRect();
79+
const offset = isRtl ? event.clientX - left : right - event.clientX;
7780

7881
if (offset > 11) {
7982
// +1px to account for the border size
8083
return;
8184
}
8285

8386
function onPointerMove(event: PointerEvent) {
84-
const width = event.clientX + offset - currentTarget.getBoundingClientRect().left;
87+
const { right, left } = currentTarget.getBoundingClientRect();
88+
const width = isRtl ? right + offset - event.clientX : event.clientX + offset - left;
8589
if (width > 0) {
8690
onColumnResize(column, width);
8791
}
@@ -139,8 +143,8 @@ export default function HeaderCell<R, SR>({
139143
}
140144

141145
function onDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
142-
const { right } = event.currentTarget.getBoundingClientRect();
143-
const offset = right - event.clientX;
146+
const { right, left } = event.currentTarget.getBoundingClientRect();
147+
const offset = isRtl ? event.clientX - left : right - event.clientX;
144148

145149
if (offset > 11) {
146150
// +1px to account for the border size

src/HeaderRow.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import clsx from 'clsx';
33
import { css } from '@linaria/core';
44

55
import HeaderCell from './HeaderCell';
6-
import type { CalculatedColumn } from './types';
6+
import type { CalculatedColumn, Direction } from './types';
77
import { getColSpan, getRowStyle } from './utils';
88
import type { DataGridProps } from './DataGrid';
99
import { cell, cellFrozen, rowSelectedClassname } from './style';
@@ -22,6 +22,7 @@ export interface HeaderRowProps<R, SR, K extends React.Key> extends SharedDataGr
2222
lastFrozenColumnIndex: number;
2323
selectedCellIdx: number | undefined;
2424
shouldFocusGrid: boolean;
25+
direction: Direction;
2526
}
2627

2728
const headerRow = css`
@@ -34,7 +35,7 @@ const headerRow = css`
3435
/* Should have a higher value than 1 to show up above frozen cells */
3536
z-index: 2;
3637
position: sticky;
37-
top: 0;
38+
inset-block-start: 0;
3839
}
3940
4041
> .${cellFrozen} {
@@ -54,7 +55,8 @@ function HeaderRow<R, SR, K extends React.Key>({
5455
lastFrozenColumnIndex,
5556
selectedCellIdx,
5657
selectCell,
57-
shouldFocusGrid
58+
shouldFocusGrid,
59+
direction
5860
}: HeaderRowProps<R, SR, K>) {
5961
const cells = [];
6062
for (let index = 0; index < columns.length; index++) {
@@ -77,6 +79,7 @@ function HeaderRow<R, SR, K extends React.Key>({
7779
sortColumns={sortColumns}
7880
selectCell={selectCell}
7981
shouldFocusGrid={shouldFocusGrid && index === 0}
82+
direction={direction}
8083
/>
8184
);
8285
}

src/SummaryCell.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import type { CalculatedColumn, CellRendererProps } from './types';
66
import { useRovingCellRef } from './hooks';
77

88
export const summaryCellClassname = css`
9-
top: var(--rdg-summary-row-top);
10-
bottom: var(--rdg-summary-row-bottom);
9+
inset-block-start: var(--rdg-summary-row-top);
10+
inset-block-end: var(--rdg-summary-row-bottom);
1111
`;
1212

1313
interface SharedCellRendererProps<R, SR>

src/SummaryRow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const summaryRow = css`
3333

3434
const summaryRowBorderClassname = css`
3535
& > .${cell} {
36-
border-top: 2px solid var(--rdg-summary-border-color);
36+
border-block-start: 2px solid var(--rdg-summary-border-color);
3737
}
3838
`;
3939

src/editors/TextEditor.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ const textEditor = css`
55
appearance: none;
66
77
box-sizing: border-box;
8-
width: 100%;
9-
height: 100%;
10-
padding: 0px 6px 0 6px;
8+
inline-size: 100%;
9+
block-size: 100%;
10+
padding-block: 0;
11+
padding-inline: 6px;
1112
border: 2px solid #ccc;
1213
vertical-align: top;
1314
color: var(--rdg-color);

src/formatters/CheckboxFormatter.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const checkboxLabel = css`
1111
justify-content: center;
1212
position: absolute;
1313
inset: 0;
14-
margin-right: 1px; /* align checkbox in row group cell */
14+
margin-inline-end: 1px; /* align checkbox in row group cell */
1515
`;
1616

1717
const checkboxLabelClassname = `rdg-checkbox-label ${checkboxLabel}`;
@@ -24,8 +24,8 @@ const checkboxInputClassname = `rdg-checkbox-input ${checkboxInput}`;
2424

2525
const checkbox = css`
2626
content: '';
27-
width: 20px;
28-
height: 20px;
27+
inline-size: 20px;
28+
block-size: 20px;
2929
border: 2px solid var(--rdg-border-color);
3030
background-color: var(--rdg-background-color);
3131
.${checkboxInput}:checked + & {

src/formatters/ToggleGroupFormatter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const groupCellContent = css`
99
const groupCellContentClassname = `rdg-group-cell-content ${groupCellContent}`;
1010

1111
const caret = css`
12-
margin-left: 4px;
12+
margin-inline-start: 4px;
1313
stroke: currentColor;
1414
stroke-width: 1.5px;
1515
fill: transparent;

src/style/cell.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { css } from '@linaria/core';
33
export const cell = css`
44
contain: strict;
55
contain: size layout style paint;
6-
padding: 0 8px;
7-
border-right: 1px solid var(--rdg-border-color);
8-
border-bottom: 1px solid var(--rdg-border-color);
6+
padding-block: 0;
7+
padding-inline: 8px;
8+
border-inline-end: 1px solid var(--rdg-border-color);
9+
border-block-end: 1px solid var(--rdg-border-color);
910
grid-row-start: var(--rdg-grid-row-start);
1011
background-color: inherit;
1112

0 commit comments

Comments
 (0)