From 146baca5c42a81a3cdc76323776e0e76ec31f919 Mon Sep 17 00:00:00 2001 From: william garrity Date: Mon, 3 Aug 2026 15:44:04 -0400 Subject: [PATCH 1/2] fix(DateInput): use useAnchoredPosition so calendar flips and clamps to viewport The calendar was portaled but hand-rolled its fixed positioning (always below the trigger, no flip/clamp), so it went off screen near the viewport bottom. Migrate to the shared useAnchoredPosition hook from #332. --- src/components/DateInput/DateInput.tsx | 44 ++++++++------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/components/DateInput/DateInput.tsx b/src/components/DateInput/DateInput.tsx index 7a7e7cbd..16cd5247 100644 --- a/src/components/DateInput/DateInput.tsx +++ b/src/components/DateInput/DateInput.tsx @@ -11,6 +11,7 @@ import { } from '../../utils/date'; import { Input, type InputProps } from '../Input'; import { Calendar } from 'lucide-react'; +import { useAnchoredPosition } from '../../hooks/useAnchoredPosition'; export type DateInputMode = | 'default' @@ -322,34 +323,17 @@ const DateInput = React.forwardRef( // Calendar picker state const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); - const calendarRef = React.useRef(null); - const buttonRef = React.useRef(null); - const [calendarStyle, setCalendarStyle] = - React.useState(); - - const updateCalendarPosition = React.useCallback(() => { - if (!buttonRef.current) return; - - const rect = buttonRef.current.getBoundingClientRect(); - setCalendarStyle({ - position: 'fixed', - top: rect.bottom + 4, - left: Math.max(8, rect.right - 288), - zIndex: 9999, - }); - }, []); - React.useEffect(() => { - if (!isCalendarOpen) return; - - updateCalendarPosition(); - window.addEventListener('scroll', updateCalendarPosition, true); - window.addEventListener('resize', updateCalendarPosition); - return () => { - window.removeEventListener('scroll', updateCalendarPosition, true); - window.removeEventListener('resize', updateCalendarPosition); - }; - }, [isCalendarOpen, updateCalendarPosition]); + // Portal + fixed positioning so the calendar escapes overflow-hidden + // ancestors, flips vertically, and clamps to the viewport. + const { + anchorRef: buttonRef, + floatingRef: calendarRef, + style: calendarStyle, + } = useAnchoredPosition({ + open: isCalendarOpen, + placement: 'bottom-end', + }); // Parse current value into date parts for calendar const parsedDate = React.useMemo(() => { @@ -484,7 +468,7 @@ const DateInput = React.forwardRef( return () => document.removeEventListener('mousedown', handleClickOutside); } - }, [isCalendarOpen]); + }, [isCalendarOpen, buttonRef, calendarRef]); // Close on Escape key React.useEffect(() => { @@ -499,7 +483,7 @@ const DateInput = React.forwardRef( document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); } - }, [isCalendarOpen]); + }, [isCalendarOpen, buttonRef]); const handleDateSelect = (day: number) => { if (!isCalendarDateInRange(day)) return; @@ -617,7 +601,7 @@ const DateInput = React.forwardRef( ref={calendarRef} className={cn( 'bg-background border-border rounded-lg border shadow-lg', - 'w-72 p-3' + 'w-72 overflow-auto p-3' )} style={calendarStyle} role="dialog" From 3ac15e8b7aa29c07c7e5e12665bbdfcd6633a619 Mon Sep 17 00:00:00 2001 From: william garrity Date: Mon, 3 Aug 2026 15:59:01 -0400 Subject: [PATCH 2/2] fix(tailwind-preset): safelist overflow-auto for scrollable floating panels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Copilot review on #352 — overflow-auto was emitted by DateInput and ~15 other components but never safelisted for TW3 consumers. --- src/tailwind-preset.cjs | 2 ++ src/tailwind-preset.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/tailwind-preset.cjs b/src/tailwind-preset.cjs index a1ed3c22..78519131 100644 --- a/src/tailwind-preset.cjs +++ b/src/tailwind-preset.cjs @@ -65,6 +65,8 @@ module.exports = { // SchedulePicker / overflow handling 'overflow-x-auto', 'overflow-hidden', + // Scrollable floating panels (DateInput calendar, Autocomplete, Select, Table, ScrollArea, …) + 'overflow-auto', // Select component 'truncate', ], diff --git a/src/tailwind-preset.ts b/src/tailwind-preset.ts index 122e1d2c..aca0adb6 100644 --- a/src/tailwind-preset.ts +++ b/src/tailwind-preset.ts @@ -382,6 +382,8 @@ export const miewebUISafelist = [ // SchedulePicker / overflow handling 'overflow-x-auto', 'overflow-hidden', + // Scrollable floating panels (DateInput calendar, Autocomplete, Select, Table, ScrollArea, …) + 'overflow-auto', // Select component 'truncate', // AIReconciliationPanel