diff --git a/components/board/category-lane.tsx b/components/board/category-lane.tsx index dd5caaf..9795d4d 100644 --- a/components/board/category-lane.tsx +++ b/components/board/category-lane.tsx @@ -177,7 +177,11 @@ export function CategoryLane({ category }: { category: CategoryDTO }) { className="grid transition-[grid-template-rows] duration-200 ease-in" style={{ gridTemplateRows: holdPhase === "collapsing" ? "0fr" : "1fr" }} > -
+ {/* overflow-hidden only while actually shrinking (grid-template-rows + easing to 0fr above) -- at rest it clips nothing, but clipped + unconditionally it also cropped the card's own hover lift + (hover:-translate-y-0.5 in GroupCard), cutting off its top stroke. */} +
- {quickTargetGroup ? "Quickly add a to-do to the first group" : "Add a group first, then quickly add to-dos"} + {quickTargetGroup ? "Quickly add a to-do to any group" : "Add a group first, then quickly add to-dos"} @@ -197,6 +197,7 @@ function Board({ title }: { title: string }) { diff --git a/components/board/todo-create-dialog.tsx b/components/board/todo-create-dialog.tsx index 3be5a3d..d5278d8 100644 --- a/components/board/todo-create-dialog.tsx +++ b/components/board/todo-create-dialog.tsx @@ -6,9 +6,17 @@ import { Button } from "@/components/ui/button"; import { colorModeFromTheme } from "@/components/theme/use-dark-theme"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { MDEditor } from "@/components/markdown/markdown-widgets"; import { useBoard } from "@/components/board/board-context"; +import type { CategoryDTO } from "@/types/board"; const TITLE_MAX = 20; @@ -19,11 +27,21 @@ const TITLE_MAX = 20; export function TodoCreateDialog({ groupId, categoryId, + categories, open, onOpenChange, }: { + // Default/only target group+category. When `categories` is also passed, + // this is just the pre-selected option in the "Group" dropdown below, + // not the final destination. groupId: string; categoryId: string; + // Pass the full board (or leave undefined) to show a "Group" dropdown + // letting the user redirect the to-do elsewhere -- used by the quick-add + // button, whose default target is otherwise just "whichever group + // happens to be first". Omitted from the per-group "+" menu, where the + // group is already unambiguous from context. + categories?: CategoryDTO[]; open: boolean; onOpenChange: (open: boolean) => void; }) { @@ -32,12 +50,23 @@ export function TodoCreateDialog({ const [title, setTitle] = useState(""); const [details, setDetails] = useState(""); + const [selectedGroupId, setSelectedGroupId] = useState(groupId); const [pending, setPending] = useState(false); + const groupOptions = categories?.flatMap((c) => + c.groups.map((g) => ({ groupId: g.id, categoryId: c.id, label: `${c.name} - ${g.title}` })) + ); + // Only meaningful when `categories` is passed -- otherwise the dialog's + // single fixed target (the props above) is used as-is. + const targetCategoryId = + groupOptions?.find((o) => o.groupId === selectedGroupId)?.categoryId ?? categoryId; + const targetGroupId = groupOptions ? selectedGroupId : groupId; + function handleOpenChange(next: boolean) { if (!next) { setTitle(""); setDetails(""); + setSelectedGroupId(groupId); } onOpenChange(next); } @@ -47,7 +76,7 @@ export function TodoCreateDialog({ const trimmed = title.trim(); if (!trimmed) return; setPending(true); - const ok = await addTodo(groupId, categoryId, trimmed, details.trim() || undefined); + const ok = await addTodo(targetGroupId, targetCategoryId, trimmed, details.trim() || undefined); setPending(false); if (ok) handleOpenChange(false); } @@ -59,6 +88,34 @@ export function TodoCreateDialog({ Add to-do
+ {groupOptions && ( +
+ + +
+ )}