Use type=text for DatePicker in Firefox to eliminate double icon

Firefox has no CSS pseudo-element to hide its native date picker
calendar icon (Mozilla bug 1830890, open P3). Firefox's date input
doesn't provide Chrome's segmented editing anyway — it renders as
a plain text field with an appended icon.

Fix: detect Firefox via user agent at module load, render type=text
with ISO format placeholder. Chromium keeps native date/datetime-local
for segmented editing UX. min/max omitted for Firefox (only valid on
native date inputs). Custom popup handles all date selection in both.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-03 04:01:38 +08:00
parent db2ec156e4
commit 8e922a1f1c

View File

@ -3,6 +3,10 @@ import { createPortal } from 'react-dom';
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react'; import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
// ── Browser detection (stable — checked once at module load) ──
const isFirefox = typeof navigator !== 'undefined' && /Firefox\//i.test(navigator.userAgent);
// ── Helpers ── // ── Helpers ──
function getDaysInMonth(year: number, month: number): number { function getDaysInMonth(year: number, month: number): number {
@ -429,18 +433,35 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
) )
: null; : null;
// ── Input variant: native date/datetime-local + calendar icon for custom popup ── // ── Input variant ──
// Firefox: use type="text" to avoid the native calendar icon that cannot be
// hidden via CSS (Firefox has no ::-webkit-calendar-picker-indicator equivalent).
// Chromium: use native type="date"/"datetime-local" for segmented editing UX,
// with the native icon hidden via CSS in index.css (.datepicker-wrapper rule).
if (variant === 'input') { if (variant === 'input') {
const nativeType = isFirefox
? 'text'
: mode === 'datetime'
? 'datetime-local'
: 'date';
const textPlaceholder = isFirefox
? mode === 'datetime'
? 'YYYY-MM-DDThh:mm'
: 'YYYY-MM-DD'
: undefined;
return ( return (
<> <>
<div ref={wrapperRef} className="datepicker-wrapper relative"> <div ref={wrapperRef} className="datepicker-wrapper relative">
<input <input
ref={inputElRef} ref={inputElRef}
type={mode === 'datetime' ? 'datetime-local' : 'date'} type={nativeType}
id={id} id={id}
name={name} name={name}
autoComplete={autoComplete} autoComplete={autoComplete}
value={value} value={value}
placeholder={textPlaceholder}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
onBlur={handleInputBlur} onBlur={handleInputBlur}
onKeyDown={(e) => { onKeyDown={(e) => {
@ -452,8 +473,8 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
}} }}
required={required} required={required}
disabled={disabled} disabled={disabled}
min={min} min={isFirefox ? undefined : min}
max={max} max={isFirefox ? undefined : max}
className={cn( 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', '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 className