CalendarApp.jsx
​
import {useState} from 'react'
const CalendarApp = () => {
const daysOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const monthsOfYear = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
const currentDate = new Date()
const [currentMonth, setCurrentMonth] = useState(currentDate.getMonth())
const [currentYear, setCurrentYear] = useState(currentDate.getFullYear())
const [selectedDate, setSelectedDate] = useState(currentDate)
const [showEventPopup, setShowEventPopup] = useState(false)
const [events, setEvents] = useState([])
const [eventTime, setEventTime] = useState({hours: '00', minutes: "00"})
const [eventText, setEventText] = useState('')
const [editingEvent, setEditingEvent] = useState(null)
const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate()
const firstDayOfMonth = new Date(currentYear, currentMonth, 1).getDay()
const prevMonth = () => {
setCurrentMonth((prevMonth) => (prevMonth === 0 ? 11 : prevMonth - 1))
setCurrentYear((prevYear) => (currentMonth === 0 ? prevYear - 1 : prevYear))
}
const nextMonth = () => {
setCurrentMonth((prevMonth) => (prevMonth === 11 ? 0 : prevMonth + 1))
setCurrentYear((prevYear) => (currentMonth === 11 ? prevYear + 1 : prevYear))
}
const handleDayClick = (day) => {
const clickedDate = new Date(currentYear, currentMonth, day)
const today = new Date()
if (clickedDate >= today || isSameDay(clickedDate, today)) {
setSelectedDate(clickedDate)
setShowEventPopup(true)
setEventTime({ hours: '00', minutes: '00'})
setEventText("")
setEditingEvent(null)
}
}
const isSameDay = (date1, date2) => {
return(
date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate()
)
}
const handleEventSubmit = () => {
const newEvent = {
id: editingEvent ? editingEvent.id : Date.now(),
date: selectedDate,
time: `${eventTime.hours.padStart(2, '0')}:${eventTime.minutes.padStart(2, '0')}`,
text: eventText,
}
let updatedEvents = [...events]
if(editingEvent) {
updatedEvents = updatedEvents.map((event) =>
event.id === editingEvent.id ? newEvent : event,
)
}
else {
updatedEvents.push(newEvent)
}
updatedEvents.sort((a, b) => new Date(a.date) - new Date(b.date))
setEvents(updatedEvents)
setEventTime({hours: '00', minutes: '00'})
setEventText("")
setShowEventPopup(false)
setEditingEvent(null)
}
const handleEditEvent = (event) => {
setSelectedDate(new Date(event.date))
setEventTime({
hours: event.time.split(":")[0],
minutes: event.time.split(":")[1],
})
setEventText(event.text)
setEditingEvent(event)
setShowEventPopup(true)
}
const handleDeleteEvent = (eventId) => {
const updatedEvents = events.filter((event) => event.id !== eventId)
setEvents(updatedEvents)
}
const handleTimeChange = (e) => {
const {name, value} = e.target
setEventTime((prevTime) => ({...prevTime, [name]: value.padStart(2, '0')}))
}
return (
<div className="calendar-app">
<div className="calendar">
<h1 className="heading">Calendar</h1>
<div className="navigate-date">
<h2 className="month">{monthsOfYear[currentMonth]},</h2>
<h2 className="year">{currentYear}</h2>
<div className="buttons">
<i className="bx bx-chevron-left" onClick={prevMonth}></i>
<i className="bx bx-chevron-right" onClick={nextMonth}></i>
</div>
</div>
<div className="weekdays">
{daysOfWeek.map((day) => <span key={day}>{day}</span>)}
</div>
<div className="days">
{[...Array(firstDayOfMonth).keys()].map((_, index) => (
<span key={`empty-${index}`} />
))}
{[...Array(daysInMonth).keys()].map((day) => {
const isCurrentDay =
day + 1 === currentDate.getDate() &&
currentMonth === currentDate.getMonth() &&
currentYear === currentDate.getFullYear();
const hasEvent = events.some(event =>
isSameDay(new Date(event.date), new Date(currentYear, currentMonth, day + 1))
);
return (
<span
key={day + 1}
className={`${isCurrentDay ? 'current-day' : ''} ${hasEvent ? 'event-day' : ''}`}
onClick={() => handleDayClick(day + 1)}
>
{day + 1}
</span>
);
})}
</div>
</div>
<div className="events">
{showEventPopup && (
<div className="event-popup">
<div className="time-input">
<div className="event-popup-time">Time</div>
<input
type="number"
name="hours"
min={0}
max={24}
className="hours"
value={eventTime.hours}
onChange={handleTimeChange}
/>
<input type="number" name="minutes" min={0} max={59} className="minutes" value={eventTime.minutes}
onChange={(e) => setEventTime({...eventTime, minutes: e.target.value})}/>
</div>
<textarea
placeholder="Enter Event Text (Maximum 60 Characters)"
value={eventText} onChange={(e) => {
if(e.target.value.length <= 60) {
setEventText(e.target.value)
}
}}
></textarea>
<button className="event-popup-btn" onClick={handleEventSubmit}>
{editingEvent ? "Update Event" : "Add Event"}
</button>
<button className="close-event-popup" onClick={() => setShowEventPopup(false)}>
<i className="bx bx-x"></i>
</button>
</div>
)}
{events.map((event, index) => (
<div className="event"key={index}>
<div className="event-date-wrapper">
<div className="event-date">{`${
monthsOfYear[event.date.getMonth()]} ${event.date.getDate()} ${event.date.getFullYear()}`}
</div>
<div className="event-time">{event.time}</div>
</div>
<div className="event-text">{event.text}</div>
<div className="event-button">
<i className="bx bxs-edit-alt" onClick={() => handleEditEvent(event)}></i>
<i className="bx bxs-message-alt-x" onClick={() => handleDeleteEvent(event.id)}></i>
</div>
</div>
))}
</div>
</div>
)
​}
export default CalendarApp
CalendarApp.css​
​
.calendar-app {
width: 60%;
min-width: 90vmin;
aspect-ratio: 3 / 2;
background-color: #1e242d;
padding: 3rem;
border-radius: 3rem;
border: 1rem solid #0f1319;
display: flex;
column-gap: 5rem;
position: relative;
transform-style: preserve-3d;
​}
.calendar-app::after {
content: "";
position: absolute;
bottom: -12rem;
left: 50%;
transform: translate(-50%) rotateX(50deg);
width: 90%;
height: 16rem;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 20rem;
filter: blur(4rem);
​}
.calendar {
width: 40%;
​}
.heading {
font-family: "Bebas Neue", sans-serif;
font-size: clamp(4rem, 3.8cqi, 7rem);
color: #fff;
letter-spacing: 0.3rem;
margin-left: 15px;
​}
.navigate-date {
display: flex;
align-items: center;
margin: 3.5rem 0;
​}
.navigate-date h2 {
font-size: clamp(1.5rem, 1.5cqi, 2.5rem);
color: #bbb;
padding-left: 1.3rem;
​}
.buttons {
display: flex;
column-gap: 1rem;
margin-left: auto;
​}
.buttons i {
width: 3.5rem;
height: 3.5rem;
background-color: #2c3542;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
color: #c97f1a;
cursor: pointer;
​}
.weekdays {
width: 100%;
display: flex;
margin: 3rem 0;
​}
.weekdays span {
width: calc(100% / 7);
font-size: clamp(1rem, 0.8cqi, 1.3rem);
font-weight: bold;
text-transform: uppercase;
color: #78879e;
letter-spacing: 0.1rem;
display: flex;
justify-content: center;
​}
.days {
display: flex;
flex-wrap: wrap;
​}
.days span {
font-size: clamp(1.2rem, 1cqi, 1.6rem);
width: calc(100% / 7);
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
color: #ddd;
cursor: pointer;
text-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.2);
​}
.current-day {
background-color: #ef9011;
border-radius: 50%;
box-shadow: 0 0 1.5rem 1rem rgba(239, 144, 17, 0.3);
​}
.events {
width: 60%;
height: 100%;
padding: 3rem 0;
overflow-y: auto;
​}
.events::-webkit-scrollbar {
display: none;
​}
.event-popup {
position: absolute;
top: 38%;
left: 3rem;
background-color: #161b22;
width: clamp(25rem, 21cqi, 40rem);
aspect-ratio: 10 / 9;
border-radius: 1rem;
box-shadow: 0 1rem 3rem rbga(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
row-gap: 2rem;
​}
.time-input {
display: flex;
column-gap: 1rem;
​}
.event-popup-time {
width: clamp(4rem, 4cqi, 7rem);
background-color: #00a3ff;
color: #fff;
font-family: "Bebas Neue", sans-serif;
font-size: clamp(1.5rem, 1.5cqi, 2.2rem);
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 0 1.5rem 1rem rgba(0, 163, 255, 0.2);
letter-spacing: 0.1rem;
​}
.time-input input {
background-color: transparent;
border: none;
border-top: 0.2rem solid #00a3ff;
border-bottom: 0.2rem solid #00a3ff;
color: #fff;
width: clamp(4rem, 4cqi, 7rem);
height: 4rem;
text-align: center;
font-size: clamp(1.2rem, 1.2cqi, 1.6rem);
​}
.time-time input [type="number"] ::-webkit-outer-spin-button,
.time-time input [type="number"] ::-webkit-inner-spin-button{
appearance: none;
​}
.event-popup textarea {
width: clamp(15rem, 15cqi, 25rem);
aspect-ratio: 5 / 2;
resize: none;
background-color: #0f1319;
border: none;
padding: 1rem;
border-radius: 0.5rem;
color: #78879e;
transition: border 0.5s;
​}
.event-popup textarea:focus{
border: 0.1rem solid #00a3ff;
​}
.event-popup textarea::placeholder {
font-size: clamp(1rem, 0.8cqi, 1.2rem);
color: #78879e;
​}
.event-popup textarea:focus::placeholder{
color: transparent;
​}
.event-popup-btn {
width: clamp(15rem, 15cqi, 25rem);
height: 4rem;
background-color: #ef9011;
color: #fff;
font-family: "Bebas Neue", sans-serif;
font-size: clamp(1.5rem, 1.5cqi, 2.2rem);
letter-spacing: 0.1rem;
border: none;
box-shadow: 0 0 1.5rem 1rem rgba(239, 144, 17, 0.2);
cursor: pointer;
​}
​.event-popup-btn:active {
transform: translateY(0.1rem);
​}
.close-event-popup {
position: absolute;
top: 1rem;
right: 1rem;
background-color: transparent;
border: none;
cursor: pointer;
​}
.close-event-popup i {
font-size: 2.5rem;
color: #fff;
​}
.event {
width: 100%;
height: 9rem;
background-color: #00a3ff;
padding: 1.5rem 0;
border-radius: 1rem;
display: flex;
align-items: center;
margin-bottom: 2rem;
position: relative;
​}
.event-day {
background-color: #00a3ff;
​}
.event-date-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 30%;
border-right: 0.1rem solid rgba(255, 255, 255, 0.5);
​}
.event-date {
font-size: clamp(1rem, 1cqi, 1.2rem);
color: #fff;
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
text-align: center;
​}
.event-time {
font-size: clamp(1.3rem, 1cqi, 1.6rem);
line-height: 4rem;
font-weight: bold;
color: #fff;
​}
.event-text {
font-size: clamp(1.2rem, 1cqi, 1.4rem);
line-height: 2rem;
color: #fff;
width: 75%;
padding: 0 3rem 0 1rem;
margin-left: 5px;
overflow-wrap: break-word;
​}
.event-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: 1rem;
display: flex;
flex-direction: column;
row-gap: 1rem;
​}
.event-button i {
font-size: 1.6rem;
color: #fff;
cursor: pointer;
​}
@media (max-width: 850px) {
.calendar-app{
flex-direction: column;
row-gap: 2rem;
aspect-ratio: 3 / 2;
}
.calendar {
width: 70%;
margin: auto;
}
.navigate-date {
margin: 1rem 0;
}
.weekdays {
margin: 1rem 0;
}
.events {
width: 80%;
margin: auto;
}
.event-popup {
top: 18%;
left: 50%;
transform: translateX(-50%);
width: 60%;
aspect-ratio: 4 / 3;
row-gap: 1rem;
}
.event-popup-time {
width: clamp(5rem, 10cqi, 8rem);
font-size: clamp(1.5rem, 3cqi, 2.2rem);
​ }
.time-input input {
width: clamp(5rem, 10cqi, 8rem);
font-size: clamp(1.2rem, 2cqi, 1rem);
​ }
.event-popup textarea {
width: clamp(18rem, 35cqi, 28rem);
}
.event-popup textarea::placeholder {
font-size: clamp(1rem, 1.5cqi, 1.2rem);
}
.event-popup-btn {
width: clamp(18rem, 35cqi, 28rem);
font-size: clamp(1.5rem, 3cqi, 2.2rem);
}
.event-date {
font-size: clamp(1rem, 1cqi, 1rem);
​ }
.event-time {
font-size: clamp(1rem, 2cqi, 1.6rem);
}
.event-text {
font-size: clamp(1rem, 2.5cqi, 1.4rem);
​ }
}
@media (max-width: 500px) {
.calendar-app {
aspect-ratio: 10 / 9;
}
.calendar {
width: 100%;
}
.event-popup {
top: 23%;
width: 80%;
}
.events {
width: 100%;
}
​}
@media (max-width: 375px) {
.calendar-app {
aspect-ratio: 3 / 2;
}
​}
​App.jsx
​
import CalendarApp from "./Components/CalendarApp"
import './Components/CalendarApp.css'
const App = () => {
return (
<div className='container'>
<CalendarApp />
</div>
)
​}
export default App
​index.css
​
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Comfortaa:wght@300..700&display=swap');
​*{
margin: 0;
padding: 0;
box-sizing: border-box;
outline: none;
font-family: "Comfortaa", sans-serif;
​}
html {
font-size: 62.5%;
​}
.container {
width: 100%;
height: 100vh;
background-color: #2c3542;
display: grid;
place-items: center;
overflow: hidden;
perspective: 100rem;
​}
@media (max-width: 500px) {
html {
font-size: 55%;
}
}