From d1e9c9fc7e5c9e61446586e0c95b9c4009fc8727 Mon Sep 17 00:00:00 2001 From: SlimusMinus Date: Wed, 12 Aug 2026 23:59:16 +0300 Subject: [PATCH] fixed calendar on post --- src/components/DateCalendarPopup.jsx | 51 ++++++++++++++++--- src/pages/PostOnFeed.jsx | 73 +++++++++++++++++++--------- src/styles/base.css | 2 + src/styles/card.css | 12 +++-- 4 files changed, 104 insertions(+), 34 deletions(-) diff --git a/src/components/DateCalendarPopup.jsx b/src/components/DateCalendarPopup.jsx index 182ee2a..cdf613f 100644 --- a/src/components/DateCalendarPopup.jsx +++ b/src/components/DateCalendarPopup.jsx @@ -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 ( -
+ return createPortal( +
{months[month]} {year} @@ -73,6 +109,7 @@ export default function DateCalendarPopup({ date, onClose }) { ) :
)}
-
+
, + document.body ); } \ No newline at end of file diff --git a/src/pages/PostOnFeed.jsx b/src/pages/PostOnFeed.jsx index bc2ab85..a3368d4 100644 --- a/src/pages/PostOnFeed.jsx +++ b/src/pages/PostOnFeed.jsx @@ -1,21 +1,20 @@ -// src/components/PostOnFeed.jsx +import {useRef, useState} from "react"; import PinIcon from "../components/icons/PinIcon"; import CalendarIcon from "../components/icons/CalendarIcon"; import PhoneIcon from "../components/icons/PhoneIcon"; -import MapIcon from "../components/icons/MapIcon"; import HeartIcon from "../components/icons/HeartIcon"; import TrashIcon from "../components/icons/TrashIcon"; -import { useState } from "react"; -import { useContactSeller } from "../hooks/useContactSeller"; -import { useAuthContext } from "../context/AuthContext"; -import { getImageUrl } from "../config/authConstants"; +import {useContactSeller} from "../hooks/useContactSeller"; +import {useAuthContext} from "../context/AuthContext"; +import {getImageUrl} from "../config/authConstants"; import DateCalendarPopup from "../components/DateCalendarPopup"; import Toast from "../components/Toast"; -import { useTranslation } from 'react-i18next'; +import {useTranslation} from 'react-i18next'; export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMarkFound, isOwner, onImageClick, onAuthRequired }) { const { t } = useTranslation(); const [showCalendar, setShowCalendar] = useState(false); + const dateAnchorRef = useRef(null); const { isAuthenticated } = useAuthContext(); const { contactSeller, error: contactError, clearError } = useContactSeller(ad); @@ -72,22 +71,38 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark
{ad.category &&
{ad.category}
} -
+
{ad.type === "loss" ? t('postCard.loss') : t('postCard.found')}
{roleBadge}
{ad.title} - {isClosed && {t('postCard.foundStatus')}} + {isClosed && {t( + 'postCard.foundStatus')}}
{ad.description}
- {address &&
{address}
} + {address &&
{address} +
} {createdAt && ( -
{ e.stopPropagation(); setShowCalendar(s => !s); }}> - {createdAt} - {showCalendar && setShowCalendar(false)} />} +
{ + e.stopPropagation(); + setShowCalendar(s => !s); + }} + > + {createdAt} + {showCalendar && ( + setShowCalendar(false)} + anchorRef={dateAnchorRef} + /> + )}
)}
@@ -96,37 +111,51 @@ export default function PostOnFeed({ ad, liked, onLike, onEdit, onDelete, onMark {ad.phone && ( e.stopPropagation()}> - {ad.phone} + {ad.phone} )}
{!isOwner && !isClosed && ( - )} {onEdit && isOwner && !isClosed && ( )} {onDelete && isOwner && ( )} {onMarkFound && isOwner && !isClosed && ( )} -
diff --git a/src/styles/base.css b/src/styles/base.css index 6c91fa6..07f16a4 100644 --- a/src/styles/base.css +++ b/src/styles/base.css @@ -14,6 +14,7 @@ --bg-app: linear-gradient(135deg, #1a2340 0%, #0d1018 100%); --bg-card: linear-gradient(155deg, #1d2436, #131720); + --bg-card-popup: linear-gradient(155deg, #2b3552, #1e2742); --bg-surface: rgba(13,16,24,.7); --bg-surface-solid: #131720; --bg-filter: rgba(13,16,24,.4); @@ -35,6 +36,7 @@ --bg-app: linear-gradient(135deg, #f0f2f5 0%, #e2e6ed 100%); --bg-card: #ffffff; + --bg-card-popup: #ffffff; --bg-surface: rgba(255,255,255,.8); --bg-surface-solid: #ffffff; --bg-filter: rgba(255,255,255,.6); diff --git a/src/styles/card.css b/src/styles/card.css index e3abf30..a084dd0 100644 --- a/src/styles/card.css +++ b/src/styles/card.css @@ -245,17 +245,19 @@ /* ── Calendar Popup ── */ .f-date-calendar { - position: absolute; - bottom: calc(100% + 6px); - left: 0; - z-index: 100; - background: var(--bg-card); + background: var(--bg-card-popup); border: 1px solid var(--border); border-radius: 12px; padding: 10px; width: 220px; box-shadow: 0 8px 32px rgba(0,0,0,.25); } +.f-date-calendar-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.25); + z-index: 1000; +} .f-cal-header { display: flex; align-items: center;