89 lines
3.0 KiB
TypeScript
89 lines
3.0 KiB
TypeScript
"use client";
|
|
|
|
import { StickyNote } from "lucide-react";
|
|
|
|
import { cn } from "@/lib/utils";
|
|
import { formatTimeDue } from "@/lib/time-of-day";
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
import { MouseFollowTooltip } from "@/components/board/mouse-follow-tooltip";
|
|
import type { HoldPhase } from "@/components/hold-on-complete";
|
|
import type { ScheduledOccurrenceDTO } from "@/types/scheduled-todo";
|
|
|
|
/** One occurrence row -- "emphasis" is how loud this date's section reads
|
|
* (Overdue/Today get their own text treatment; every other day relies on
|
|
* ScheduledPanel fading its whole section via opacity instead). */
|
|
export function ScheduledTodoItem({
|
|
occurrence,
|
|
emphasis = "normal",
|
|
holdPhase,
|
|
onToggle,
|
|
onEdit,
|
|
}: {
|
|
occurrence: ScheduledOccurrenceDTO;
|
|
emphasis?: "overdue" | "today" | "normal";
|
|
// Only ever set for Overdue rows -- see ExpandedPanel/useHoldOnComplete.
|
|
// A freshly-checked-off one rides this through "visible" -> "fading" ->
|
|
// "collapsing" before ExpandedPanel actually drops it from the list.
|
|
holdPhase?: HoldPhase;
|
|
onToggle: (completed: boolean) => void;
|
|
onEdit: () => void;
|
|
}) {
|
|
const titleButton = (
|
|
<button
|
|
type="button"
|
|
onClick={onEdit}
|
|
className={cn(
|
|
"min-w-0 flex-1 truncate text-left text-sm hover:underline",
|
|
occurrence.completed
|
|
? "text-muted-foreground line-through"
|
|
: emphasis === "overdue"
|
|
? "font-semibold text-destructive"
|
|
: emphasis === "today"
|
|
? "font-medium text-foreground"
|
|
: "text-foreground"
|
|
)}
|
|
>
|
|
{occurrence.title}
|
|
</button>
|
|
);
|
|
|
|
return (
|
|
<li
|
|
className="grid transition-[grid-template-rows] duration-200 ease-in"
|
|
style={{ gridTemplateRows: holdPhase === "collapsing" ? "0fr" : "1fr" }}
|
|
>
|
|
<div className="overflow-hidden">
|
|
<div
|
|
className="flex items-start gap-2 py-1 transition-opacity duration-1000 ease-in"
|
|
style={{ opacity: holdPhase === "fading" || holdPhase === "collapsing" ? 0 : 1 }}
|
|
>
|
|
<Checkbox
|
|
checked={occurrence.completed}
|
|
onCheckedChange={(checked) => onToggle(!!checked)}
|
|
className="mt-0.5"
|
|
aria-label={`Mark "${occurrence.title}" ${occurrence.completed ? "incomplete" : "complete"}`}
|
|
/>
|
|
{occurrence.details ? (
|
|
<MouseFollowTooltip content={occurrence.details}>{titleButton}</MouseFollowTooltip>
|
|
) : (
|
|
titleButton
|
|
)}
|
|
{occurrence.timeDue && !occurrence.completed && (
|
|
<span
|
|
className={cn(
|
|
"mt-0.5 shrink-0 text-xs",
|
|
emphasis === "overdue" ? "font-semibold text-destructive" : "text-muted-foreground"
|
|
)}
|
|
>
|
|
{formatTimeDue(occurrence.timeDue)}
|
|
</span>
|
|
)}
|
|
{occurrence.details && (
|
|
<StickyNote className="mt-0.5 size-3 shrink-0 text-muted-foreground/70" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|