+ {/* 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