Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
181a1e6
Feat cell renderer
Wroud Apr 17, 2023
05f6126
Merge remote-tracking branch 'upstream/main' into cell-renderer
Wroud Jun 27, 2023
dcad4da
chore: rename cellRenderer -> renderCell
Wroud Jun 27, 2023
5d4d5db
Merge remote-tracking branch 'upstream/main' into cell-renderer
Wroud Sep 11, 2023
333d30f
chore: codestyle
Wroud Sep 11, 2023
3a7fec4
chore: add renderCell tests and readme
Wroud Sep 12, 2023
4341329
chore: pr review fixes
Wroud Sep 12, 2023
c5515be
Merge branch 'main' into cell-renderer
Wroud Sep 12, 2023
6c82655
fix typo
Wroud Sep 17, 2023
f6c8083
Merge branch 'main' into cell-renderer
Wroud Sep 17, 2023
3be0a69
Merge branch 'main' into cell-renderer
Wroud Sep 20, 2023
dc5b9be
Merge branch 'main' into cell-renderer
Wroud Oct 1, 2023
923837e
Merge branch 'main' into cell-renderer
Wroud Dec 5, 2023
6e0c393
Merge branch 'main' into cell-renderer
Wroud Jan 22, 2024
1509ffc
Merge remote-tracking branch 'upstream/main' into cell-renderer
Wroud Jun 17, 2024
2f3881d
Merge remote-tracking branch 'upstream/main' into cell-renderer
sergeyteleshev Oct 7, 2024
025ab95
Refactor setupProvider function signature in renderers.test.tsx
sergeyteleshev Oct 7, 2024
ffa2b23
Merge remote-tracking branch 'origin/main' into cell-renderer
Liooo Oct 20, 2024
c3068ef
fix typing
Liooo Oct 16, 2024
0e614dc
allow passing style to cellRenderer
Liooo Oct 20, 2024
233a625
Merge branch 'main' into cell-renderer
Liooo Oct 24, 2024
35004a0
use object spread operator
Liooo Nov 11, 2024
6c62ed1
Merge branch 'main' into cell-renderer
Liooo Nov 11, 2024
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ interface Renderers<TRow, TSummaryRow> {
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
noRowsFallback?: Maybe<ReactNode>;
}
```
Expand Down
58 changes: 38 additions & 20 deletions src/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { memo } from 'react';
import { forwardRef, memo, type RefAttributes } from 'react';
import { css } from '@linaria/core';

import { useRovingTabIndex } from './hooks';
Expand All @@ -25,31 +25,37 @@ const cellDraggedOver = css`

const cellDraggedOverClassname = `rdg-cell-dragged-over ${cellDraggedOver}`;

