diff --git a/com/static/bundled/com/components/ics-calendar-index.ts b/com/static/bundled/com/components/ics-calendar-index.ts index f88b9b0f..130dd8ef 100644 --- a/com/static/bundled/com/components/ics-calendar-index.ts +++ b/com/static/bundled/com/components/ics-calendar-index.ts @@ -1,6 +1,6 @@ import { makeUrl } from "#core:utils/api"; import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; -import { Calendar } from "@fullcalendar/core"; +import { Calendar, type EventClickArg } from "@fullcalendar/core"; import enLocale from "@fullcalendar/core/locales/en-gb"; import frLocale from "@fullcalendar/core/locales/fr"; import dayGridPlugin from "@fullcalendar/daygrid"; @@ -46,6 +46,71 @@ export class IcsCalendar extends inheritHtmlElement("div") { }; } + formatDate(date: Date) { + return new Intl.DateTimeFormat(this.locale, { + dateStyle: "medium", + timeStyle: "short", + }).format(date); + } + + createEventDetailPopup(event: EventClickArg) { + // Delete previous popup + const oldPopup = document.getElementById("event-details"); + if (oldPopup !== null) { + oldPopup.remove(); + } + + // Create new popup + const popup = document.createElement("div"); + const popupContainer = document.createElement("div"); + const popupFirstRow = document.createElement("div"); + const popupSecondRow = document.createElement("div"); + const popupTitleTimeIcon = document.createElement("i"); + const popupTitleTime = document.createElement("div"); + const popupTitle = document.createElement("h4"); + const popupTime = document.createElement("span"); + + popup.setAttribute("id", "event-details"); + popupContainer.setAttribute("class", "event-details-container"); + popupFirstRow.setAttribute("class", "event-details-row"); + popupSecondRow.setAttribute("class", "event-details-row"); + + popupTitleTimeIcon.setAttribute("class", "fa-solid fa-calendar-days fa-xl"); + + popupTitle.setAttribute("class", "event-details-title"); + popupTitle.textContent = event.event.title; + + popupTime.setAttribute("class", "event-details-time"); + popupTime.textContent = `${this.formatDate(event.event.start)} - ${this.formatDate(event.event.end)}`; + + popupTitleTime.appendChild(popupTitle); + popupTitleTime.appendChild(popupTime); + + popupFirstRow.appendChild(popupTitleTimeIcon); + popupSecondRow.appendChild(popupTitleTime); + + popupContainer.appendChild(popupFirstRow); + popupContainer.appendChild(popupSecondRow); + + popup.appendChild(popupContainer); + + // We can't just add the element relative to the one we want to appear under + // Otherwise, it either gets clipped by the boundaries of the calendar or resize cells + // Here, we create a popup outside the calendar that follows the clicked element + this.node.appendChild(popup); + const follow = (node: HTMLElement) => { + const rect = node.getBoundingClientRect(); + popup.setAttribute( + "style", + `top: calc(${rect.top + window.scrollY}px + ${rect.height}px); left: ${rect.left + window.scrollX}px;`, + ); + }; + follow(event.el); + window.addEventListener("resize", () => { + follow(event.el); + }); + } + async connectedCallback() { super.connectedCallback(); this.calendar = new Calendar(this.node, { @@ -69,7 +134,20 @@ export class IcsCalendar extends inheritHtmlElement("div") { this.calendar.changeView(this.currentView()); this.calendar.setOption("headerToolbar", this.currentToolbar()); }, + eventClick: (event) => { + // Avoid our popup to be deleted because we clicked outside of it + event.jsEvent.stopPropagation(); + this.createEventDetailPopup(event); + }, }); this.calendar.render(); + + window.addEventListener("click", (event: MouseEvent) => { + // Auto close popups when clicking outside of it + const popup = document.getElementById("event-details"); + if (popup !== null && !popup.contains(event.target as Node)) { + popup.remove(); + } + }); } } diff --git a/com/static/com/components/ics-calendar.scss b/com/static/com/components/ics-calendar.scss index 25a66c34..825a7ebb 100644 --- a/com/static/com/components/ics-calendar.scss +++ b/com/static/com/components/ics-calendar.scss @@ -11,53 +11,49 @@ --fc-button-hover-bg-color: #15608F; --fc-today-bg-color: rgba(26, 120, 179, 0.1); --fc-border-color: #DDDDDD; - --sc-main-background-color: #f9fafb; - --sc-main-padding: 5px; - --sc-main-border: 0px solid #DDDDDD; - --sc-main-border-radius: 0px; - --sc-body-font-family: Roboto; - --sc-title-font-family: Roboto; - --sc-body-font-size: 16px; - --sc-title-font-size: 28px; - --sc-body-font-weight: 400; - --sc-title-font-weight: 500; - --sc-title-font-color: #111111; - --sc-base-body-font-color: #222222; - --sc-title-font-style: normal; - --sc-body-font-style: normal; - --sc-event-dot-color: #1a78b3; - --sc-button-border: 1px solid #ffffff; - --sc-button-border-radius: 4px; - --sc-button-icons-size: 22px; - --sc-grid-event-white-space: nowrap; - --sc-block-event-background-color-hovered: rgb(245, 245, 245); - --sc-block-event-border: 1px solid rgba(255, 255, 255, 0); - --sc-block-event-border-radius: 2.5px; - --sc-dot-event-background-color: rgba(255, 255, 255, 0); - --sc-dot-event-background-color-hovered: rgb(245, 245, 245); - --sc-dot-event-text-color: #222222; - --sc-dot-event-border: 1px solid rgba(255, 255, 255, 0); - --sc-dot-event-border-radius: 2.5px; - --sc-grid-day-header-background-color: rgba(255, 255, 255, 0); - --sc-list-day-header-background-color: rgba(208, 208, 208, 0.3); - --sc-inner-calendar-background-color: rgba(255, 255, 255, 0); - --sc-past-day-background-color: rgba(255, 255, 255, 0); - --sc-future-day-background-color: rgba(255, 255, 255, 0); - --sc-disabled-day-background-color: rgba(208, 208, 208, 0.3); - --sc-event-overlay-background-color: white; - --sc-event-overlay-padding: 20px; - --sc-event-overlay-border: 1px solid #EEEEEE; - --sc-event-overlay-border-radius: 4px; - --sc-event-overlay-primary-icon-color: #1a78b3; - --sc-event-overlay-secondary-icon-color: black; - --sc-event-overlay-box-shadow: 0px 6px 20px 4px rgb(0 0 0 / 16%); - --sc-event-overlay-max-width: 600px; + --event-details-background-color: white; + --event-details-padding: 20px; + --event-details-border: 1px solid #EEEEEE; + --event-details-border-radius: 4px; + --event-details-box-shadow: 0px 6px 20px 4px rgb(0 0 0 / 16%); + --event-details-max-width: 600px; } ics-calendar { border: none; box-shadow: none; + #event-details { + z-index: 10; + max-width: 1151px; + position: absolute; + + .event-details-container { + display: flex; + color: black; + flex-direction: column; + min-width: 200px; + max-width: var(--event-details-max-width); + padding: var(--event-details-padding); + border: var(--event-details-border); + border-radius: var(--event-details-border-radius); + background-color: var(--event-details-background-color); + box-shadow: var(--event-details-box-shadow); + } + + .event-details-row { + display: flex; + flex-direction: row; + align-items: start; + } + + .event-details-title { + background-color: var(--event-details-background-color); + margin-top: 0px; + margin-bottom: 4px; + } + } + a.fc-col-header-cell-cushion, a.fc-col-header-cell-cushion:hover { color: black; @@ -69,7 +65,7 @@ ics-calendar { } td { - overflow: visible; // Show events on multiple days + overflow-x: visible; // Show events on multiple days } //Reset from style.scss