Files
thrilltrack-explorer/src/components/ui/flexible-date-input.tsx
gpt-engineer-app[bot] 2f579b08ba Add date precision help text
Extend FlexibleDateInput with contextual help text for all precision options (exact, month, year, decade, century, approximate), including an Info icon import and dynamic guidance displayed under the dropdown. Also prepare for enhanced SelectItem labels and optional tooltip enhancements.
2025-11-11 22:56:15 +00:00

211 lines
6.4 KiB
TypeScript

import * as React from "react";
import { format } from "date-fns";
import { CalendarIcon, Info } from "lucide-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { DatePicker } from "@/components/ui/date-picker";
import { MonthYearPicker } from "@/components/ui/month-year-picker";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { toDateOnly, toDateWithPrecision } from "@/lib/dateUtils";
export type DatePrecision = 'exact' | 'month' | 'year' | 'decade' | 'century' | 'approximate';
interface FlexibleDateInputProps {
value?: Date;
precision?: DatePrecision;
onChange: (date: Date | undefined, precision: DatePrecision) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
disableFuture?: boolean;
disablePast?: boolean;
fromYear?: number;
toYear?: number;
label?: string;
}
export function FlexibleDateInput({
value,
precision = 'exact',
onChange,
placeholder = "Select date",
disabled = false,
className,
disableFuture = false,
disablePast = false,
fromYear = 1800,
toYear = new Date().getFullYear() + 10,
label,
}: FlexibleDateInputProps) {
const [localPrecision, setLocalPrecision] = React.useState<DatePrecision>(precision);
const [yearValue, setYearValue] = React.useState<string>(
value && precision === 'year' ? value.getFullYear().toString() : ''
);
React.useEffect(() => {
setLocalPrecision(precision);
}, [precision]);
React.useEffect(() => {
if (value && precision === 'year') {
setYearValue(value.getFullYear().toString());
}
}, [value, precision]);
const handlePrecisionChange = (newPrecision: DatePrecision) => {
setLocalPrecision(newPrecision);
// If we have a value, adjust it to the new precision
if (value) {
const year = value.getFullYear();
const month = value.getMonth();
let newDate: Date;
switch (newPrecision) {
case 'year':
case 'decade':
case 'century':
case 'approximate':
newDate = new Date(year, 0, 1); // January 1st (local timezone)
setYearValue(year.toString());
break;
case 'month':
newDate = new Date(year, month, 1); // 1st of month (local timezone)
break;
case 'exact':
default:
newDate = value; // Keep existing date
break;
}
onChange(newDate, newPrecision);
} else {
onChange(undefined, newPrecision);
}
};
const handleYearChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const yearStr = e.target.value;
setYearValue(yearStr);
const yearNum = parseInt(yearStr, 10);
if (yearStr && !isNaN(yearNum) && yearNum >= fromYear && yearNum <= toYear) {
const newDate = new Date(yearNum, 0, 1);
onChange(newDate, 'year');
} else if (!yearStr) {
onChange(undefined, 'year');
}
};
const getPlaceholderText = () => {
switch (localPrecision) {
case 'year':
case 'decade':
case 'century':
case 'approximate':
return 'Enter year (e.g., 2005)';
case 'month':
return 'Select month and year';
case 'exact':
default:
return placeholder;
}
};
const getPrecisionHelpText = () => {
switch (localPrecision) {
case 'exact':
return 'Use when you know the specific day (e.g., June 15, 2010)';
case 'month':
return 'Use when you only know the month (e.g., June 2010)';
case 'year':
return 'Use when you only know the year (e.g., 2010)';
case 'decade':
return 'Use for events in a general decade (e.g., 1980s). Enter any year from that decade.';
case 'century':
return 'Use for very old dates spanning a century (e.g., 19th century). Enter any year from that century.';
case 'approximate':
return 'Use when the date is uncertain or estimated (e.g., circa 2010)';
default:
return '';
}
};
return (
<div className={cn("space-y-2", className)}>
{label && <Label>{label}</Label>}
<div className="flex gap-2">
<div className="flex-1">
{(localPrecision === 'exact') && (
<DatePicker
date={value}
onSelect={(date) => onChange(date, 'exact')}
placeholder={getPlaceholderText()}
disabled={disabled}
disableFuture={disableFuture}
disablePast={disablePast}
fromYear={fromYear}
toYear={toYear}
/>
)}
{localPrecision === 'month' && (
<MonthYearPicker
date={value}
onSelect={(date) => onChange(date, 'month')}
placeholder={getPlaceholderText()}
disabled={disabled}
fromYear={fromYear}
toYear={toYear}
/>
)}
{(localPrecision === 'year' || localPrecision === 'decade' || localPrecision === 'century' || localPrecision === 'approximate') && (
<Input
type="number"
value={yearValue}
onChange={handleYearChange}
placeholder={getPlaceholderText()}
disabled={disabled}
min={fromYear}
max={toYear}
className="w-full"
/>
)}
</div>
<Select
value={localPrecision}
onValueChange={(value) => handlePrecisionChange(value as DatePrecision)}
disabled={disabled}
>
<SelectTrigger className="w-[160px]">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="exact">Exact Day</SelectItem>
<SelectItem value="month">Month & Year</SelectItem>
<SelectItem value="year">Year Only</SelectItem>
<SelectItem value="decade">Decade</SelectItem>
<SelectItem value="century">Century</SelectItem>
<SelectItem value="approximate">Approximate</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-start gap-2 text-xs text-muted-foreground">
<Info className="h-3.5 w-3.5 mt-0.5 flex-shrink-0" />
<p>{getPrecisionHelpText()}</p>
</div>
</div>
);
}