diff --git a/examples/interactive-calendar/README.md b/examples/interactive-calendar/README.md new file mode 100644 index 00000000..08eb75a5 --- /dev/null +++ b/examples/interactive-calendar/README.md @@ -0,0 +1,66 @@ +# 📅 Interactive Calendar + +A lightweight, responsive calendar widget built with vanilla JavaScript. This project demonstrates DOM manipulation, Date object handling, and local state management without external libraries. + +## 🚀 Features + +- **Dynamic Rendering:** Automatically generates the correct grid for any month and year. +- **Navigation:** Browse through past and future months. +- **Current Date Highlighting:** visual indicator for today's date. +- **Event Management (Bonus):** Click any date to add, view, or delete notes. +- **Data Persistence:** Events are saved to the browser's `localStorage`, so they remain after refreshing the page. +- **Responsive Design:** Built with CSS Grid to adapt to different screen sizes. + +## 🛠️ Technologies Used + +- **HTML5:** Semantic structure. +- **CSS3:** Flexbox and Grid layout; CSS Variables for theming. +- **JavaScript (ES6+):** Logic for date calculation and event handling. + +## 📂 Project Structure + +```text +interactive-calendar/ +├── index.html # Main HTML structure +├── style.css # Styling and Grid layout +├── script.js # Calendar logic and Event handling +└── README.md # Project documentation + +##💡 How It Works +1. Date Calculation +The calendar grid is calculated using the native Date object: + +Start Day: new Date(year, month, 1).getDay() determines which day of the week the month starts on (0=Sunday, 1=Monday). + +Total Days: new Date(year, month + 1, 0).getDate() retrieves the exact number of days in the current month. + +2. Rendering the Grid +We use a loop to generate
elements. + +First, we insert empty placeholder divs to align the 1st of the month with the correct weekday column. + +Then, we generate the actual numbered days. + +3. State Management +Events are stored in a simple JSON object and saved to LocalStorage: + +JavaScript +// Data Structure Example +{ + "2023-10-25": "Meeting with team", + "2023-10-31": "Halloween Party" +} + +## 🏃‍♂️ How to Run +Clone the repository. + +Navigate to the interactive-calendar folder. + +Open index.html in your browser. + +# 🔮 Future Improvements +Add drag-and-drop functionality for events. + +Support for multiple events per day. + +Add specific time slots for events. \ No newline at end of file diff --git a/examples/interactive-calendar/index.html b/examples/interactive-calendar/index.html new file mode 100644 index 00000000..bbca4a72 --- /dev/null +++ b/examples/interactive-calendar/index.html @@ -0,0 +1,39 @@ + + + + + + Interactive Calendar + + + + +
+
+
+ < +

