feat(board): open a card without breaking drag

This commit is contained in:
Dave 2026-09-16 11:05:33 +02:00
parent c4a8ad2860
commit 07656a5bc7
3 changed files with 35 additions and 6 deletions

View file

@ -31,6 +31,8 @@ export default async function BoardPage({
<Board
role={session.role}
projectId={board.project.id}
clientSlug={clientSlug}
projectSlug={projectSlug}
columns={board.columns}
initialCards={board.cards}
/>

View file

@ -28,10 +28,14 @@ import type { CardRecord, ColumnRecord, Role } from "@/lib/pb/types";
function SortableCard({
card,
role,
clientSlug,
projectSlug,
onPatched,
}: {
card: CardRecord;
role: Role;
clientSlug: string;
projectSlug: string;
onPatched: (card: CardRecord) => void;
}) {
const draggable = role === "owner";
@ -43,6 +47,7 @@ function SortableCard({
ref={setNodeRef}
card={card}
dragging={isDragging}
href={`/p/${clientSlug}/${projectSlug}/c/${card.id}`}
style={{ transform: CSS.Translate.toString(transform), transition }}
className={draggable ? "cursor-grab active:cursor-grabbing" : undefined}
{...(draggable ? attributes : {})}
@ -81,11 +86,15 @@ function Droppable({
export function Board({
role,
projectId,
clientSlug,
projectSlug,
columns,
initialCards,
}: {
role: Role;
projectId: string;
clientSlug: string;
projectSlug: string;
columns: ColumnRecord[];
initialCards: CardRecord[];
}) {
@ -218,6 +227,8 @@ export function Board({
key={card.id}
card={card}
role={role}
clientSlug={clientSlug}
projectSlug={projectSlug}
onPatched={onPatched}
/>
))}

View file

@ -1,7 +1,8 @@
"use client";
import { forwardRef } from "react";
import { Inbox, CalendarDays } from "lucide-react";
import Link from "next/link";
import { Inbox, CalendarDays, Maximize2 } from "lucide-react";
import { Pill } from "@/components/ui/pill";
import { cn } from "@/lib/cn";
@ -27,10 +28,11 @@ export const CardTile = forwardRef<
{
card: CardRecord;
dragging?: boolean;
href?: string;
className?: string;
children?: React.ReactNode;
} & React.HTMLAttributes<HTMLDivElement>
>(function CardTile({ card, dragging, className, children, ...props }, ref) {
>(function CardTile({ card, dragging, href, className, children, ...props }, ref) {
const due = classifyDueDate(card.target_date);
const dueLabel = formatDueDate(card.target_date);
@ -38,16 +40,30 @@ export const CardTile = forwardRef<
<div
ref={ref}
className={cn(
"rounded-[var(--radius-tile)] border border-border-subtle bg-panel p-3",
"group/tile rounded-[var(--radius-tile)] border border-border-subtle bg-panel p-3",
"shadow-[var(--shadow-tile)] transition-shadow",
dragging && "shadow-[var(--shadow-drag)] opacity-90",
className
)}
{...props}
>
<p className="text-[13px] font-medium leading-[1.45] text-text-primary">
{card.title}
</p>
<div className="flex items-start gap-2">
<p className="min-w-0 flex-1 text-[13px] font-medium leading-[1.45] text-text-primary">
{card.title}
</p>
{href && (
<Link
href={href}
aria-label={`Open ${card.title}`}
// Stops the pointer reaching the drag listeners on the tile, so
// opening a card never begins a drag.
onPointerDown={(event) => event.stopPropagation()}
className="shrink-0 rounded p-0.5 text-text-muted opacity-0 transition-opacity hover:text-text-primary focus-visible:opacity-100 group-hover/tile:opacity-100"
>
<Maximize2 size={13} strokeWidth={2} />
</Link>
)}
</div>
<div className="mt-2.5 flex flex-wrap items-center gap-1.5">
<Pill tone={PRIORITY_TONE[card.priority]}>