fixed calendar on post

This commit is contained in:
SlimusMinus
2026-08-12 23:59:16 +03:00
parent ba5ff93dca
commit d1e9c9fc7e
4 changed files with 104 additions and 34 deletions

View File

@@ -1,11 +1,14 @@
import { useState, useEffect, useRef } from "react";
// src/components/DateCalendarPopup.jsx
import { useState, useEffect, useRef, useLayoutEffect } from "react";
import { createPortal } from "react-dom";
import { useTranslation } from 'react-i18next';
export default function DateCalendarPopup({ date, onClose }) {
export default function DateCalendarPopup({ date, onClose, anchorRef }) {
const { t } = useTranslation();
const months = t('calendar.months', { returnObjects: true });
const dayNames = t('calendar.days', { returnObjects: true });
const ref = useRef(null);
const [coords, setCoords] = useState(null);
const selectedDate = new Date(date);
const isValidDate = !Number.isNaN(selectedDate.getTime());
@@ -13,11 +16,32 @@ export default function DateCalendarPopup({ date, onClose }) {
const [year, setYear] = useState(isValidDate ? selectedDate.getFullYear() : new Date().getFullYear());
const [month, setMonth] = useState(isValidDate ? selectedDate.getMonth() : new Date().getMonth());
// Двухпроходное позиционирование:
// 1) сразу после монтирования попап невидим (opacity: 0), но уже в DOM —
// нужно, чтобы измерить его реальные width/height.
// 2) как только размеры известны — пересчитываем координаты так, чтобы
// попап открывался влево-вверх от якоря, и показываем (opacity: 1).
useLayoutEffect(() => {
if (!anchorRef?.current || !ref.current) return;
const anchorRect = anchorRef.current.getBoundingClientRect();
const popupRect = ref.current.getBoundingClientRect();
setCoords({
top: anchorRect.top + window.scrollY - popupRect.height - 4,
left: anchorRect.right + window.scrollX - popupRect.width,
});
}, [anchorRef, month, year]); // пересчитываем и при смене месяца — высота грид может немного плавать
useEffect(() => {
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
const handler = (e) => {
const clickedAnchor = anchorRef?.current?.contains(e.target);
const clickedPopup = ref.current?.contains(e.target);
if (!clickedAnchor && !clickedPopup) onClose();
};
document.addEventListener("mousedown", handler);
return () => document.removeEventListener("mousedown", handler);
}, [onClose]);
}, [onClose, anchorRef]);
const goToPrevMonth = () => {
if (month === 0) {
@@ -51,8 +75,20 @@ export default function DateCalendarPopup({ date, onClose }) {
selectedDate.getMonth() === month &&
selectedDate.getDate() === day;
return (
<div className="f-date-calendar" ref={ref}>
return createPortal(
<div
className="f-date-calendar"
ref={ref}
style={{
position: "absolute",
top: coords?.top ?? 0,
left: coords?.left ?? 0,
zIndex: 1000,
// Пока координаты не вычислены (первый проход) — прячем, чтобы
// не мелькал в неправильном месте до пересчёта.
visibility: coords ? "visible" : "hidden",
}}
>
<div className="f-cal-header">
<button type="button" onClick={goToPrevMonth}>‹</button>
<span>{months[month]} {year}</span>
@@ -73,6 +109,7 @@ export default function DateCalendarPopup({ date, onClose }) {
) : <div key={i} />
)}
</div>
</div>
</div>,
document.body
);
}