Adjustments to Drag and Drop features, as well as theme features

This commit is contained in:
Brian Fertig 2026-08-11 13:26:10 -06:00
parent 9a1aad6ed0
commit 0d7336858c
3 changed files with 31 additions and 9 deletions

View File

@ -2,7 +2,7 @@
import { useSortable } from "@dnd-kit/sortable";
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
import { useDroppable } from "@dnd-kit/core";
import { useDndContext, useDroppable } from "@dnd-kit/core";
import { CSS } from "@dnd-kit/utilities";
import { GripVertical, MoreVertical, Trash2 } from "lucide-react";
@ -21,6 +21,19 @@ import type { CategoryDTO } from "@/types/board";
export function CategoryLane({ category }: { category: CategoryDTO }) {
const { removeCategory } = useBoard();
// The lane itself is sortable (so lanes can be reordered), which
// registers its *entire* rect -- header, cards, empty space, all of it --
// as a droppable. Dragging a group card over empty lane space then had
// two overlapping droppables to resolve against: this lane's own
// "category" droppable and the narrower "category-dropzone" below. With
// `closestCenter`, the much larger lane rect often won, so drop handling
// never saw a "group" or "category-dropzone" target and silently did
// nothing. Disabling this lane's droppable side while a *group* (not a
// lane) is being dragged removes it from collision detection entirely,
// leaving only the group cards and the dropzone as valid targets.
const { active } = useDndContext();
const isDraggingGroup = active?.data.current?.type === "group";
const {
setNodeRef,
setActivatorNodeRef,
@ -32,6 +45,7 @@ export function CategoryLane({ category }: { category: CategoryDTO }) {
} = useSortable({
id: category.id,
data: { type: "category" },
disabled: { draggable: false, droppable: isDraggingGroup },
});
const { setNodeRef: setDropzoneRef } = useDroppable({
@ -75,7 +89,7 @@ export function CategoryLane({ category }: { category: CategoryDTO }) {
<DropdownMenuContent align="end">
<DropdownMenuItem
variant="destructive"
onSelect={() => removeCategory(category.id)}
onClick={() => removeCategory(category.id)}
>
<Trash2 className="size-4" />
Delete category

View File

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useCallback, useState } from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useTheme } from "next-themes";
@ -47,10 +47,18 @@ export function GroupCard({ group }: { group: GroupDTO }) {
// portaled Popover/Dialog content nested inside the card (the notes
// editor, the "add to-do" popover). Wiring the same node as the
// activator restores the target check.
function setCardRef(node: HTMLDivElement | null) {
setNodeRef(node);
setActivatorNodeRef(node);
}
//
// This must be memoized: a fresh function identity on every render makes
// React detach+reattach the ref each time, which during a drag (many
// re-renders as transform/isDragging change) resets dnd-kit's node
// registration and broke drop detection entirely.
const setCardRef = useCallback(
(node: HTMLDivElement | null) => {
setNodeRef(node);
setActivatorNodeRef(node);
},
[setNodeRef, setActivatorNodeRef]
);
const color = getGroupColor(group.color);
const borderColor = resolvedTheme === "dark" ? color.dark : color.light;
@ -90,7 +98,7 @@ export function GroupCard({ group }: { group: GroupDTO }) {
<DropdownMenuContent align="end">
<DropdownMenuItem
variant="destructive"
onSelect={() => removeGroup(group.id, group.categoryId)}
onClick={() => removeGroup(group.id, group.categoryId)}
>
<Trash2 className="size-4" />
Delete group

View File

@ -38,7 +38,7 @@ export function ThemeToggle({ collapsed }: { collapsed?: boolean }) {
/>
<DropdownMenuContent align="start" side="right">
{OPTIONS.map((option) => (
<DropdownMenuItem key={option.value} onSelect={() => setTheme(option.value)}>
<DropdownMenuItem key={option.value} onClick={() => setTheme(option.value)}>
<option.icon className="size-4" />
{option.label}
</DropdownMenuItem>