diff --git a/src/components/Table/GridTable.stories.tsx b/src/components/Table/GridTable.stories.tsx index 51adefbcb..244f11175 100644 --- a/src/components/Table/GridTable.stories.tsx +++ b/src/components/Table/GridTable.stories.tsx @@ -2020,3 +2020,48 @@ export function DraggableNestedRows() { /> ); } + +export function DraggableCardRows() { + const dragColumn = dragHandleColumn({}); + const nameColumn: GridColumn = { + header: "Name", + data: ({ name }) => ({ content:
{name}
, sortValue: name }), + }; + + const actionColumn: GridColumn = { + header: "Action", + data: () =>
Actions
, + clientSideSort: false, + }; + + let rowArray: GridDataRow[] = new Array(26).fill(0); + rowArray = rowArray.map((elem, idx) => ({ + kind: "data", + id: "" + (idx + 1), + order: idx + 1, + data: { name: "" + (idx + 1), value: idx + 1 }, + draggable: true, + })); + + const [rows, setRows] = useState[]>([simpleHeader, ...rowArray]); + + // also works with as="table" and as="virtual" + return ( + { + const tempRows = [...rows]; + // remove dragged row + const draggedRowIndex = tempRows.findIndex((r) => r.id === draggedRow.id); + const reorderRow = tempRows.splice(draggedRowIndex, 1)[0]; + + const droppedRowIndex = tempRows.findIndex((r) => r.id === droppedRow.id); + + // insert it at the dropped row index + setRows([...insertAtIndex(tempRows, reorderRow, droppedRowIndex + indexOffset)]); + }} + rows={[...rows]} + style={cardStyle} + /> + ); +} diff --git a/src/components/Table/GridTable.tsx b/src/components/Table/GridTable.tsx index c104d18d0..cc9bd0698 100644 --- a/src/components/Table/GridTable.tsx +++ b/src/components/Table/GridTable.tsx @@ -352,7 +352,11 @@ export function GridTable = an evt.preventDefault(); // set flags for css spacer - recursiveSetDraggedOver(rows, DraggedOver.None); + // don't set none for the row we are entering + recursiveSetDraggedOver( + rows.filter((r) => r.id !== row.id), + DraggedOver.None, + ); if (draggedRowRef.current) { if (draggedRowRef.current.id === row.id) { @@ -373,7 +377,8 @@ export function GridTable = an evt.preventDefault(); if (draggedRowRef.current) { - if (draggedRowRef.current.id === row.id) { + if (draggedRowRef.current.id === row.id || !evt.currentTarget) { + tableState.maybeSetRowDraggedOver(row.id, DraggedOver.None, draggedRowRef.current); return; } diff --git a/src/components/Table/components/Row.tsx b/src/components/Table/components/Row.tsx index fbf92a72d..eebbe9970 100644 --- a/src/components/Table/components/Row.tsx +++ b/src/components/Table/components/Row.tsx @@ -1,5 +1,5 @@ import { observer } from "mobx-react"; -import { ReactElement, useContext, useRef } from "react"; +import { ReactElement, useContext, useRef, useCallback } from "react"; import { defaultRenderFn, headerRenderFn, @@ -31,7 +31,7 @@ import { import { Css, Palette } from "src/Css"; import { AnyObject } from "src/types"; import { isFunction } from "src/utils"; -import { Icon } from "src"; +import { useDebouncedCallback } from "use-debounce"; interface RowProps { as: RenderAs; @@ -93,8 +93,13 @@ function RowImpl(props: RowProps): ReactElement { const rowStyleCellCss = maybeApplyFunction(row as any, rowStyle?.cellCss); const levelIndent = style.levels && style.levels[level]?.rowIndent; + const containerCss = { + ...Css.add("transition", "padding 0.25s ease-in-out").$, + ...(rs.isDraggedOver === DraggedOver.Above && Css.ptPx(25).$), + ...(rs.isDraggedOver === DraggedOver.Below && Css.pbPx(25).$), + }; + const rowCss = { - ...Css.add("transition", "padding 0.5s ease-in-out").$, ...(!reservedRowKinds.includes(row.kind) && style.nonHeaderRowCss), // Optionally include the row hover styles, by default they should be turned on. ...(showRowHoverColor && { @@ -117,8 +122,6 @@ function RowImpl(props: RowProps): ReactElement { [`:hover > .${revealOnRowHoverClass} > *`]: Css.visible.$, }, ...(isLastKeptRow && Css.addIn("&>*", style.keptLastRowCss).$), - ...(rs.isDraggedOver === DraggedOver.Above && Css.add("paddingTop", "35px").$), - ...(rs.isDraggedOver === DraggedOver.Below && Css.add("paddingBottom", "35px").$), }; let currentColspan = 1; @@ -131,36 +134,16 @@ function RowImpl(props: RowProps): ReactElement { // used to render the whole row when dragging with the handle const ref = useRef(null); - return ( - onDrop?.(row, evt)} - onDragEnter={(evt) => onDragEnter?.(row, evt)} - onDragOver={(evt) => onDragOver?.(row, evt)} - ref={ref} - > - {/* {row.draggable && ( -
{ - // show the whole row being dragged when dragging with the handle - ref.current && evt.dataTransfer.setDragImage(ref.current, 0, 0); - return onDragStart?.(row, evt); - }} - onDragEnd={(evt) => onDragEnd?.(row, evt)} - onDrop={(evt) => onDrop?.(row, evt)} - onDragEnter={(evt) => onDragEnter?.(row, evt)} - onDragOver={(evt) => onDragOver?.(row, evt)} - css={Css.mh100.ma.$} - > - -
- )} */} + // debounce drag over callback to avoid excessive re-renders + const dragOverCallback = useCallback( + (row: GridDataRow, evt: React.DragEvent) => onDragOver?.(row, evt), + [onDragOver], + ); + // when the event is not called, we still need to call preventDefault + const onDragOverDebounced = useDebouncedCallback(dragOverCallback, 100); + + const RowContent = () => ( + {isKeptGroupRow ? ( ) : ( @@ -227,7 +210,7 @@ function RowImpl(props: RowProps): ReactElement { onDragEnd, onDrop, onDragEnter, - onDragOver, + onDragOver: onDragOverDebounced, }); // Only use the `numExpandedColumns` as the `colspan` when rendering the "Expandable Header" @@ -372,6 +355,27 @@ function RowImpl(props: RowProps): ReactElement { )} ); + + return row.draggable ? ( +
onDrop?.(row, evt)} + onDragEnter={(evt) => onDragEnter?.(row, evt)} + onDragOver={(evt) => { + // when the event isn't called due to debounce, we still need to + // call preventDefault for the drop event to fire + evt.preventDefault(); + onDragOverDebounced(row, evt); + }} + ref={ref} + > + {RowContent()} +
+ ) : ( + <>{RowContent()} + ); } /** diff --git a/src/components/Table/utils/RowStates.ts b/src/components/Table/utils/RowStates.ts index 823029a33..41e009001 100644 --- a/src/components/Table/utils/RowStates.ts +++ b/src/components/Table/utils/RowStates.ts @@ -203,6 +203,7 @@ export class RowStates { } // this allows a single-row re-render + if (rs.isDraggedOver === draggedOver) return; rs.isDraggedOver = draggedOver; } } diff --git a/src/components/Table/utils/columns.tsx b/src/components/Table/utils/columns.tsx index 3c96b20c7..c022ff1f2 100644 --- a/src/components/Table/utils/columns.tsx +++ b/src/components/Table/utils/columns.tsx @@ -200,32 +200,22 @@ export function dragHandleColumn(columnDef?: Partial { - // return (data: any, { row, level }: { row: GridDataRow; level: number }) => ({ - // content: 0} />, - // }); - // }) as any; - return newMethodMissingProxy(base, (key) => { return (data: any, { row, dragData }: { row: GridDataRow; dragData: DragData }) => { if (!dragData) return; const { rowRenderRef: ref, onDragStart, onDragEnd, onDrop, onDragEnter, onDragOver } = dragData; return { - // how do we get the callbacks and the ref here? - // inject them into the row in the Row component? content: row.draggable ? (
rect.top + pt + (rect.height - pb) / 2; }