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));
+}