feat(DateTimePicker): implement calendar open handler to preserve scroll position
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:
2026-03-04 11:12:56 +01:00
parent 15bfcc80c3
commit a78fc06f2b
3 changed files with 26 additions and 9 deletions

View File

@@ -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 } },

View File

@@ -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>

View File

@@ -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);