# Custom Date Picker — Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Replace the native HTML date input with a fully custom calendar widget that matches the app's dark OKLCH theme. **Architecture:** Create a new `CalendarPopover` component (calendar grid + month/year selectors + footer) using date-fns for date math and Radix Popover for positioning. Rewrite `DueDatePicker` to use it instead of ``. No new dependencies. **Tech Stack:** React 19, TypeScript, date-fns v4, Framer Motion 12, Tailwind 4, Radix Popover --- ### Task 1: Create CalendarPopover component **Files:** - Create: `src/components/card-detail/CalendarPopover.tsx` **Step 1: Create the file with the complete component** Create `src/components/card-detail/CalendarPopover.tsx` with: ```tsx import { useState, useMemo } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { startOfMonth, endOfMonth, startOfWeek, endOfWeek, eachDayOfInterval, format, isSameDay, isSameMonth, isToday as isTodayFn, isPast, addMonths, subMonths, setMonth, setYear, getYear, getMonth, } from "date-fns"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Button } from "@/components/ui/button"; import { springs } from "@/lib/motion"; interface CalendarPopoverProps { selectedDate: Date | null; onSelect: (date: Date) => void; onClear: () => void; children: React.ReactNode; } type ViewMode = "days" | "months" | "years"; const MONTH_NAMES = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; const WEEKDAYS = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]; export function CalendarPopover({ selectedDate, onSelect, onClear, children, }: CalendarPopoverProps) { const [open, setOpen] = useState(false); const [viewDate, setViewDate] = useState(() => selectedDate ?? new Date()); const [viewMode, setViewMode] = useState("days"); // Reset view when opening function handleOpenChange(nextOpen: boolean) { if (nextOpen) { setViewDate(selectedDate ?? new Date()); setViewMode("days"); } setOpen(nextOpen); } function handleSelectDate(date: Date) { onSelect(date); setOpen(false); } function handleToday() { const today = new Date(); onSelect(today); setOpen(false); } function handleClear() { onClear(); setOpen(false); } // Build the 6×7 grid of days for the current viewDate month const calendarDays = useMemo(() => { const monthStart = startOfMonth(viewDate); const monthEnd = endOfMonth(viewDate); const gridStart = startOfWeek(monthStart, { weekStartsOn: 1 }); // Monday const gridEnd = endOfWeek(monthEnd, { weekStartsOn: 1 }); return eachDayOfInterval({ start: gridStart, end: gridEnd }); }, [viewDate]); // Year range for year selector: current year ± 5 const yearRange = useMemo(() => { const center = getYear(viewDate); const years: number[] = []; for (let y = center - 5; y <= center + 5; y++) { years.push(y); } return years; }, [viewDate]); return ( {children} {/* Navigation header */}
{/* Body: days / months / years */}
{viewMode === "days" && ( {/* Weekday headers */}
{WEEKDAYS.map((wd) => (
{wd}
))}
{/* Day grid */}
{calendarDays.map((day) => { const inMonth = isSameMonth(day, viewDate); const today = isTodayFn(day); const selected = selectedDate != null && isSameDay(day, selectedDate); const past = isPast(day) && !today; if (!inMonth) { return
; } return ( ); })}
)} {viewMode === "months" && ( {MONTH_NAMES.map((name, i) => ( ))} )} {viewMode === "years" && ( {yearRange.map((year) => ( ))} )}
{/* Footer */}
); } ``` **Step 2: Verify TypeScript compiles** Run: `npx tsc --noEmit` Expected: No errors **Step 3: Commit** ```bash git add src/components/card-detail/CalendarPopover.tsx git commit -m "feat: create custom CalendarPopover component" ``` --- ### Task 2: Rewrite DueDatePicker to use CalendarPopover **Files:** - Modify: `src/components/card-detail/DueDatePicker.tsx` (full rewrite) **Step 1: Replace the entire file** Replace `src/components/card-detail/DueDatePicker.tsx` with: ```tsx import { format, formatDistanceToNow, isPast, isToday } from "date-fns"; import { X } from "lucide-react"; import { useBoardStore } from "@/stores/board-store"; import { CalendarPopover } from "@/components/card-detail/CalendarPopover"; interface DueDatePickerProps { cardId: string; dueDate: string | null; } export function DueDatePicker({ cardId, dueDate }: DueDatePickerProps) { const updateCard = useBoardStore((s) => s.updateCard); const dateObj = dueDate ? new Date(dueDate) : null; const overdue = dateObj != null && isPast(dateObj) && !isToday(dateObj); function handleSelect(date: Date) { updateCard(cardId, { dueDate: format(date, "yyyy-MM-dd") }); } function handleClear() { updateCard(cardId, { dueDate: null }); } return (
{/* Header with clear button */}

Due Date

{dueDate && ( )}
{/* Clickable date display → opens calendar */}
); } ``` **Step 2: Verify TypeScript compiles** Run: `npx tsc --noEmit` Expected: No errors **Step 3: Commit** ```bash git add src/components/card-detail/DueDatePicker.tsx git commit -m "feat: rewrite DueDatePicker to use custom CalendarPopover" ``` --- ### Task 3: Visual verification and final commit **Step 1: Run TypeScript check** Run: `npx tsc --noEmit` Expected: No errors **Step 2: Run the dev server** Run: `npx tauri dev` Verify: - Open a card → Due Date cell shows "Click to set date..." or the current date - Click the cell → calendar popover appears below - Calendar shows correct month with today highlighted (ring) - Click a date → it's selected (filled accent), popover closes, cell shows formatted date - Click month name → month selector grid appears, click a month → returns to days - Click year → year selector grid appears, click a year → returns to days - Left/right arrows navigate months - "Today" button selects today and closes - "Clear" button in popover footer removes the date and closes - × button in cell header clears the date without opening calendar - Past dates are dimmed but clickable - Overdue dates show in red **Step 3: Commit** ```bash git add -A git commit -m "feat: custom date picker with calendar popover complete" ```