我正在 React 项目中使用 FullCalendar,并尝试在选择时间范围时在新创建的事件旁边显示弹出窗口(使用浮动 ui)。 (选择时间范围会创建一个标题为空的事件,弹出窗口最终应该编辑该事件的详细信息。现在我只是想让弹出窗口正确显示。)
选择时间范围后,如何在 FullCalendar 中新创建的事件旁边显示弹出框?有人能指出我正确的方向吗?
我正在使用弹窗,因为它们在对另一个问题的回复中被建议。
我已经成功地创建了事件并分别显示了弹出窗口,但是我无法将两者结合起来以便将弹出窗口定位在事件旁边。
我尝试使用 setTimeout 等待事件元素在 DOM 中可用,然后获取元素的边界矩形来定位弹出框。但是,这似乎不起作用,我仍然无法正确定位弹出窗口。
我尝试修改 handleSelect 函数以获取所选事件的位置并将其存储在组件状态中,然后更新 Popover 组件以接受该位置作为道具并将该位置应用于其样式。在 Calendar 组件中,我将存储的位置传递给 Popover 组件。这也没有用——当我创建一个新事件时什么也没有发生。
在下面的代码中,我定义了一个 Popover 组件,它在单击按钮时显示一个弹出框。使用 FullCalendar 的日历组件显示事件。因此,当用户在日历上选择一个时间范围时,将创建一个新的临时事件......最终目标是弹出窗口出现在它旁边(以提供一个用于将更新应用于事件规范的界面)。
import React, { useState, useRef } from 'react';
import FullCalendar from '@fullcalendar/react';
import timeGridPlugin from '@fullcalendar/timegrid';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import customDayHeaderContent from './customDayHeaderContent';
import { nanoid } from 'nanoid';
import {
useFloating,
autoUpdate,
offset,
flip,
shift,
useDismiss,
useRole,
useClick,
useInteractions,
FloatingFocusManager,
useId
} from "@floating-ui/react";
function Popover() {
const [isOpen, setIsOpen] = useState(false);
const { x, y, refs, strategy, context } = useFloating({
open: isOpen,
onOpenChange: setIsOpen,
middleware: [
offset(10),
flip({ fallbackAxisSideDirection: "end" }),
shift()
],
whileElementsMounted: autoUpdate
});
const click = useClick(context);
const dismiss = useDismiss(context);
const role = useRole(context);
const { getReferenceProps, getFloatingProps } = useInteractions([
click,
dismiss,
role
]);
const headingId = useId();
return (
<>
<div style={{ position: 'static' }}>
<button ref={refs.setReference} {...getReferenceProps()}>
Add review
</button>
{isOpen && (
<FloatingFocusManager context={context} modal={false}>
<div
className="Popover z-10"
ref={refs.setFloating}
style={{
position: 'absolute', // Change this to absolute
top: y ?? 0,
left: x ?? 0
}}
aria-labelledby={headingId}
{...getFloatingProps()}
>
<h2 id={headingId}>Review balloon</h2>
<textarea placeholder="Write your review..." />
<br />
<button
style={{ float: "right" }}
onClick={() => {
console.log("Added review.");
setIsOpen(false);
}}
>
Add
</button>
</div>
</FloatingFocusManager>
)}
</div>
</>
);
}
export default function Calendar() {
const [events, setEvents] = useState([
{ title: "YM's birthday", date: '2023-03-21' },
{ title: 'Anniversary', date: '2023-03-22' },
{
title: 'Review',
start: '2023-03-21T00:00:00',
end: '2023-03-21T03:00:00',
extendedProps: { department: 'BioChemistry' },
description: 'Lecture',
},
{
title: 'Class',
start: '2023-03-22T13:00:00',
end: '2023-03-22T15:00:00',
extendedProps: { department: 'BioChemistry' },
description: 'Lecture',
},
]);
const calendarRef = useRef(null);
const [eventTitle, setEventTitle] = useState('');
const [selectedEvent, setSelectedEvent] = useState(null);
const handleSelect = (selectInfo) => {
const calendarApi = selectInfo.view.calendar;
calendarApi.unselect(); // clear date selection
const newEventId = `temp-event-${nanoid()}`; // Generate a new unique ID for the temporary event
const newEvent = {
id: newEventId,
title: '',
start: selectInfo.startStr,
end: selectInfo.endStr,
allDay: selectInfo.allDay,
};
setSelectedEvent(newEvent);
calendarApi.addEvent(newEvent); // Add a temporary event with an empty title
setIsOpen(true);
// Cleanup function to remove the temporary event when component unmounts or selection is changed
return () => {
const tempEvent = calendarApi.getEventById(newEventId);
if (tempEvent) {
tempEvent.remove();
}
};
};
const handleSaveEvent = () => {
const calendarApi = calendarRef.current.getApi();
if (selectedEvent) {
const tempEvent = calendarApi.getEventById(selectedEvent.id);
if (tempEvent && selectedEvent.title !== '') {
tempEvent.setProp('title', selectedEvent.title); // Update the title of the temporary event
tempEvent.setExtendedProp('id', nanoid()); // Set a unique ID for the saved event
} else {
tempEvent.remove(); // Remove the temporary event if no title is provided
}
}
setIsOpen(false);
};
const [isOpen, setIsOpen] = useState(false);
const updateEventTitle = (title) => {
if (selectedEvent) {
const updatedEvent = { ...selectedEvent, title };
setSelectedEvent(updatedEvent);
const calendarApi = calendarRef.current.getApi();
const tempEvent = calendarApi.getEventById(selectedEvent.id);
if (tempEvent) {
tempEvent.setProp('title', updatedEvent.title); // Update the title of the temporary event in real-time
}
}
};
return (
<>
<FullCalendar
ref={calendarRef}
plugins={[timeGridPlugin, dayGridPlugin, interactionPlugin]}
initialView="timeGridWeek"
height="100%"
nowIndicator={true}
editable={true}
selectable={true}
eventResizableFromStart={true}
dayHeaderContent={customDayHeaderContent}
select={handleSelect}
titleFormat={{
year: 'numeric',
month: 'long'
}}
dayHeaderFormat={{
weekday: 'short',
month: 'numeric',
day: 'numeric',
omitCommas: true
}}
headerToolbar={{
left: 'title',
// center: 'title',
right: 'prev,today,next timeGridDay,timeGridWeek,dayGridMonth'
}}
views={{
timeGridWeek: {
type: 'timeGrid',
duration: { days: 7 },
buttonText: 'week',
dateAlignment: 'week',
dayHeaderFormat: { day: 'numeric', weekday: 'short', omitCommas: true },
eventTimeFormat: {
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: true,
meridiem: 'short'
},
},
dayGridMonth: {
type: 'dayGrid',
duration: { months: 1 },
buttonText: 'month',
dayHeaderFormat: { weekday: 'short' },
eventTimeFormat: {
hour: 'numeric',
minute: '2-digit',
omitZeroMinute: true,
meridiem: 'short'
},
},
}}
eventMinHeight={50}
events={events}
/>
<Popover />
</>
);
}