Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add columns reordering example #2007

Merged
merged 11 commits into from
Apr 20, 2020
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
- ⚠️ `onGridKeyDown`
- ⚠️ `onGridKeyUp`
- ⚠️ `onRowDoubleClick`
- ⚠️ `onHeaderDrop`
- ⚠️ `draggableHeaderCell`
- Check [#2007](https://github.com/adazzle/react-data-grid/pull/2007) on how to migrate
- ⚠️ `rowsContainer`
- ⚠️ Subrow props: `getSubRowDetails`, `onCellExpand`, `onDeleteSubRow`, and `onAddSubRow`
- Check [#1853](https://github.com/adazzle/react-data-grid/pull/1853) on how to migrate
Expand All @@ -56,6 +59,7 @@
- Check [#1845](https://github.com/adazzle/react-data-grid/pull/1845) on how to migrate
- ⚠️ `column.getRowMetaData`
- ⚠️ `column.filterable`
- ⚠️ `column.draggable`
- ⚠️ `cellRangeSelection.{onStart,onUpdate,onEnd}`
- ⚠️ `fromRowId`, `toRowId`, and `fromRowData` from `onRowsUpdate` argument
- ⚠️ Stopped exporting `HeaderCell`
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
"mini-css-extract-plugin": "^0.9.0",
"react": "^16.13.1",
"react-contextmenu": "^2.13.0",
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dom": "^16.13.1",
"react-select": "^3.1.0",
"react-virtualized": "^9.21.2",
Expand Down
5 changes: 0 additions & 5 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@ export interface DataGridProps<R, K extends keyof R, SR = unknown> {
rowRenderer?: React.ComponentType<RowRendererProps<R, SR>>;
rowGroupRenderer?: React.ComponentType;
emptyRowsView?: React.ComponentType<{}>;
/** Component used to render a draggable header cell */
draggableHeaderCell?: React.ComponentType<{ column: CalculatedColumn<R, SR>; onHeaderDrop: () => void }>;

/**
* Event props
Expand All @@ -126,7 +124,6 @@ export interface DataGridProps<R, K extends keyof R, SR = unknown> {
onScroll?: (scrollPosition: ScrollPosition) => void;
/** Called when a column is resized */
onColumnResize?: (idx: number, width: number) => void;
onHeaderDrop?: () => void;
onRowExpandToggle?: (event: RowExpandToggleEvent) => void;
/** Function called whenever selected cell is changed */
onSelectedCellChange?: (position: Position) => void;
Expand Down Expand Up @@ -427,8 +424,6 @@ function DataGrid<R, K extends keyof R, SR>({
columns={viewportColumns}
onColumnResize={handleColumnResize}
lastFrozenColumnIndex={lastFrozenColumnIndex}
draggableHeaderCell={props.draggableHeaderCell}
onHeaderDrop={props.onHeaderDrop}
allRowsSelected={selectedRows?.size === rows.length}
onSelectedRowsChange={onSelectedRowsChange}
sortColumn={props.sortColumn}
Expand Down
18 changes: 0 additions & 18 deletions src/HeaderCell.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ interface Row {
}

describe('HeaderCell', () => {
function DraggableHeaderCell() {
return <div />;
}

function setup(overrideProps = {}, columnProps = {}) {
const props: HeaderCellProps<Row, unknown> = {
column: {
Expand All @@ -27,8 +23,6 @@ describe('HeaderCell', () => {
},
lastFrozenColumnIndex: -1,
onResize: jest.fn(),
onHeaderDrop() { },
draggableHeaderCell: DraggableHeaderCell,
allRowsSelected: false,
onAllRowsSelectionChange() {},
...overrideProps
Expand All @@ -53,16 +47,4 @@ describe('HeaderCell', () => {
expect(props.onResize).toHaveBeenCalledWith(props.column, 200);
});
});

describe('Render draggableHeaderCell', () => {
it('should not render DraggableHeaderCell when draggable is false', () => {
const { wrapper } = setup({}, { draggable: false });
expect(wrapper.find(DraggableHeaderCell)).toHaveLength(0);
});

it('should not render DraggableHeaderCell when draggable is true', () => {
const { wrapper } = setup({}, { draggable: true });
expect(wrapper.find(DraggableHeaderCell)).toHaveLength(1);
});
});
});
14 changes: 0 additions & 14 deletions src/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ type SharedHeaderRowProps<R, SR> = Pick<HeaderRowProps<R, never, SR>,
| 'sortColumn'
| 'sortDirection'
| 'onSort'
| 'onHeaderDrop'
| 'allRowsSelected'
| 'draggableHeaderCell'
>;

export interface HeaderCellProps<R, SR> extends SharedHeaderRowProps<R, SR> {
Expand Down Expand Up @@ -78,17 +76,5 @@ export default function HeaderCell<R, SR>({
);
}

const DraggableHeaderCell = props.draggableHeaderCell;
if (column.draggable && DraggableHeaderCell) {
return (
<DraggableHeaderCell
column={column}
onHeaderDrop={props.onHeaderDrop!}
>
{cell}
</DraggableHeaderCell>
);
}

return cell;
}
8 changes: 2 additions & 6 deletions src/HeaderRow.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ describe('HeaderRow', () => {
onColumnResize() { },
onSort: jest.fn(),
sortDirection: 'NONE',
allRowsSelected: false,
onHeaderDrop() { },
draggableHeaderCell: () => <div />
allRowsSelected: false
};

const setup = (testProps?: Partial<HeaderRowProps<Row, 'id', unknown>>) => {
Expand Down Expand Up @@ -77,9 +75,7 @@ describe('HeaderRow', () => {
lastFrozenColumnIndex: 1,
onSort: jest.fn(),
allRowsSelected: false,
onColumnResize: jest.fn(),
onHeaderDrop() { },
draggableHeaderCell: () => <div />
onColumnResize: jest.fn()
};

it('passes classname property', () => {
Expand Down
4 changes: 0 additions & 4 deletions src/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { assertIsValidKey } from './utils';
import { DataGridProps } from './DataGrid';

type SharedDataGridProps<R, K extends keyof R, SR> = Pick<DataGridProps<R, K, SR>,
| 'draggableHeaderCell'
| 'rows'
| 'onHeaderDrop'
| 'onSelectedRowsChange'
| 'sortColumn'
| 'sortDirection'
Expand Down Expand Up @@ -53,10 +51,8 @@ function HeaderRow<R, K extends keyof R, SR>({
column={column}
lastFrozenColumnIndex={props.lastFrozenColumnIndex}
onResize={props.onColumnResize}
onHeaderDrop={props.onHeaderDrop}
allRowsSelected={props.allRowsSelected}
onAllRowsSelectionChange={handleAllRowsSelectionChange}
draggableHeaderCell={props.draggableHeaderCell}
onSort={props.onSort}
sortColumn={props.sortColumn}
sortDirection={props.sortDirection}
Expand Down
4 changes: 1 addition & 3 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export interface Column<TRow, TSummaryRow = unknown> {
summaryFormatter?: React.ComponentType<SummaryFormatterProps<TSummaryRow, TRow>>;
/** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */
editable?: boolean | ((row: TRow) => boolean);
/** Enable dragging of a column */
draggable?: boolean;
/** Determines whether column is frozen or not */
frozen?: boolean;
/** Enable resizing of a column */
Expand Down Expand Up @@ -115,7 +113,7 @@ export interface EditorProps<TValue, TRow = any, TSummaryRow = any> {
onOverrideKeyDown: (e: KeyboardEvent) => void;
}

export interface HeaderRendererProps<TRow, TSummaryRow> {
export interface HeaderRendererProps<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
allRowsSelected: boolean;
onAllRowsSelectionChange: (checked: boolean) => void;
Expand Down
2 changes: 1 addition & 1 deletion stories/demos/CellNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function createRows(): Row[] {
return rows;
}

export default function ScrollToRow() {
export default function CellNavigation() {
const [rows] = useState(createRows);
const [cellNavigatioMode, setCellNavigationMode] = useState<CellNavigationMode>(CellNavigationMode.CHANGE_ROW);

Expand Down
130 changes: 130 additions & 0 deletions stories/demos/ColumnsReordering.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import React, { useState, useCallback, useMemo } from 'react';
import { DndProvider } from 'react-dnd';
import Backend from 'react-dnd-html5-backend';

import { DraggableHeaderRenderer } from './components/HeaderRenderers';
import DataGrid, { Column, HeaderRendererProps, SortDirection } from '../../src';

interface Row {
id: number;
task: string;
complete: number;
priority: string;
issueType: string;
}

function createRows(): Row[] {
const rows = [];
for (let i = 1; i < 500; i++) {
rows.push({
id: i,
task: `Task ${i}`,
complete: Math.min(100, Math.round(Math.random() * 110)),
priority: ['Critical', 'High', 'Medium', 'Low'][Math.floor((Math.random() * 3) + 1)],
issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.floor((Math.random() * 3) + 1)]
});
}

return rows;
}

function createColumns(): Column<Row>[] {
return [
{
key: 'id',
name: 'ID',
width: 80
},
{
key: 'task',
name: 'Title',
resizable: true,
sortable: true
},
{
key: 'priority',
name: 'Priority',
resizable: true,
sortable: true
},
{
key: 'issueType',
name: 'Issue Type',
resizable: true,
sortable: true
},
{
key: 'complete',
name: '% Complete',
resizable: true,
sortable: true
}
];
}

export default function ColumnsReordering() {
const [rows] = useState(createRows);
const [columns, setColumns] = useState(createColumns);
const [[sortColumn, sortDirection], setSort] = useState<[string, SortDirection]>(['task', 'NONE']);

const handleSort = useCallback((columnKey: string, direction: SortDirection) => {
setSort([columnKey, direction]);
}, []);

const draggableColumns = useMemo(() => {
function HeaderRenderer(props: HeaderRendererProps<Row>) {
return <DraggableHeaderRenderer {...props} onColumnsReorder={handleColumnsReorder} />;
}

function handleColumnsReorder(sourceKey: string, targetKey: string) {
const sourceColumnIndex = columns.findIndex(c => c.key === sourceKey)!;
const targetColumnIndex = columns.findIndex(c => c.key === targetKey)!;
amanmahajan7 marked this conversation as resolved.
Show resolved Hide resolved
const reorderedColumns = [...columns];

reorderedColumns.splice(
targetColumnIndex,
0,
reorderedColumns.splice(sourceColumnIndex, 1)[0]
);

setColumns(reorderedColumns);
}

return columns.map(c => {
if (c.key === 'id') return c;
return { ...c, headerRenderer: HeaderRenderer };
});
}, [columns]);

const sortedRows = useMemo((): readonly Row[] => {
if (sortDirection === 'NONE') return rows;

let sortedRows: Row[] = [...rows];

switch (sortColumn) {
case 'task':
case 'priority':
case 'issueType':
sortedRows = sortedRows.sort((a, b) => a[sortColumn].localeCompare(b[sortColumn]));
break;
case 'complete':
sortedRows = sortedRows.sort((a, b) => a[sortColumn] - b[sortColumn]);
break;
default:
}

return sortDirection === 'DESC' ? sortedRows.reverse() : sortedRows;
}, [rows, sortDirection, sortColumn]);

return (
<DndProvider backend={Backend}>
<DataGrid
columns={draggableColumns}
rows={sortedRows}
sortColumn={sortColumn}
sortDirection={sortDirection}
onSort={handleSort}
/>
</DndProvider>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
Copy link
Contributor

@qili26 qili26 Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just not sure about this file. What do you guys think if we provide this in the DataGrid and export this as a default DraggableHeaderRenderer?

pros: consumer devs can just use the default column function;
cons: 1. we might need to provide more APIs. 2. We have to include the react-dnd as a dependency...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to keep the dependencies to a minimum and provide a flexible API so users can write their own implementations. Composition is always more maintainable than adding extra props

Copy link
Contributor

@nstepien nstepien Apr 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it's extra work to maintain the feature + dependency on other libs.
That way users can use whatever implementation they want to header dragging.

import { useDrag, useDrop, DragObjectWithType } from 'react-dnd';

import { HeaderRendererProps } from '../../../../src';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we don't need this empty line.


interface ColumnDragObject extends DragObjectWithType {
key: string;
}

function wrapRefs<T>(...refs: React.Ref<T>[]) {
return (handle: T | null) => {
for (const ref of refs) {
if (typeof ref === 'function') {
ref(handle);
} else if (ref !== null) {
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31065
(ref as React.MutableRefObject<T | null>).current = handle;
}
}
};
}

export function DraggableHeaderRenderer<R>({ onColumnsReorder, ...props }: HeaderRendererProps<R> & { onColumnsReorder: (sourceKey: string, targetKey: string) => void }) {
const [{ isDragging }, drag] = useDrag({
item: { key: props.column.key, type: 'COLUMN_DRAG' },
collect: monitor => ({
isDragging: !!monitor.isDragging()
})
});

const [{ isOver }, drop] = useDrop({
accept: 'COLUMN_DRAG',
drop({ key, type }: ColumnDragObject) {
if (type === 'COLUMN_DRAG') {
onColumnsReorder(key, props.column.key);
}
},
collect: monitor => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop()
})
});

return (
<div
ref={wrapRefs(drag, drop)}
style={{
opacity: isDragging ? 0.5 : 1,
backgroundColor: isOver ? '#ececec' : 'inherit',
cursor: 'move'
}}
>
{props.column.name}
</div>
);
}
1 change: 1 addition & 0 deletions stories/demos/components/HeaderRenderers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DraggableHeaderRenderer';
4 changes: 3 additions & 1 deletion stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ContextMenu from './demos/ContextMenu';
import ScrollToRow from './demos/ScrollToRow';
import CellNavigation from './demos/CellNavigation';
import HeaderFilters from './demos/HeaderFilters';
import ColumnsReordering from './demos/ColumnsReordering';

storiesOf('Demos', module)
.add('Common Features', () => <CommonFeatures />)
Expand All @@ -25,4 +26,5 @@ storiesOf('Demos', module)
.add('Context Menu', () => <ContextMenu />)
.add('Scroll To Row', () => <ScrollToRow />)
.add('Cell Navigation', () => <CellNavigation />)
.add('Header Filters', () => <HeaderFilters />);
.add('Header Filters', () => <HeaderFilters />)
.add('Columns Reordering', () => <ColumnsReordering />);