feat(DateTimePicker): implement calendar open handler to preserve scroll position
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled
feat(Modal): remove max height restriction for modal body style: clean up z-index for datepicker popper
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import ReactDatePicker from 'react-datepicker';
|
||||
import { de } from 'date-fns/locale';
|
||||
import { Calendar as CalendarIcon, Clock } from 'lucide-react';
|
||||
import { forwardRef } from 'react';
|
||||
import { forwardRef, useRef, useCallback } from 'react';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
/**
|
||||
@@ -39,8 +39,29 @@ export default function DateTimePicker({
|
||||
|
||||
const Icon = icon === 'clock' ? Clock : CalendarIcon;
|
||||
|
||||
return (
|
||||
<div className="dtp-wrapper">
|
||||
// react-datepicker calls scrollIntoView on the selected time item when the
|
||||
// calendar opens. This scrolls the nearest scrollable ancestor (page or
|
||||
// modal). We capture every scrollable ancestor's position just before open
|
||||
// and restore it in the next animation frame — after scrollIntoView fires.
|
||||
const wrapperRef = useRef(null);
|
||||
const handleCalendarOpen = useCallback(() => {
|
||||
const snapshots = [];
|
||||
let el = wrapperRef.current?.parentElement;
|
||||
while (el && el !== document.body) {
|
||||
const { overflow, overflowY } = getComputedStyle(el);
|
||||
if (/(auto|scroll)/.test(overflow + overflowY)) {
|
||||
snapshots.push({ el, top: el.scrollTop, left: el.scrollLeft });
|
||||
}
|
||||
el = el.parentElement;
|
||||
}
|
||||
// also capture window scroll
|
||||
const winY = window.scrollY;
|
||||
requestAnimationFrame(() => {
|
||||
snapshots.forEach(s => { s.el.scrollTop = s.top; s.el.scrollLeft = s.left; });
|
||||
window.scrollTo({ top: winY, behavior: 'instant' });
|
||||
});
|
||||
}, []);
|
||||
<div className="dtp-wrapper" ref={wrapperRef}>
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-th-text mb-1.5">
|
||||
{label}{required && ' *'}
|
||||
@@ -59,6 +80,7 @@ export default function DateTimePicker({
|
||||
minDate={minDate}
|
||||
required={required}
|
||||
popperPlacement="bottom-start"
|
||||
onCalendarOpen={handleCalendarOpen}
|
||||
popperModifiers={[
|
||||
{ name: 'offset', options: { offset: [0, 4] } },
|
||||
{ name: 'preventOverflow', options: { rootBoundary: 'viewport', tether: false, altAxis: true } },
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function Modal({ title, children, onClose, maxWidth = 'max-w-lg'
|
||||
</button>
|
||||
</div>
|
||||
{/* Body */}
|
||||
<div className="p-6 overflow-y-auto max-h-[calc(90vh-4rem)]">
|
||||
<div className="p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -819,11 +819,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Must sit above the modal backdrop (z-50 stacking context) */
|
||||
.react-datepicker-popper {
|
||||
z-index: 200 !important;
|
||||
}
|
||||
|
||||
/* ── Header (custom rendered) ─────────────────────────────────── */
|
||||
.dtp-calendar .react-datepicker__header {
|
||||
background: var(--bg-secondary);
|
||||
|
||||
Reference in New Issue
Block a user