mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
feat(base): header select-all with tri-state checkbox
This commit is contained in:
@@ -210,6 +210,7 @@ export function GridContainer({
|
||||
table={table}
|
||||
baseId={baseId}
|
||||
columnOrder={table.getState().columnOrder}
|
||||
loadedRowIds={rowIds}
|
||||
onPropertyCreated={handlePropertyCreated}
|
||||
/>
|
||||
</SortableContext>
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
IconUserEdit,
|
||||
} from "@tabler/icons-react";
|
||||
import { PropertyMenuContent } from "@/features/base/components/property/property-menu";
|
||||
import { RowNumberHeaderCell } from "./row-number-header-cell";
|
||||
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
|
||||
import classes from "@/features/base/styles/grid.module.css";
|
||||
|
||||
const typeIcons: Record<string, typeof IconLetterT> = {
|
||||
@@ -44,10 +46,12 @@ const typeIcons: Record<string, typeof IconLetterT> = {
|
||||
|
||||
type GridHeaderCellProps = {
|
||||
header: Header<IBaseRow, unknown>;
|
||||
loadedRowIds: string[];
|
||||
};
|
||||
|
||||
export const GridHeaderCell = memo(function GridHeaderCell({
|
||||
header,
|
||||
loadedRowIds,
|
||||
}: GridHeaderCellProps) {
|
||||
const property = header.column.columnDef.meta?.property as
|
||||
| IBaseProperty
|
||||
@@ -55,6 +59,8 @@ export const GridHeaderCell = memo(function GridHeaderCell({
|
||||
const isRowNumber = header.column.id === "__row_number";
|
||||
const isPinned = header.column.getIsPinned();
|
||||
const pinOffset = isPinned ? header.column.getStart("left") : undefined;
|
||||
const { selectionCount } = useRowSelection();
|
||||
const hasSelection = selectionCount > 0;
|
||||
|
||||
const [activePropertyMenu, setActivePropertyMenu] = useAtom(activePropertyMenuAtom) as unknown as [string | null, (val: string | null) => void];
|
||||
const menuOpened = activePropertyMenu === header.column.id;
|
||||
@@ -118,7 +124,7 @@ export const GridHeaderCell = memo(function GridHeaderCell({
|
||||
return (
|
||||
<div
|
||||
ref={combinedRef}
|
||||
className={`${classes.headerCell} ${isPinned ? classes.headerCellPinned : ""}`}
|
||||
className={`${classes.headerCell} ${isPinned ? classes.headerCellPinned : ""} ${hasSelection ? classes.hasSelection : ""}`}
|
||||
style={{
|
||||
...(isPinned ? { left: pinOffset } : {}),
|
||||
...(isRowNumber ? {} : { cursor: "pointer" }),
|
||||
@@ -129,7 +135,7 @@ export const GridHeaderCell = memo(function GridHeaderCell({
|
||||
{...(isSortableDisabled ? {} : listeners)}
|
||||
>
|
||||
{isRowNumber ? (
|
||||
flexRender(header.column.columnDef.header, header.getContext())
|
||||
<RowNumberHeaderCell loadedRowIds={loadedRowIds} />
|
||||
) : (
|
||||
<div className={classes.headerCellContent}>
|
||||
{TypeIcon && (
|
||||
|
||||
@@ -11,6 +11,7 @@ type GridHeaderProps = {
|
||||
// Passed explicitly to break memo when columns change
|
||||
// (table ref is stable from useReactTable, so memo won't fire without this)
|
||||
columnOrder: ColumnOrderState;
|
||||
loadedRowIds: string[];
|
||||
onPropertyCreated?: () => void;
|
||||
};
|
||||
|
||||
@@ -19,6 +20,7 @@ export const GridHeader = memo(function GridHeader({
|
||||
baseId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
columnOrder: _columnOrder,
|
||||
loadedRowIds,
|
||||
onPropertyCreated,
|
||||
}: GridHeaderProps) {
|
||||
const headerGroups = table.getHeaderGroups();
|
||||
@@ -26,7 +28,7 @@ export const GridHeader = memo(function GridHeader({
|
||||
return (
|
||||
<div className={classes.headerRow} role="row">
|
||||
{headerGroups[0]?.headers.map((header) => (
|
||||
<GridHeaderCell key={header.id} header={header} />
|
||||
<GridHeaderCell key={header.id} header={header} loadedRowIds={loadedRowIds} />
|
||||
))}
|
||||
{baseId && (
|
||||
<CreatePropertyPopover
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { memo, useMemo } from "react";
|
||||
import { Checkbox, Tooltip } from "@mantine/core";
|
||||
import { useRowSelection } from "@/features/base/hooks/use-row-selection";
|
||||
import classes from "@/features/base/styles/grid.module.css";
|
||||
|
||||
type RowNumberHeaderCellProps = {
|
||||
loadedRowIds: string[];
|
||||
};
|
||||
|
||||
export const RowNumberHeaderCell = memo(function RowNumberHeaderCell({
|
||||
loadedRowIds,
|
||||
}: RowNumberHeaderCellProps) {
|
||||
const { selectedIds, toggleAll } = useRowSelection();
|
||||
|
||||
const { checked, indeterminate } = useMemo(() => {
|
||||
if (loadedRowIds.length === 0) {
|
||||
return { checked: false, indeterminate: false };
|
||||
}
|
||||
const selectedInLoaded = loadedRowIds.reduce(
|
||||
(acc, id) => (selectedIds.has(id) ? acc + 1 : acc),
|
||||
0,
|
||||
);
|
||||
return {
|
||||
checked: selectedInLoaded === loadedRowIds.length,
|
||||
indeterminate:
|
||||
selectedInLoaded > 0 && selectedInLoaded < loadedRowIds.length,
|
||||
};
|
||||
}, [loadedRowIds, selectedIds]);
|
||||
|
||||
if (loadedRowIds.length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className={classes.rowNumberHeaderInner}>
|
||||
<span className={classes.rowNumberHeaderHash}>#</span>
|
||||
<span className={classes.rowNumberHeaderCheckbox}>
|
||||
<Tooltip label="Select all loaded rows" withinPortal>
|
||||
<Checkbox
|
||||
size="xs"
|
||||
checked={checked}
|
||||
indeterminate={indeterminate}
|
||||
onChange={() => toggleAll(loadedRowIds)}
|
||||
aria-label="Select all loaded rows"
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -347,3 +347,30 @@
|
||||
.rowSelected .cell {
|
||||
background: light-dark(var(--mantine-color-blue-0), var(--mantine-color-dark-6));
|
||||
}
|
||||
|
||||
.rowNumberHeaderInner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.rowNumberHeaderHash {
|
||||
color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
|
||||
font-size: var(--mantine-font-size-xs);
|
||||
}
|
||||
|
||||
.rowNumberHeaderCheckbox {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.headerCell:hover .rowNumberHeaderHash,
|
||||
.hasSelection .rowNumberHeaderHash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.headerCell:hover .rowNumberHeaderCheckbox,
|
||||
.hasSelection .rowNumberHeaderCheckbox {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user