From 3fca962c9fc4da0e334a543b0a898df48b7b024c Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 18 Apr 2026 16:40:21 +0100 Subject: [PATCH] feat(base): row-number cell renders checkbox + drag handle on hover --- .../base/components/grid/grid-cell.tsx | 21 +++--- .../base/components/grid/grid-container.tsx | 1 + .../base/components/grid/grid-row.tsx | 7 +- .../base/components/grid/row-number-cell.tsx | 70 +++++++++++++++++++ .../src/features/base/styles/grid.module.css | 51 ++++++++++++++ 5 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 apps/client/src/features/base/components/grid/row-number-cell.tsx diff --git a/apps/client/src/features/base/components/grid/grid-cell.tsx b/apps/client/src/features/base/components/grid/grid-cell.tsx index f8ff3ddc..b043e4f3 100644 --- a/apps/client/src/features/base/components/grid/grid-cell.tsx +++ b/apps/client/src/features/base/components/grid/grid-cell.tsx @@ -18,6 +18,7 @@ import { CellFile } from "@/features/base/components/cells/cell-file"; import { CellCreatedAt } from "@/features/base/components/cells/cell-created-at"; import { CellLastEditedAt } from "@/features/base/components/cells/cell-last-edited-at"; import { CellLastEditedBy } from "@/features/base/components/cells/cell-last-edited-by"; +import { RowNumberCell } from "./row-number-cell"; import classes from "@/features/base/styles/grid.module.css"; type CellComponentProps = { @@ -59,6 +60,7 @@ type GridCellProps = { rowIndex: number; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; rowDragProps?: RowDragProps; + orderedRowIds?: string[]; }; export const GridCell = memo(function GridCell({ @@ -66,6 +68,7 @@ export const GridCell = memo(function GridCell({ rowIndex, onCellUpdate, rowDragProps, + orderedRowIds, }: GridCellProps) { const property = cell.column.columnDef.meta?.property; const isRowNumber = cell.column.id === "__row_number"; @@ -107,16 +110,14 @@ export const GridCell = memo(function GridCell({ if (isRowNumber) { return ( -
- {rowIndex + 1} -
+ ); } diff --git a/apps/client/src/features/base/components/grid/grid-container.tsx b/apps/client/src/features/base/components/grid/grid-container.tsx index b478ba98..803cf161 100644 --- a/apps/client/src/features/base/components/grid/grid-container.tsx +++ b/apps/client/src/features/base/components/grid/grid-container.tsx @@ -227,6 +227,7 @@ export function GridContainer({ row={row} rowIndex={virtualRow.index} onCellUpdate={onCellUpdate} + orderedRowIds={rowIds} dragHandlers={ onRowReorder ? { diff --git a/apps/client/src/features/base/components/grid/grid-row.tsx b/apps/client/src/features/base/components/grid/grid-row.tsx index 75c6b352..b24be01f 100644 --- a/apps/client/src/features/base/components/grid/grid-row.tsx +++ b/apps/client/src/features/base/components/grid/grid-row.tsx @@ -1,6 +1,7 @@ import { memo, useCallback } from "react"; import { Row } from "@tanstack/react-table"; import { IBaseRow } from "@/features/base/types/base.types"; +import { useRowSelection } from "@/features/base/hooks/use-row-selection"; import { GridCell } from "./grid-cell"; import classes from "@/features/base/styles/grid.module.css"; @@ -19,6 +20,7 @@ type GridRowProps = { rowIndex: number; onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void; dragHandlers?: RowDragHandlers; + orderedRowIds: string[]; }; export const GridRow = memo(function GridRow({ @@ -26,7 +28,9 @@ export const GridRow = memo(function GridRow({ rowIndex, onCellUpdate, dragHandlers, + orderedRowIds, }: GridRowProps) { + const isSelected = useRowSelection().isSelected(row.id); const handleDragStart = useCallback( (e: React.DragEvent) => { e.dataTransfer.effectAllowed = "move"; @@ -51,7 +55,7 @@ export const GridRow = memo(function GridRow({ return (
{ @@ -68,6 +72,7 @@ export const GridRow = memo(function GridRow({ cell={cell} rowIndex={rowIndex} onCellUpdate={onCellUpdate} + orderedRowIds={orderedRowIds} rowDragProps={ isRowNumber && dragHandlers ? { diff --git a/apps/client/src/features/base/components/grid/row-number-cell.tsx b/apps/client/src/features/base/components/grid/row-number-cell.tsx new file mode 100644 index 00000000..8c114425 --- /dev/null +++ b/apps/client/src/features/base/components/grid/row-number-cell.tsx @@ -0,0 +1,70 @@ +import { memo, useCallback } from "react"; +import { Checkbox } from "@mantine/core"; +import { IconGripVertical } from "@tabler/icons-react"; +import { useRowSelection } from "@/features/base/hooks/use-row-selection"; +import classes from "@/features/base/styles/grid.module.css"; + +type RowDragProps = { + draggable: boolean; + onDragStart: (e: React.DragEvent) => void; +}; + +type RowNumberCellProps = { + rowId: string; + rowIndex: number; + orderedRowIds: string[]; + isPinned: boolean; + pinOffset?: number; + rowDragProps?: RowDragProps; +}; + +export const RowNumberCell = memo(function RowNumberCell({ + rowId, + rowIndex, + orderedRowIds, + isPinned, + pinOffset, + rowDragProps, +}: RowNumberCellProps) { + const { isSelected, toggle } = useRowSelection(); + const selected = isSelected(rowId); + + const handleCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + const nativeEvent = e.nativeEvent as MouseEvent; + toggle(rowId, { + shiftKey: nativeEvent.shiftKey === true, + rowIndex, + orderedRowIds, + }); + }, + [rowId, rowIndex, orderedRowIds, toggle], + ); + + return ( +
+
+ + + + + + + {rowIndex + 1} +
+
+ ); +}); diff --git a/apps/client/src/features/base/styles/grid.module.css b/apps/client/src/features/base/styles/grid.module.css index a9597713..e66a8199 100644 --- a/apps/client/src/features/base/styles/grid.module.css +++ b/apps/client/src/features/base/styles/grid.module.css @@ -296,3 +296,54 @@ .primaryCell { font-weight: 500; } + +.rowNumberCellInner { + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + width: 100%; + height: 100%; + padding: 0 6px; +} + +.rowNumberIndex { + display: inline; +} + +.rowNumberCheckbox, +.rowNumberDragHandle { + display: none; + align-items: center; + justify-content: center; +} + +.rowNumberDragHandle { + color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); + cursor: grab; +} + +.rowNumberDragHandle:active { + cursor: grabbing; +} + +/* On hover, hide the index and show drag handle + checkbox */ +.row:hover .rowNumberIndex { + display: none; +} +.row:hover .rowNumberCheckbox, +.row:hover .rowNumberDragHandle { + display: inline-flex; +} + +/* When selected, checkbox always visible; index + drag handle hidden */ +.rowSelected .rowNumberIndex, +.rowSelected .rowNumberDragHandle { + display: none; +} +.rowSelected .rowNumberCheckbox { + display: inline-flex; +} +.rowSelected .cell { + background: light-dark(var(--mantine-color-blue-0), var(--mantine-color-dark-6)); +}