feat: custom calendar date picker replacing native input
Build fully custom CalendarPopover with date-fns + Radix Popover. Month/year dropdown selectors, today button, clear button, past dates dimmed. Rewrite DueDatePicker to use it instead of <input type=date>.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { format, formatDistanceToNow, isPast, isToday } from "date-fns";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { X } from "lucide-react";
|
||||
import { useBoardStore } from "@/stores/board-store";
|
||||
import { CalendarPopover } from "@/components/card-detail/CalendarPopover";
|
||||
|
||||
interface DueDatePickerProps {
|
||||
cardId: string;
|
||||
@@ -13,70 +14,67 @@ export function DueDatePicker({ cardId, dueDate }: DueDatePickerProps) {
|
||||
const dateObj = dueDate ? new Date(dueDate) : null;
|
||||
const overdue = dateObj != null && isPast(dateObj) && !isToday(dateObj);
|
||||
|
||||
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const val = e.target.value;
|
||||
updateCard(cardId, { dueDate: val || null });
|
||||
function handleSelect(date: Date) {
|
||||
updateCard(cardId, { dueDate: format(date, "yyyy-MM-dd") });
|
||||
}
|
||||
|
||||
function handleClear() {
|
||||
updateCard(cardId, { dueDate: null });
|
||||
}
|
||||
|
||||
// Format the date value for the HTML date input (YYYY-MM-DD)
|
||||
const inputValue = dateObj
|
||||
? format(dateObj, "yyyy-MM-dd")
|
||||
: "";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Header */}
|
||||
<h4 className="font-mono text-xs uppercase tracking-widest text-pylon-text-secondary">
|
||||
Due Date
|
||||
</h4>
|
||||
|
||||
{/* Current date display */}
|
||||
{dateObj && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text"
|
||||
}`}
|
||||
>
|
||||
{format(dateObj, "MMM d, yyyy")}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text-secondary"
|
||||
}`}
|
||||
>
|
||||
{overdue
|
||||
? `overdue by ${formatDistanceToNow(dateObj)}`
|
||||
: isToday(dateObj)
|
||||
? "today"
|
||||
: `in ${formatDistanceToNow(dateObj)}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Date input + clear */}
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="date"
|
||||
value={inputValue}
|
||||
onChange={handleChange}
|
||||
className="h-7 rounded-md border border-pylon-text-secondary/20 bg-pylon-column px-2 text-xs text-pylon-text outline-none focus:border-pylon-accent focus:ring-1 focus:ring-pylon-accent"
|
||||
/>
|
||||
{/* Header with clear button */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-mono text-xs uppercase tracking-widest text-pylon-text-secondary">
|
||||
Due Date
|
||||
</h4>
|
||||
{dueDate && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
<button
|
||||
onClick={handleClear}
|
||||
className="text-pylon-text-secondary hover:text-pylon-danger"
|
||||
className="rounded p-0.5 text-pylon-text-secondary transition-colors hover:bg-pylon-danger/10 hover:text-pylon-danger"
|
||||
aria-label="Clear due date"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<X className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Clickable date display -> opens calendar */}
|
||||
<CalendarPopover
|
||||
selectedDate={dateObj}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
>
|
||||
<button className="flex w-full items-center gap-2 rounded-md px-1 py-1 text-left transition-colors hover:bg-pylon-column/60">
|
||||
{dateObj ? (
|
||||
<>
|
||||
<span
|
||||
className={`text-sm font-medium ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text"
|
||||
}`}
|
||||
>
|
||||
{format(dateObj, "MMM d, yyyy")}
|
||||
</span>
|
||||
<span
|
||||
className={`text-xs ${
|
||||
overdue ? "text-pylon-danger" : "text-pylon-text-secondary"
|
||||
}`}
|
||||
>
|
||||
{overdue
|
||||
? `overdue by ${formatDistanceToNow(dateObj)}`
|
||||
: isToday(dateObj)
|
||||
? "today"
|
||||
: `in ${formatDistanceToNow(dateObj)}`}
|
||||
</span>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-sm italic text-pylon-text-secondary/60">
|
||||
Click to set date...
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</CalendarPopover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user