add font Inter

update UI cho main grid calendar. add hover css and state. click able on header cell
This commit is contained in:
lenhanphung
2025-09-22 15:56:50 +07:00
parent 3e96034b92
commit e6bb665c87
4 changed files with 265 additions and 24 deletions
+5 -1
View File
@@ -26,7 +26,11 @@
href="https://fonts.googleapis.com/css2?family=Cal+Sans&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
/>
<title>Twake Calendar</title>
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,100..900;1,100..900&family=Cal+Sans&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
rel="stylesheet"
/>
<title>React App</title>
<script src="<%= assetPrefix %>/.env.js"></script>
<script src="<%= assetPrefix %>/appList.js"></script>
</head>
-13
View File
@@ -15,7 +15,6 @@
flex-grow: 1;
height: 100%;
overflow: hidden;
padding: 0 25px 0 10px;
}
.calendarListHeader {
@@ -42,18 +41,6 @@
border: dashed 1px #ffffff;
}
.fc .fc-timegrid-slot {
height: auto;
min-height: 54px !important;
}
.fc .fc-timegrid-slot-label {
display: flex;
align-items: flex-start;
padding-top: 2px;
height: 100%;
}
.fc-event-main span,
.fc-daygrid-event div {
display: block !important;
+156 -3
View File
@@ -320,7 +320,7 @@ export default function CalendarApp() {
}}
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
initialView="timeGridWeek"
firstDay={1}
firstDay={0}
editable={true}
selectable={true}
timeZone="local"
@@ -361,8 +361,8 @@ export default function CalendarApp() {
)}
weekNumbers
weekNumberFormat={{ week: "long" }}
slotDuration={"00:30:00"}
slotLabelInterval={"00:30:00"}
slotDuration={"01:00:00"}
slotLabelInterval={"01:00:00"}
scrollTime={new Date(Date.now() - 2 * 60 * 60 * 1000)
.toTimeString()
.slice(0, 5)}
@@ -402,6 +402,159 @@ export default function CalendarApp() {
</div>
);
}}
dayHeaderDidMount={(arg) => {
// Add click handler to day headers in week view
if (arg.view.type === "timeGridWeek") {
const headerEl = arg.el;
const handleDayHeaderClick = () => {
// Switch to day view and navigate to the clicked date
calendarRef.current?.changeView("timeGridDay", arg.date);
setSelectedDate(new Date(arg.date));
setSelectedMiniDate(new Date(arg.date));
};
headerEl.addEventListener("click", handleDayHeaderClick);
// Store the handler for cleanup
(headerEl as any).__dayHeaderClickHandler = handleDayHeaderClick;
}
}}
dayHeaderWillUnmount={(arg) => {
// Clean up event listeners to prevent memory leaks
const headerEl = arg.el;
if ((headerEl as any).__dayHeaderClickHandler) {
headerEl.removeEventListener(
"click",
(headerEl as any).__dayHeaderClickHandler
);
delete (headerEl as any).__dayHeaderClickHandler;
}
}}
viewDidMount={(arg) => {
// Add global hover effect for week and day views
if (
arg.view.type === "timeGridWeek" ||
arg.view.type === "timeGridDay"
) {
const calendarEl = document.querySelector(".fc") as HTMLElement;
if (calendarEl) {
const handleMouseMove = (e: MouseEvent) => {
// Find the timegrid container
const timegridEl =
calendarEl.querySelector(".fc-timegrid-body");
if (!timegridEl) return;
// Check if mouse is over all-day events area (fc-scrollgrid-sync-table)
const allDayTable = calendarEl.querySelector(
".fc-scrollgrid-sync-table"
);
if (allDayTable) {
const allDayRect = allDayTable.getBoundingClientRect();
if (
e.clientY >= allDayRect.top &&
e.clientY <= allDayRect.bottom
) {
// Mouse is over all-day events area, don't show highlight
timegridEl
.querySelectorAll(".hour-highlight")
.forEach((el: Element) => el.remove());
return;
}
}
// Check if mouse is over time slot labels (left side with hours)
const target = e.target as HTMLElement;
if (target && target.closest(".fc-timegrid-slot-label")) {
// Mouse is over time slot labels, don't show highlight
timegridEl
.querySelectorAll(".hour-highlight")
.forEach((el: Element) => el.remove());
return;
}
// Get all day columns
const dayColumns =
timegridEl.querySelectorAll(".fc-timegrid-col");
if (dayColumns.length === 0) return;
// Remove previous highlights
timegridEl
.querySelectorAll(".hour-highlight")
.forEach((el: Element) => el.remove());
// Find which day column the mouse is over
let targetColumn: Element | null = null;
for (const column of dayColumns) {
const rect = column.getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX <= rect.right) {
targetColumn = column;
break;
}
}
if (targetColumn) {
const rect = targetColumn.getBoundingClientRect();
const relativeY = e.clientY - rect.top;
const hourHeight = rect.height / 24; // Assuming 24 hours
const hourIndex = Math.floor(relativeY / hourHeight);
// Only show highlight if mouse is actually over the timegrid area (not all-day events)
if (relativeY >= 0 && relativeY <= rect.height) {
// Create highlight for the specific hour in the specific day
const highlight = document.createElement("div");
highlight.className = "hour-highlight";
highlight.style.top = `${hourIndex * hourHeight}px`;
highlight.style.height = `${hourHeight}px`;
(targetColumn as HTMLElement).style.position = "relative";
targetColumn.appendChild(highlight);
}
}
};
const handleMouseLeave = () => {
// Remove all hour highlights when mouse leaves calendar
const timegridEl =
calendarEl.querySelector(".fc-timegrid-body");
if (timegridEl) {
timegridEl
.querySelectorAll(".hour-highlight")
.forEach((el: Element) => el.remove());
}
};
calendarEl.addEventListener("mousemove", handleMouseMove);
calendarEl.addEventListener("mouseleave", handleMouseLeave);
// Store handlers for cleanup
(calendarEl as any).__calendarMouseMoveHandler =
handleMouseMove;
(calendarEl as any).__calendarMouseLeaveHandler =
handleMouseLeave;
}
}
}}
viewWillUnmount={(arg) => {
// Clean up event listeners to prevent memory leaks
const calendarEl = document.querySelector(".fc") as HTMLElement;
if (calendarEl) {
if ((calendarEl as any).__calendarMouseMoveHandler) {
calendarEl.removeEventListener(
"mousemove",
(calendarEl as any).__calendarMouseMoveHandler
);
delete (calendarEl as any).__calendarMouseMoveHandler;
}
if ((calendarEl as any).__calendarMouseLeaveHandler) {
calendarEl.removeEventListener(
"mouseleave",
(calendarEl as any).__calendarMouseLeaveHandler
);
delete (calendarEl as any).__calendarMouseLeaveHandler;
}
}
}}
eventClick={(info) => {
info.jsEvent.preventDefault(); // don't let the browser navigate
+104 -7
View File
@@ -5,8 +5,11 @@
.fc-theme-standard td,
.fc-theme-standard th {
border: 1px solid #b8c1cc;
}
th.fc-col-header-cell.fc-day {
border-left: 0;
}
a.fc-timegrid-axis-cushion {
color: #243b55;
text-align: center;
@@ -24,11 +27,12 @@ a.fc-timegrid-axis-cushion {
font-weight: 500;
line-height: 20px;
letter-spacing: 0.25px;
font-family: "Inter", sans-serif;
}
.fc-daygrid-day-top .fc-daygrid-day-number,
span.fc-daygrid-day-number {
color: #243b55;
margin: 0 8px 0 0;
margin: 0 6px 0 0;
font-family: Roboto;
font-size: 28px;
font-style: normal;
@@ -44,11 +48,12 @@ span.fc-daygrid-day-number:after {
content: "";
z-index: -1;
border-radius: 50%;
width: 48px;
height: 48px;
width: 45px;
height: 45px;
position: absolute;
left: 0;
top: -1px;
top: 1px;
left: 1px;
transition: background-color 0.3s ease;
}
span.fc-daygrid-day-number.current-date {
color: #fff;
@@ -65,10 +70,16 @@ th.fc-timegrid-axis.fc-scrollgrid-shrink {
border-right: 1px solid #b8c1cc;
}
.fc .fc-timegrid-axis-cushion {
min-width: 70px;
max-width: 70px;
min-width: 80px;
max-width: 80px;
padding: 0;
text-align: left;
font-family: Roboto;
font-size: 12.507px;
font-style: normal;
font-weight: 400;
line-height: 18.76px;
padding: 0 0 0 10px;
}
.fc .fc-timegrid-divider {
display: none;
@@ -85,6 +96,7 @@ th.fc-timegrid-axis.fc-scrollgrid-shrink {
}
th.fc-col-header-cell.fc-day {
border-right: 0;
border-bottom: 0;
}
.fc .fc-scrollgrid {
border: 0;
@@ -106,3 +118,88 @@ th.fc-col-header-cell.fc-day {
top: 9.5px;
right: -15px;
}
.fc .fc-timegrid-col.fc-day-today,
.fc .fc-daygrid-day.fc-day-today {
background-color: #fff;
}
.fc .fc-daygrid-body-unbalanced .fc-daygrid-day-events {
min-height: 36px;
}
.fc .fc-daygrid-body-natural .fc-daygrid-day-events {
margin-bottom: 0;
}
.fc .fc-timegrid-now-indicator-line {
z-index: 5;
border: 0 solid #f67e35;
border-top-width: 2px;
position: absolute;
left: 0;
right: 0;
}
.fc .fc-timegrid-now-indicator-line::after {
content: "";
z-index: 5;
background-color: #f67e35;
border-radius: 50%;
width: 10px;
height: 10px;
position: absolute;
top: -6px;
left: -5px;
}
.fc .fc-timegrid-now-indicator-arrow {
display: none;
}
.fc-day-today .fc-timegrid-now-indicator-container {
overflow: unset;
}
.fc .fc-timegrid-slot {
height: auto;
min-height: 40px !important;
}
.fc .fc-timegrid-slot-label {
display: flex;
align-items: flex-start;
padding-top: 2px;
height: 100%;
}
tr:has(> td.fc-timegrid-divider.fc-cell-shaded) {
display: none;
}
.fc .fc-scrollgrid-section td {
border-bottom: 0;
}
.fc .fc-scrollgrid-section td .fc-daygrid-day-frame {
border-bottom: 1px solid #b8c1cc;
}
th.fc-col-header-cell.fc-day {
cursor: pointer;
}
th.fc-col-header-cell.fc-day:hover
.fc-daygrid-day-number:not(.current-date)::after {
background-color: #b8c1cc56;
}
/* Hover effects for month view using CSS */
.fc-daygrid-day:hover {
background-color: #b8c1cc1a !important;
transition: background-color 0.3s ease;
}
/* Hover effects for week and day views using JavaScript */
.hoverable-day-cell {
transition: background-color 0.3s ease;
}
.hour-highlight {
transition: background-color 0.3s ease;
background-color: #b8c1cc1a !important;
position: absolute;
left: 0;
right: 0;
pointer-events: none;
z-index: 1;
}
.fc-timegrid-slot-label {
background-color: #fff !important;
}