Match native datetime-local format in DatePicker input variant

Pad 12-hour display to 2 digits to match Chrome native input format:
03/03/2026 03:12 AM (was 3:12 AM). Relax day/month parser to accept
1-2 digit input while still outputting zero-padded ISO strings.
Update placeholder to DD/MM/YYYY hh:mm AM.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-03 03:21:47 +08:00
parent 59a4f67b42
commit 247c701e12

View File

@ -34,7 +34,7 @@ function to24Hour(h12: number, ampm: string): number {
return isPM ? h12 + 12 : h12;
}
/** ISO string → user-friendly display: DD/MM/YYYY or DD/MM/YYYY h:mm AM/PM */
/** ISO string → user-friendly display: DD/MM/YYYY or DD/MM/YYYY hh:mm AM */
function isoToDisplay(iso: string, mode: 'date' | 'datetime'): string {
if (!iso) return '';
const parts = iso.split('T');
@ -47,20 +47,20 @@ function isoToDisplay(iso: string, mode: 'date' | 'datetime'): string {
const h = parseInt(tp[0], 10);
if (isNaN(h)) return dateStr;
const { hour: h12, ampm } = to12Hour(h);
return `${dateStr} ${h12}:${tp[1].slice(0, 2)} ${ampm}`;
return `${dateStr} ${pad(h12)}:${tp[1].slice(0, 2)} ${ampm}`;
}
/** User-friendly display → ISO string, or null if incomplete/invalid */
function displayToIso(display: string, mode: 'date' | 'datetime'): string | null {
if (!display) return null;
if (mode === 'date') {
const m = display.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
const m = display.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
if (!m) return null;
const d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10);
if (mo < 1 || mo > 12 || d < 1 || d > getDaysInMonth(y, mo - 1)) return null;
return `${m[3]}-${m[2]}-${m[1]}`;
return `${m[3]}-${pad(mo)}-${pad(d)}`;
}
const m = display.match(/^(\d{2})\/(\d{2})\/(\d{4})\s+(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
const m = display.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2})\s*(AM|PM)$/i);
if (!m) return null;
const d = parseInt(m[1], 10), mo = parseInt(m[2], 10), y = parseInt(m[3], 10);
if (mo < 1 || mo > 12 || d < 1 || d > getDaysInMonth(y, mo - 1)) return null;
@ -68,7 +68,7 @@ function displayToIso(display: string, mode: 'date' | 'datetime'): string | null
const min = parseInt(m[5], 10);
if (h < 1 || h > 12 || min < 0 || min > 59) return null;
h = to24Hour(h, m[6]);
return `${m[3]}-${m[2]}-${m[1]}T${pad(h)}:${pad(min)}`;
return `${m[3]}-${pad(mo)}-${pad(d)}T${pad(h)}:${pad(min)}`;
}
const MONTH_NAMES = [
@ -152,7 +152,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
const [hour, setHour] = React.useState(parsed?.hour ?? 0);
const [minute, setMinute] = React.useState(parsed?.minute ?? 0);
// Input variant: user-friendly display string (DD/MM/YYYY or DD/MM/YYYY h:mm AM/PM)
// Input variant: user-friendly display string (DD/MM/YYYY or DD/MM/YYYY hh:mm AM)
const [displayValue, setDisplayValue] = React.useState(() =>
variant === 'input' ? isoToDisplay(value, mode) : ''
);
@ -520,7 +520,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
}}
required={required}
disabled={disabled}
placeholder={placeholder || (mode === 'datetime' ? 'DD/MM/YYYY h:mm AM/PM' : 'DD/MM/YYYY')}
placeholder={placeholder || (mode === 'datetime' ? 'DD/MM/YYYY hh:mm AM' : 'DD/MM/YYYY')}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 pr-9 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className