mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat(base): row-number cell renders checkbox + drag handle on hover
This commit is contained in:
@@ -18,6 +18,7 @@ import { CellFile } from "@/features/base/components/cells/cell-file";
|
|||||||
import { CellCreatedAt } from "@/features/base/components/cells/cell-created-at";
|
import { CellCreatedAt } from "@/features/base/components/cells/cell-created-at";
|
||||||
import { CellLastEditedAt } from "@/features/base/components/cells/cell-last-edited-at";
|
import { CellLastEditedAt } from "@/features/base/components/cells/cell-last-edited-at";
|
||||||
import { CellLastEditedBy } from "@/features/base/components/cells/cell-last-edited-by";
|
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";
|
import classes from "@/features/base/styles/grid.module.css";
|
||||||
|
|
||||||
type CellComponentProps = {
|
type CellComponentProps = {
|
||||||
@@ -59,6 +60,7 @@ type GridCellProps = {
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
||||||
rowDragProps?: RowDragProps;
|
rowDragProps?: RowDragProps;
|
||||||
|
orderedRowIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridCell = memo(function GridCell({
|
export const GridCell = memo(function GridCell({
|
||||||
@@ -66,6 +68,7 @@ export const GridCell = memo(function GridCell({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
onCellUpdate,
|
onCellUpdate,
|
||||||
rowDragProps,
|
rowDragProps,
|
||||||
|
orderedRowIds,
|
||||||
}: GridCellProps) {
|
}: GridCellProps) {
|
||||||
const property = cell.column.columnDef.meta?.property;
|
const property = cell.column.columnDef.meta?.property;
|
||||||
const isRowNumber = cell.column.id === "__row_number";
|
const isRowNumber = cell.column.id === "__row_number";
|
||||||
@@ -107,16 +110,14 @@ export const GridCell = memo(function GridCell({
|
|||||||
|
|
||||||
if (isRowNumber) {
|
if (isRowNumber) {
|
||||||
return (
|
return (
|
||||||
<div
|
<RowNumberCell
|
||||||
className={`${classes.cell} ${classes.rowNumberCell} ${isPinned ? classes.cellPinned : ""} ${rowDragProps ? classes.rowNumberDraggable : ""}`}
|
rowId={rowId}
|
||||||
style={{
|
rowIndex={rowIndex}
|
||||||
...(isPinned ? { left: pinOffset } : {}),
|
orderedRowIds={orderedRowIds ?? []}
|
||||||
}}
|
isPinned={Boolean(isPinned)}
|
||||||
draggable={rowDragProps?.draggable}
|
pinOffset={pinOffset}
|
||||||
onDragStart={rowDragProps?.onDragStart}
|
rowDragProps={rowDragProps}
|
||||||
>
|
/>
|
||||||
{rowIndex + 1}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -227,6 +227,7 @@ export function GridContainer({
|
|||||||
row={row}
|
row={row}
|
||||||
rowIndex={virtualRow.index}
|
rowIndex={virtualRow.index}
|
||||||
onCellUpdate={onCellUpdate}
|
onCellUpdate={onCellUpdate}
|
||||||
|
orderedRowIds={rowIds}
|
||||||
dragHandlers={
|
dragHandlers={
|
||||||
onRowReorder
|
onRowReorder
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { memo, useCallback } from "react";
|
import { memo, useCallback } from "react";
|
||||||
import { Row } from "@tanstack/react-table";
|
import { Row } from "@tanstack/react-table";
|
||||||
import { IBaseRow } from "@/features/base/types/base.types";
|
import { IBaseRow } from "@/features/base/types/base.types";
|
||||||
|
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
|
||||||
import { GridCell } from "./grid-cell";
|
import { GridCell } from "./grid-cell";
|
||||||
import classes from "@/features/base/styles/grid.module.css";
|
import classes from "@/features/base/styles/grid.module.css";
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ type GridRowProps = {
|
|||||||
rowIndex: number;
|
rowIndex: number;
|
||||||
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
onCellUpdate: (rowId: string, propertyId: string, value: unknown) => void;
|
||||||
dragHandlers?: RowDragHandlers;
|
dragHandlers?: RowDragHandlers;
|
||||||
|
orderedRowIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridRow = memo(function GridRow({
|
export const GridRow = memo(function GridRow({
|
||||||
@@ -26,7 +28,9 @@ export const GridRow = memo(function GridRow({
|
|||||||
rowIndex,
|
rowIndex,
|
||||||
onCellUpdate,
|
onCellUpdate,
|
||||||
dragHandlers,
|
dragHandlers,
|
||||||
|
orderedRowIds,
|
||||||
}: GridRowProps) {
|
}: GridRowProps) {
|
||||||
|
const isSelected = useRowSelection().isSelected(row.id);
|
||||||
const handleDragStart = useCallback(
|
const handleDragStart = useCallback(
|
||||||
(e: React.DragEvent) => {
|
(e: React.DragEvent) => {
|
||||||
e.dataTransfer.effectAllowed = "move";
|
e.dataTransfer.effectAllowed = "move";
|
||||||
@@ -51,7 +55,7 @@ export const GridRow = memo(function GridRow({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${classes.row} ${dragHandlers?.isDragging ? classes.rowDragging : ""} ${dropIndicatorClass}`}
|
className={`${classes.row} ${dragHandlers?.isDragging ? classes.rowDragging : ""} ${dropIndicatorClass} ${isSelected ? classes.rowSelected : ""}`}
|
||||||
role="row"
|
role="row"
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
onDrop={(e) => {
|
onDrop={(e) => {
|
||||||
@@ -68,6 +72,7 @@ export const GridRow = memo(function GridRow({
|
|||||||
cell={cell}
|
cell={cell}
|
||||||
rowIndex={rowIndex}
|
rowIndex={rowIndex}
|
||||||
onCellUpdate={onCellUpdate}
|
onCellUpdate={onCellUpdate}
|
||||||
|
orderedRowIds={orderedRowIds}
|
||||||
rowDragProps={
|
rowDragProps={
|
||||||
isRowNumber && dragHandlers
|
isRowNumber && dragHandlers
|
||||||
? {
|
? {
|
||||||
|
|||||||
@@ -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<HTMLInputElement>) => {
|
||||||
|
const nativeEvent = e.nativeEvent as MouseEvent;
|
||||||
|
toggle(rowId, {
|
||||||
|
shiftKey: nativeEvent.shiftKey === true,
|
||||||
|
rowIndex,
|
||||||
|
orderedRowIds,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[rowId, rowIndex, orderedRowIds, toggle],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`${classes.cell} ${classes.rowNumberCell} ${isPinned ? classes.cellPinned : ""}`}
|
||||||
|
style={isPinned ? { left: pinOffset } : undefined}
|
||||||
|
>
|
||||||
|
<div className={classes.rowNumberCellInner}>
|
||||||
|
<span
|
||||||
|
className={classes.rowNumberDragHandle}
|
||||||
|
draggable={rowDragProps?.draggable}
|
||||||
|
onDragStart={rowDragProps?.onDragStart}
|
||||||
|
aria-label="Drag row"
|
||||||
|
>
|
||||||
|
<IconGripVertical size={12} />
|
||||||
|
</span>
|
||||||
|
<span className={classes.rowNumberCheckbox}>
|
||||||
|
<Checkbox
|
||||||
|
size="xs"
|
||||||
|
checked={selected}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<span className={classes.rowNumberIndex}>{rowIndex + 1}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -296,3 +296,54 @@
|
|||||||
.primaryCell {
|
.primaryCell {
|
||||||
font-weight: 500;
|
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));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user