From 63b3a3a073bc72e4d921ccf0e564e025313531de Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Tue, 3 Mar 2026 15:56:05 +0800 Subject: [PATCH] Fix Firefox DatePicker popup positioning at top-left When Firefox input variant falls through to button variant, the positioning logic, close handler, and click-outside handler still checked variant==='input' and used wrapperRef (which is unattached). Introduced usesNativeInput flag (input variant + not Firefox) so all three handlers correctly use triggerRef for Firefox fallback. Co-Authored-By: Claude Opus 4.6 --- frontend/src/components/ui/date-picker.tsx | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/ui/date-picker.tsx b/frontend/src/components/ui/date-picker.tsx index 88af6a6..359d2be 100644 --- a/frontend/src/components/ui/date-picker.tsx +++ b/frontend/src/components/ui/date-picker.tsx @@ -144,8 +144,10 @@ const DatePicker = React.forwardRef( }, [value, open]); // Position popup + // Firefox + input variant falls through to button variant, so use triggerRef + const usesNativeInput = variant === 'input' && !isFirefox; const updatePosition = React.useCallback(() => { - const el = variant === 'input' ? wrapperRef.current : triggerRef.current; + const el = usesNativeInput ? wrapperRef.current : triggerRef.current; if (!el) return; const rect = el.getBoundingClientRect(); const popupHeight = mode === 'datetime' ? 370 : 320; @@ -156,7 +158,7 @@ const DatePicker = React.forwardRef( top: flipped ? rect.top - popupHeight - 4 : rect.bottom + 4, left: Math.min(rect.left, window.innerWidth - 290), }); - }, [mode, variant]); + }, [mode, usesNativeInput]); React.useEffect(() => { if (!open) return; @@ -172,13 +174,13 @@ const DatePicker = React.forwardRef( const closePopup = React.useCallback( (refocusTrigger = true) => { setOpen(false); - if (variant === 'button') { + if (!usesNativeInput) { onBlur?.(); } else if (refocusTrigger) { setTimeout(() => inputElRef.current?.focus(), 0); } }, - [variant, onBlur] + [usesNativeInput, onBlur] ); // Dismiss on click outside @@ -186,13 +188,13 @@ const DatePicker = React.forwardRef( if (!open) return; const handler = (e: MouseEvent) => { if (popupRef.current?.contains(e.target as Node)) return; - if (variant === 'button' && triggerRef.current?.contains(e.target as Node)) return; - if (variant === 'input' && wrapperRef.current?.contains(e.target as Node)) return; + if (!usesNativeInput && triggerRef.current?.contains(e.target as Node)) return; + if (usesNativeInput && wrapperRef.current?.contains(e.target as Node)) return; closePopup(false); }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); - }, [open, variant, closePopup]); + }, [open, usesNativeInput, closePopup]); // Dismiss on Escape React.useEffect(() => {