add font Inter
update UI cho main grid calendar. add hover css and state. click able on header cell
This commit is contained in:
+5
-1
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user