mirror of
https://github.com/docmost/docmost.git
synced 2026-06-10 01:52:43 +08:00
feat(bases): drop cards onto empty kanban columns / below last card
This commit is contained in:
@@ -4,6 +4,7 @@ import { KanbanCard } from "./kanban-card";
|
||||
import { KanbanColumnHeader } from "./kanban-column-header";
|
||||
import { KanbanAddCardButton } from "./kanban-add-card-button";
|
||||
import type { CardDropPayload } from "@/features/base/hooks/use-kanban-card-drag";
|
||||
import { useKanbanColumnDrop } from "@/features/base/hooks/use-kanban-column-drop";
|
||||
import classes from "@/features/base/styles/kanban.module.css";
|
||||
|
||||
type KanbanColumnProps = {
|
||||
@@ -21,6 +22,11 @@ export function KanbanColumn({
|
||||
onAddCard,
|
||||
onCardDrop,
|
||||
}: KanbanColumnProps) {
|
||||
const { ref: bodyRef, isOver } = useKanbanColumnDrop({
|
||||
columnKey: column.key,
|
||||
onDrop: onCardDrop,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classes.column} data-column-key={column.key}>
|
||||
<KanbanColumnHeader
|
||||
@@ -28,7 +34,12 @@ export function KanbanColumn({
|
||||
color={column.color}
|
||||
count={column.rows.length}
|
||||
/>
|
||||
<div className={classes.columnBody} data-column-body={column.key}>
|
||||
<div
|
||||
ref={bodyRef}
|
||||
className={classes.columnBody}
|
||||
data-column-body={column.key}
|
||||
data-over={isOver || undefined}
|
||||
>
|
||||
{column.rows.map((row) => (
|
||||
<KanbanCard
|
||||
key={row.id}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
||||
import type { CardDropPayload } from "./use-kanban-card-drag";
|
||||
|
||||
export const COLUMN_BODY_TARGET_ID = "__column-body__";
|
||||
|
||||
export function useKanbanColumnDrop({
|
||||
columnKey,
|
||||
onDrop,
|
||||
}: {
|
||||
columnKey: string;
|
||||
onDrop: (payload: CardDropPayload) => void;
|
||||
}) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [isOver, setIsOver] = useState(false);
|
||||
// Keep onDrop fresh without re-registering the effect each render.
|
||||
const onDropRef = useRef(onDrop);
|
||||
onDropRef.current = onDrop;
|
||||
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
if (!el) return;
|
||||
return dropTargetForElements({
|
||||
element: el,
|
||||
canDrop: ({ source }) => source.data.type === "base-kanban-card",
|
||||
getIsSticky: () => true,
|
||||
onDragEnter: () => setIsOver(true),
|
||||
onDragLeave: () => setIsOver(false),
|
||||
onDrop: ({ source }) => {
|
||||
setIsOver(false);
|
||||
if (source.data.type !== "base-kanban-card") return;
|
||||
// If a card-level target inside this column already handled the
|
||||
// drop, Pragmatic-dnd only invokes the innermost matching target,
|
||||
// so this column-body handler won't fire. When it does fire, the
|
||||
// user missed every card — append to the column.
|
||||
onDropRef.current({
|
||||
draggedCardId: source.data.cardId as string,
|
||||
targetCardId: COLUMN_BODY_TARGET_ID,
|
||||
edge: "bottom",
|
||||
sourceColumnKey: source.data.columnKey as string,
|
||||
targetColumnKey: columnKey,
|
||||
});
|
||||
},
|
||||
});
|
||||
}, [columnKey]);
|
||||
|
||||
return { ref, isOver };
|
||||
}
|
||||
@@ -52,6 +52,10 @@
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.columnBody[data-over="true"] {
|
||||
background: var(--mantine-color-blue-0);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 10px 12px;
|
||||
background: var(--mantine-color-body);
|
||||
|
||||
Reference in New Issue
Block a user