+ > +
+
+ +
+
Sun
Mon
Tue
Wed
Thu
Fri
Sat
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/examples/interactive-calendar/script.js b/examples/interactive-calendar/script.js new file mode 100644 index 00000000..ec4b04b2 --- /dev/null +++ b/examples/interactive-calendar/script.js @@ -0,0 +1,121 @@ +const monthYearElement = document.getElementById('month-year'); +const datesElement = document.getElementById('calendar-dates'); +const prevBtn = document.getElementById('month-prev'); +const nextBtn = document.getElementById('month-next'); + +// Modal Elements +const modal = document.getElementById('event-modal'); +const closeModalBtn = document.querySelector('.close-btn'); +const selectedDateTitle = document.getElementById('selected-date'); +const eventInput = document.getElementById('event-input'); +const saveEventBtn = document.getElementById('save-event-btn'); +const deleteEventBtn = document.getElementById('delete-event-btn'); + +let currentDate = new Date(); +let clickedDate = null; +let events = JSON.parse(localStorage.getItem('events')) || {}; + +const months = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +]; + +// --- Core Functions --- + +function renderCalendar() { + const year = currentDate.getFullYear(); + const month = currentDate.getMonth(); + + monthYearElement.innerText = `${months[month]} ${year}`; + datesElement.innerHTML = ''; + + // First day of the month (0 = Sunday, 1 = Monday, etc.) + const firstDay = new Date(year, month, 1).getDay(); + // Total days in the current month + const totalDays = new Date(year, month + 1, 0).getDate(); + + // Add empty divs for days before the first day of the month + for (let i = 0; i < firstDay; i++) { + const emptyDiv = document.createElement('div'); + datesElement.appendChild(emptyDiv); + } + + // Render days + for (let day = 1; day <= totalDays; day++) { + const dayDiv = document.createElement('div'); + dayDiv.classList.add('day'); + dayDiv.innerText = day; + + // Check if this day is "Today" + const today = new Date(); + if (day === today.getDate() && month === today.getMonth() && year === today.getFullYear()) { + dayDiv.classList.add('current-date'); + } + + // Check for Events + const dateString = `${year}-${month + 1}-${day}`; + if (events[dateString]) { + dayDiv.classList.add('has-event'); + dayDiv.title = events[dateString]; // Tooltip + } + + // Add Click Event for Modal + dayDiv.addEventListener('click', () => openModal(dateString)); + + datesElement.appendChild(dayDiv); + } +} + +function openModal(dateStr) { + clickedDate = dateStr; + selectedDateTitle.innerText = `Event for: ${dateStr}`; + eventInput.value = events[dateStr] || ''; + modal.classList.remove('hidden'); +} + +function closeModal() { + modal.classList.add('hidden'); + clickedDate = null; +} + +// --- Event Listeners --- + +prevBtn.addEventListener('click', () => { + currentDate.setMonth(currentDate.getMonth() - 1); + renderCalendar(); +}); + +nextBtn.addEventListener('click', () => { + currentDate.setMonth(currentDate.getMonth() + 1); + renderCalendar(); +}); + +closeModalBtn.addEventListener('click', closeModal); + +saveEventBtn.addEventListener('click', () => { + if (eventInput.value.trim()) { + events[clickedDate] = eventInput.value; + localStorage.setItem('events', JSON.stringify(events)); + } + closeModal(); + renderCalendar(); +}); + +deleteEventBtn.addEventListener('click', () => { + if (events[clickedDate]) { + delete events[clickedDate]; + localStorage.setItem('events', JSON.stringify(events)); + } + closeModal(); + renderCalendar(); +}); + +// Close modal if clicking outside +window.addEventListener('click', (e) => { + if (e.target === modal) { + closeModal(); + } +}); + +// Initial Render +renderCalendar(); \ No newline at end of file diff --git a/examples/interactive-calendar/style.css b/examples/interactive-calendar/style.css new file mode 100644 index 00000000..f5559b09 --- /dev/null +++ b/examples/interactive-calendar/style.css @@ -0,0 +1,145 @@ +:root { + --primary-color: #4a90e2; + --bg-color: #f4f4f9; + --text-color: #333; + --highlight-color: #e0f7fa; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: var(--bg-color); + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + margin: 0; +} + +.calendar-container { + background: white; + width: 90%; + max-width: 400px; + border-radius: 10px; + box-shadow: 0 10px 25px rgba(0,0,0,0.1); + overflow: hidden; + padding: 20px; +} + +.calendar-header { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 20px; +} + +.calendar-navigation { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + +.nav-btn { + cursor: pointer; + font-size: 1.5rem; + -webkit-user-select: none; + user-select: none; + padding: 0 10px; +} + +.nav-btn:hover { + color: var(--primary-color); +} + +/* Grid System */ +.calendar-weekdays, .calendar-dates { + display: grid; + grid-template-columns: repeat(7, 1fr); + text-align: center; +} + +.calendar-weekdays div { + font-weight: bold; + color: #888; + margin-bottom: 10px; +} + +.day { + padding: 10px; + cursor: pointer; + border-radius: 5px; + transition: background 0.2s; + position: relative; +} + +.day:hover { + background-color: #f0f0f0; +} + +.day.current-date { + background-color: var(--primary-color); + color: white; + font-weight: bold; +} + +.day.has-event::after { + content: ''; + position: absolute; + bottom: 5px; + left: 50%; + transform: translateX(-50%); + width: 5px; + height: 5px; + background-color: red; + border-radius: 50%; +} + +.day.inactive { + color: #ccc; + pointer-events: none; +} + +/* Modal Styles */ +.modal { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0,0,0,0.5); + display: flex; + justify-content: center; + align-items: center; +} + +.hidden { display: none; } + +.modal-content { + background: white; + padding: 20px; + border-radius: 8px; + width: 300px; + position: relative; +} + +.close-btn { + position: absolute; + top: 10px; right: 15px; + cursor: pointer; + font-size: 1.2rem; +} + +textarea { + width: 100%; + height: 60px; + margin: 10px 0; + padding: 5px; +} + +button { + background: var(--primary-color); + color: white; + border: none; + padding: 8px 15px; + cursor: pointer; + border-radius: 4px; +} + +button.secondary { background: #ccc; } \ No newline at end of file