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 ReactDatePicker from 'react-datepicker';
|
||||||
import { de } from 'date-fns/locale';
|
import { de } from 'date-fns/locale';
|
||||||
import { Calendar as CalendarIcon, Clock } from 'lucide-react';
|
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';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,8 +39,29 @@ export default function DateTimePicker({
|
|||||||
|
|
||||||
const Icon = icon === 'clock' ? Clock : CalendarIcon;
|
const Icon = icon === 'clock' ? Clock : CalendarIcon;
|
||||||
|
|
||||||
return (
|
// react-datepicker calls scrollIntoView on the selected time item when the
|
||||||
<div className="dtp-wrapper">
|
// 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 && (
|
||||||
<label className="block text-sm font-medium text-th-text mb-1.5">
|
<label className="block text-sm font-medium text-th-text mb-1.5">
|
||||||
{label}{required && ' *'}
|
{label}{required && ' *'}
|
||||||
@@ -59,6 +80,7 @@ export default function DateTimePicker({
|
|||||||
minDate={minDate}
|
minDate={minDate}
|
||||||
required={required}
|
required={required}
|
||||||
popperPlacement="bottom-start"
|
popperPlacement="bottom-start"
|
||||||
|
onCalendarOpen={handleCalendarOpen}
|
||||||
popperModifiers={[
|
popperModifiers={[
|
||||||
{ name: 'offset', options: { offset: [0, 4] } },
|
{ name: 'offset', options: { offset: [0, 4] } },
|
||||||
{ name: 'preventOverflow', options: { rootBoundary: 'viewport', tether: false, altAxis: true } },
|
{ 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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="p-6 overflow-y-auto max-h-[calc(90vh-4rem)]">
|
<div className="p-6">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -819,11 +819,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Must sit above the modal backdrop (z-50 stacking context) */
|
|
||||||
.react-datepicker-popper {
|
|
||||||
z-index: 200 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Header (custom rendered) ─────────────────────────────────── */
|
/* ── Header (custom rendered) ─────────────────────────────────── */
|
||||||
.dtp-calendar .react-datepicker__header {
|
.dtp-calendar .react-datepicker__header {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
|
|||||||
Reference in New Issue
Block a user