feat: Phase 2 card interactions - priority picker, context menu, WIP limits, column collapse, checklist reorder
- PriorityPicker component with 5 colored chips in card detail modal - Card context menu: Move to column, Set priority, Duplicate, Delete - duplicateCard store action (clones card, inserts after original) - Column WIP limits with amber/red indicators when at/over limit - Column collapse/expand to 40px vertical strip - Checklist item drag reordering with grip handle - Comment store actions (addComment, deleteComment) for Phase 3
This commit is contained in:
@@ -3,8 +3,11 @@ import { MoreHorizontal } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
@@ -17,7 +20,6 @@ import type { Column, ColumnWidth } from "@/types/board";
|
||||
interface ColumnHeaderProps {
|
||||
column: Column;
|
||||
cardCount: number;
|
||||
boardColor?: string;
|
||||
}
|
||||
|
||||
const COLOR_PRESETS = [
|
||||
@@ -33,7 +35,7 @@ const COLOR_PRESETS = [
|
||||
{ hue: "0", label: "Slate" },
|
||||
];
|
||||
|
||||
export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProps) {
|
||||
export function ColumnHeader({ column, cardCount }: ColumnHeaderProps) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editValue, setEditValue] = useState(column.title);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
@@ -42,6 +44,8 @@ export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProp
|
||||
const deleteColumn = useBoardStore((s) => s.deleteColumn);
|
||||
const setColumnWidth = useBoardStore((s) => s.setColumnWidth);
|
||||
const setColumnColor = useBoardStore((s) => s.setColumnColor);
|
||||
const setColumnWipLimit = useBoardStore((s) => s.setColumnWipLimit);
|
||||
const toggleColumnCollapse = useBoardStore((s) => s.toggleColumnCollapse);
|
||||
|
||||
useEffect(() => {
|
||||
if (editing && inputRef.current) {
|
||||
@@ -74,13 +78,7 @@ export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProp
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between border-b border-border px-3 pb-2 pt-3" style={{
|
||||
borderTop: column.color
|
||||
? `3px solid oklch(55% 0.12 ${column.color})`
|
||||
: boardColor
|
||||
? `3px solid ${boardColor}30`
|
||||
: undefined
|
||||
}}>
|
||||
<div className="flex items-center justify-between border-b border-border px-3 pb-2 pt-3">
|
||||
<div className="flex items-center gap-2 overflow-hidden">
|
||||
{editing ? (
|
||||
<input
|
||||
@@ -102,8 +100,14 @@ export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProp
|
||||
{column.title}
|
||||
</span>
|
||||
)}
|
||||
<span className="shrink-0 font-mono text-xs text-pylon-text-secondary">
|
||||
{cardCount}
|
||||
<span className={`shrink-0 font-mono text-xs ${
|
||||
column.wipLimit != null && cardCount > column.wipLimit
|
||||
? "text-pylon-danger font-bold"
|
||||
: column.wipLimit != null && cardCount === column.wipLimit
|
||||
? "text-[oklch(65%_0.15_70)]"
|
||||
: "text-pylon-text-secondary"
|
||||
}`}>
|
||||
{cardCount}{column.wipLimit != null ? `/${column.wipLimit}` : ""}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -126,38 +130,28 @@ export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProp
|
||||
>
|
||||
Rename
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => toggleColumnCollapse(column.id)}>
|
||||
Collapse
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Width</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => handleWidthChange("narrow")}>
|
||||
Narrow
|
||||
{column.width === "narrow" && (
|
||||
<span className="ml-auto text-pylon-accent">*</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleWidthChange("standard")}>
|
||||
Standard
|
||||
{column.width === "standard" && (
|
||||
<span className="ml-auto text-pylon-accent">*</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => handleWidthChange("wide")}>
|
||||
Wide
|
||||
{column.width === "wide" && (
|
||||
<span className="ml-auto text-pylon-accent">*</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuRadioGroup value={column.width} onValueChange={(v) => handleWidthChange(v as ColumnWidth)}>
|
||||
<DropdownMenuRadioItem value="narrow">Narrow</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="standard">Standard</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="wide">Wide</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>Color</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuItem onClick={() => setColumnColor(column.id, null)}>
|
||||
<DropdownMenuCheckboxItem
|
||||
checked={column.color == null}
|
||||
onSelect={() => setColumnColor(column.id, null)}
|
||||
>
|
||||
None
|
||||
{column.color == null && (
|
||||
<span className="ml-auto text-pylon-accent">*</span>
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuCheckboxItem>
|
||||
<DropdownMenuSeparator />
|
||||
<div className="flex flex-wrap gap-1.5 px-2 py-1.5">
|
||||
{COLOR_PRESETS.map(({ hue, label }) => (
|
||||
@@ -176,6 +170,21 @@ export function ColumnHeader({ column, cardCount, boardColor }: ColumnHeaderProp
|
||||
</div>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>WIP Limit</DropdownMenuSubTrigger>
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={column.wipLimit?.toString() ?? "none"}
|
||||
onValueChange={(v) => setColumnWipLimit(column.id, v === "none" ? null : parseInt(v))}
|
||||
>
|
||||
<DropdownMenuRadioItem value="none">None</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="3">3</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="5">5</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="7">7</DropdownMenuRadioItem>
|
||||
<DropdownMenuRadioItem value="10">10</DropdownMenuRadioItem>
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
|
||||
Reference in New Issue
Block a user