function Cell<R, SR>({
column,
colSpan,
isCellSelected,
isCopied,
isDraggedOver,
row,
rowIdx,
onClick,
onDoubleClick,
onContextMenu,
onRowChange,
selectCell,
...props
}: CellRendererProps<R, SR>) {
function Cell<R, SR>(
{
column,
colSpan,
isCellSelected,
isCopied,
isDraggedOver,
row,
rowIdx,
className,
onClick,
onDoubleClick,
onContextMenu,
onRowChange,
selectCell,
style,
...props
}: CellRendererProps<R, SR>,
ref: React.Ref<HTMLDivElement>
) {
const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected);

const { cellClass } = column;
const className = getCellClassname(
className = getCellClassname(
column,
{
[cellCopiedClassname]: isCopied,
[cellDraggedOverClassname]: isDraggedOver
},
typeof cellClass === 'function' ? cellClass(row) : cellClass
typeof cellClass === 'function' ? cellClass(row) : cellClass,
className
);
const isEditable = isCellEditableUtil(column, row);

Expand Down Expand Up @@ -95,9 +101,13 @@ function Cell<R, SR>({
aria-colspan={colSpan}
aria-selected={isCellSelected}
aria-readonly={!isEditable || undefined}
ref={ref}
tabIndex={tabIndex}
className={className}
style={getCellStyle(column, colSpan)}
style={{
...getCellStyle(column, colSpan),
...style
}}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onContextMenu={handleContextMenu}
Expand All @@ -116,4 +126,12 @@ function Cell<R, SR>({
);
}

export default memo(Cell) as <R, SR>(props: CellRendererProps<R, SR>) => React.JSX.Element;
const CellComponent = memo(forwardRef(Cell)) as <R, SR>(
props: CellRendererProps<R, SR> & RefAttributes<HTMLDivElement>
) => React.JSX.Element;

export default CellComponent;

export function defaultRenderCell<R, SR>(key: React.Key, props: CellRendererProps<R, SR>) {
return <CellComponent key={key} {...props} />;
}
7 changes: 5 additions & 2 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import type {
SelectRowEvent,
SortColumn
} from './types';
import { defaultRenderCell } from './Cell';
import { renderCheckbox as defaultRenderCheckbox } from './cellRenderers';
import {
DataGridDefaultRenderersProvider,
Expand Down Expand Up @@ -273,6 +274,7 @@ function DataGrid<R, SR, K extends Key>(
const headerRowHeight = rawHeaderRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35);
const summaryRowHeight = rawSummaryRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35);
const renderRow = renderers?.renderRow ?? defaultRenderers?.renderRow ?? defaultRenderRow;
const renderCell = renderers?.renderCell ?? defaultRenderers?.renderCell ?? defaultRenderCell;
const renderSortStatus =
renderers?.renderSortStatus ?? defaultRenderers?.renderSortStatus ?? defaultRenderSortStatus;
const renderCheckbox =
Expand Down Expand Up @@ -364,9 +366,10 @@ function DataGrid<R, SR, K extends Key>(
const defaultGridComponents = useMemo(
() => ({
renderCheckbox,
renderSortStatus
renderSortStatus,
renderCell
}),
[renderCheckbox, renderSortStatus]
[renderCheckbox, renderSortStatus, renderCell]
);

const headerSelectionValue = useMemo((): HeaderRowSelectionContextValue => {
Expand Down
33 changes: 17 additions & 16 deletions src/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import clsx from 'clsx';
import { RowSelectionProvider, useLatestFunc, type RowSelectionContextValue } from './hooks';
import { getColSpan, getRowStyle } from './utils';
import type { CalculatedColumn, RenderRowProps } from './types';
import Cell from './Cell';
import { useDefaultRenderers } from './DataGridDefaultRenderersProvider';
import { rowClassname, rowSelectedClassname } from './style/row';

function Row<R, SR>(
Expand Down Expand Up @@ -33,6 +33,8 @@ function Row<R, SR>(
}: RenderRowProps<R, SR>,
ref: React.Ref<HTMLDivElement>
) {
const renderCell = useDefaultRenderers<R, SR>()!.renderCell!;

const handleRowChange = useLatestFunc((column: CalculatedColumn<R, SR>, newRow: R) => {
onRowChange(column, rowIdx, newRow);
});
Expand Down Expand Up @@ -68,21 +70,20 @@ function Row<R, SR>(
cells.push(selectedCellEditor);
} else {
cells.push(
<Cell
key={column.key}
column={column}
colSpan={colSpan}
row={row}
rowIdx={rowIdx}
isCopied={copiedCellIdx === idx}
isDraggedOver={draggedOverCellIdx === idx}
isCellSelected={isCellSelected}
onClick={onCellClick}
onDoubleClick={onCellDoubleClick}
onContextMenu={onCellContextMenu}
onRowChange={handleRowChange}
selectCell={selectCell}
/>
renderCell(column.key, {
column,
colSpan,
row,
rowIdx,
isCopied: copiedCellIdx === idx,
isDraggedOver: draggedOverCellIdx === idx,
isCellSelected,
onClick: onCellClick,
onDoubleClick: onCellDoubleClick,
onContextMenu: onCellContextMenu,
onRowChange: handleRowChange,
selectCell
})
);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export { default, type DataGridProps, type DataGridHandle } from './DataGrid';
export { default as TreeDataGrid, type TreeDataGridProps } from './TreeDataGrid';
export { DataGridDefaultRenderersProvider } from './DataGridDefaultRenderersProvider';
export { default as Row } from './Row';
export { default as Cell } from './Cell';
export * from './Columns';
export * from './cellRenderers';
export { default as textEditor } from './editors/textEditor';
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export interface CellRendererProps<TRow, TSummaryRow>
extends Pick<RenderRowProps<TRow, TSummaryRow>, 'row' | 'rowIdx' | 'selectCell'>,
Omit<
React.HTMLAttributes<HTMLDivElement>,
'style' | 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu'
'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu'
> {
column: CalculatedColumn<TRow, TSummaryRow>;
colSpan: number | undefined;
Expand Down Expand Up @@ -312,6 +312,7 @@ export interface Renderers<TRow, TSummaryRow> {
renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>;
renderRow?: Maybe<(key: Key, props: RenderRowProps<TRow, TSummaryRow>) => ReactNode>;
renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>;
renderCell?: Maybe<(key: Key, props: CellRendererProps<TRow, TSummaryRow>) => ReactNode>;
noRowsFallback?: Maybe<ReactNode>;
}

Expand Down
66 changes: 60 additions & 6 deletions test/browser/renderers.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,19 @@ import DataGrid, {
renderSortIcon,
SelectColumn
} from '../../src';
import type { Column, DataGridProps, RenderSortStatusProps, SortColumn } from '../../src';
import { getHeaderCells, getRows, setup } from './utils';
import type {
CellRendererProps,
Column,
DataGridProps,
RenderSortStatusProps,
SortColumn
} from '../../src';
import { getCells, getHeaderCells, getRows, setup } from './utils';

interface Row {
id: number;
col1: string;
col2: string;
}

const noRows: readonly Row[] = [];
Expand All @@ -30,6 +38,22 @@ const columns: readonly Column<Row>[] = [
}
];

function globalCellRenderer(key: React.Key, props: CellRendererProps<Row, unknown>) {
return (
<div key={key} role="gridcell">
{props.row[props.column.key as keyof Row]}
</div>
);
}

function localCellRenderer(key: React.Key) {
return (
<div key={key} role="gridcell">
local
</div>
);
}

function NoRowsFallback() {
return <div>Local no rows fallback</div>;
}
Expand Down Expand Up @@ -76,7 +100,8 @@ function setupProvider<R, SR, K extends React.Key>(props: DataGridProps<R, SR, K
value={{
noRowsFallback: <GlobalNoRowsFallback />,
renderCheckbox: globalRenderCheckbox,
renderSortStatus: globalSortStatus
renderSortStatus: globalSortStatus,
renderCell: globalCellRenderer
}}
>
<TestGrid {...props} />
Expand Down Expand Up @@ -106,21 +131,29 @@ test('fallback defined using both provider and renderers with no rows', () => {
});

test('fallback defined using renderers prop with a row', () => {
setup({ columns, rows: [{ id: 1 }], renderers: { noRowsFallback: <NoRowsFallback /> } });
setup({
columns,
rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }],
renderers: { noRowsFallback: <NoRowsFallback /> }
});

expect(getRows()).toHaveLength(1);
expect(screen.queryByText('Local no rows fallback')).not.toBeInTheDocument();
});

test('fallback defined using provider with a row', () => {
setupProvider({ columns, rows: [{ id: 1 }] });
setupProvider({ columns, rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }] });

expect(getRows()).toHaveLength(1);
expect(screen.queryByText('Global no rows fallback')).not.toBeInTheDocument();
});

test('fallback defined using both provider and renderers with a row', () => {
setupProvider({ columns, rows: [{ id: 1 }], renderers: { noRowsFallback: <NoRowsFallback /> } });
setupProvider({
columns,
rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }],
renderers: { noRowsFallback: <NoRowsFallback /> }
});

expect(getRows()).toHaveLength(1);
expect(screen.queryByText('Global no rows fallback')).not.toBeInTheDocument();
Expand Down Expand Up @@ -180,3 +213,24 @@ test('sortPriority defined using both providers and renderers', async () => {

expect(screen.queryByTestId('global-sort-priority')).not.toBeInTheDocument();
});

test('renderCell defined using provider', () => {
setupProvider({ columns, rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }] });

const [, cell1, cell2] = getCells();
expect(cell1).toHaveTextContent('col 1 value');
expect(cell2).toHaveTextContent('col 2 value');
});

test('renderCell defined using both providers and renderers', () => {
setupProvider({
columns,
rows: [{ id: 1, col1: 'col 1 value', col2: 'col 2 value' }],
renderers: { renderCell: localCellRenderer }
});

const [selectCell, cell1, cell2] = getCells();
expect(selectCell).toHaveTextContent('local');
expect(cell1).toHaveTextContent('local');
expect(cell2).toHaveTextContent('local');
});