mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 22:11:24 -05:00
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.
211 lines
6.4 KiB
TypeScript
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>
|
|
);
|
|
}
|