ARTICLE AD BOX
I'm working on a calendar for a project where, when a chosen event is clicked, a pop-up box is supposed to appear right above the clicked element. I originally tried to do it where it works based on the position of the physical element the user clicked, but it's inconsistent and unresponsive, so it is always off to one side (the attached image will give you an idea of what I mean. Also, ignore the weird formatting of the text in the popup; I got rid of some text for the sake of anonymity).
What I'm wondering is if there is a way for me to have it just always be displayed above the yellow event relative to that event so that no matter the page size, it always pops up there. Here's the code I used to try and make it so the pop-up box with the text in it is just always above the yellow box that is on the calendar (the one over the 13 in the calendar itself, not the one at the bottom).
const Cal = () => { const [popupBoxVisibility, usePopupBoxVisibility] = useState('hidden'); //This will be what renders the popup box when the event is highlighted const [clientPosition, useClientPosition] = useState([0, 0]); //This will be used to determine the position of the popup box const [eventDetails, useEventDetails] = useState(null); //This will contain the event details to be rendered in the popup box const [currMonth, useCurrMonth] = useState(null); //Gives the current month so we can use it to dynamically space out the prev and next arrows const [currDate, useCurrDate] = useState(null); //currdate const calendarRef = useRef(null); //This is the ref that will determine if the FullCalendar component mounted const containerRef = useRef(null); //This is so we can make it so scrolling doesn't effect the position of the popup box const handleEventClick = (e) => { //This function is what happens when you hover over the event since the width of the calendar is 430px and there are 7 days spanning it, we set midpoint of event as (e.el.getBoundingClientRect().x + e.el.getBoundingClientRect().x + (430/7))/2 as we want the average of the two values. This obviously didn't work out as we wanted since we had to use margins in the img for the popupbox but it works now so I'm gonna keep it... useEventDetails(e.event); //saves event details to be used as a prop in the Popupbox component const pastClientPosition = clientPosition; //The current position before we update clientPosition in state const pastEventDate = currDate; //This is the date of the former event const eventRect = e.el.getBoundingClientRect(); const containerRect = containerRef.current.getBoundingClientRect(); const newClientPosition = [ eventRect.left - containerRect.left, eventRect.top - containerRect.top, ]; //This is to get the bounding box of the event the user clicked on useClientPosition(newClientPosition); //Simply updating clientPosition useCurrDate(e.event.startStr); console.log(`currDate: ${currDate}`); console.log(`pastEventDate: ${pastEventDate}`); if ( JSON.stringify(pastEventDate) === JSON.stringify(e.event.startStr) || JSON.stringify(pastClientPosition) === '[0,0]' ) { //By stringifying it, we compare not the arrays, which would require looping through and checking at each step, but just if strings of them look the same The purpose of this is that if the user clicks the same event twice, it will show then hide the popup box but if you click a different box, it will simply move the box to that point We also include the || ...==="[0,0]" as it is an edge case usePopupBoxVisibility( popupBoxVisibility === 'visible' ? 'hidden' : 'visible' ); } else if ( JSON.stringify(pastEventDate) !== JSON.stringify(e.event.startStr) ) { //This code accounts for one more issue which is let's say a user presses on event one and then presses on it again to hide it If the user then clicks on a different event after having hid that one, nothing will show up because the conditional will return 'false' since the past and new positions are different. This accounts for that by saying automatically that if the thing is different, it will always default to visible usePopupBoxVisibility('visible'); } }; const handleEventEnter = (e) => { //Changes color on event hover e.el.style.backgroundColor = '#ffce78'; }; const handleEventLeave = (e) => { //Returns color on mouse out e.el.style.backgroundColor = '#DCAA47'; }; return ( <section className="calendar-section" ref={containerRef}> {/* <div className="prev-button" onClick={(e) => { if (calendarRef.current) { const calendar = calendarRef.current.getApi(); calendar.prev(); } }} style={{ marginLeft: calendarRef.current ? {isBigScreen} ? `${-110 - 3 * currMonth.length}px` : `${80 - 3* currMonth.length}px` : `-110px`, marginTop: {isBigScreen} ? '133.5px' : '14px', }} ></div> <div className="next-button" onClick={(e) => { if (calendarRef.current) { const calendar = calendarRef.current.getApi(); calendar.next(); } }} style={{ marginLeft: calendarRef.current ? {isBigScreen} ? `${25 + 3 * currMonth.length}px` : `${-90 + 3 * currMonth.length}` : `25px`, marginTop: '133.5px', }} ></div> */} <FullCalendar ref={calendarRef} plugins={[dayGridPlugin, interactionPlugin]} //List of built-in plugins from full calendar initialView="dayGridMonth" //TODO figure out what this means events={[ { date: '2025-09-24', name: 'name_1', streetAddress: '123 abc street', cityState: 'a,b', eventLink: 'google.com', photosLink: '', }, { date: '2025-09-25', name: 'name_2', streetAddress: 'a', cityState: 'b', eventLink: 'c', photosLink: '', }, { date: '2025-09-26', name: 'test', streetAddress: '', cityState: '', eventLink: '', photosLink: '', }, { date: '2025-11-13', name: '', streetAddress: ',', cityState: '', eventLink: '', photosLink: '', }, ]} //These are the events. eventColor="#DCAA47" //This is a built-in thing that sets the color of the events on the calendar eventClick={handleEventClick} //This will be how the text bubbles with the info will pop up eventMouseEnter={handleEventEnter} //This will change color on hover in eventMouseLeave={handleEventLeave} //This will return color on hover out headerToolbar={{ left: '', center: 'prev,title,next', right: '' }} //This is how you order the things in the toolbar (as in where the month shows up) height="auto" handleWindowResize={true} //Resizes on window change datesSet={(dateInfo) => { //When you change the month, you want the popup box to hide itself again so this does that usePopupBoxVisibility('hidden'); const currDate = new Date( (dateInfo.start.getTime() + dateInfo.end.getTime()) / 2 ); //Start and end are in previous and next month so we need to find the midpoint to have the current month useCurrMonth( formatDate(currDate, { month: 'long', }) ); //We set currMonth to be the current month }} /> <PopupBox props={{ visibility: `${popupBoxVisibility}`, pos: clientPosition, event: eventDetails, }} /> <div className="event-list"> <img src="./yellowsquare.png" className="yellow-square" alt="Yellow Square" /> <p className="event-list-title">Upcoming Events</p> </div> </section> ); }; const PopupBox = (props) => { //This will be the component for the boxes that pop up when you highlight an event console.log( props.props.event === null ? '' : props.props.event.start.getDay() ); const popupFlag = props.props.event === null ? -1 : props.props.event.start.getDay() === 0 ? 0 : 1; //This is what we use to determine which thing we should render and if we should move it around const whichPopup = popupFlag === -1 ? '' : popupFlag === 0 ? 'popupleft.png#svgView(viewBox(-5,0,350.82,88.07))' : 'popup-cropped.svg#svgView(viewBox(0,0,299.18,162.07))'; //If it's not there, we render nothing, if it's there, then if it's Sunday (0), it's popupleft and if it's not, then it's the normal popup return ( <div className="popup-box-div" style={{ visibility: props.props.visibility, top: `${props.props.pos[1]}px`, left: `${props.props.pos[0]}px`, position: 'absolute', width: '380.82px', zIndex: 20, marginTop: `${popupFlag === 0 ? '-210px' : '-210px'}`, marginLeft: `${popupFlag === 0 ? '130px' : '110px'}`, }} > <img src={whichPopup} width="380.82px" height="100px" className="popupbox" alt="popup box" /> <div className="popup-box-text" style={{ marginTop: `${popupFlag === -1 ? '0' : popupFlag === 0 ? '-150' : '-130'}px`, marginLeft: `${popupFlag === -1 ? '0' : popupFlag === 0 ? '25' : '45'}px`, }} > <p className="box-text title"> {props.props.event === null ? '' : props.props.event.extendedProps.name} </p> <p className="box-text date"> {props.props.event === null ? '' : formatDate(props.props.event.start, { month: 'long', year: 'numeric', day: 'numeric', })} </p> <p className="box-text location"> {props.props.event === null ? '' : `${props.props.event.extendedProps.streetAddress} ${props.props.event.extendedProps.cityState}`} </p> <p className="box-text link">Add to calendar coming soon</p> {/* <a href={props.props.event===null ? '' : props.props.event.extendedProps.eventLink} className='box-text link' target="_blank">Click here to add event to your calendar</a> */} </div> </div> ); };Here is the CSS code that goes with this popupbox too:
.popup-box-div { // margin-top: -190px; // margin-left: 130px z-index: 20; } .popup-box-text { //This is the div containing the text that appears within the popup box position: relative; margin-top: -160px; // margin-left: 45px; z-index: 3; .box-text { //This is the text itself font-size: 15px; font-family: 'Helvetica', 'Arial', sans-serif; margin-left: 10px; } .title { //This is the text only pertaining to the title (technically data is saved as name in events) of the event font-size: 20px; margin-top: -30px; font-weight: bold; } .date { //I kept trying to make these all one class (like "sub-text") but it kept breaking so they're all going to be separate position: absolute; margin-top: -5px; } .location { position: absolute; margin-top: 20px; } .link { position: absolute; margin-top: 60px; } }
