kenat
Version:
A JavaScript library for the Ethiopian calendar with date and time support.
234 lines (207 loc) • 11.8 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kenat Calendar Example</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; text-align: center; background-color: #f9f9f9; padding: 1rem;}
table { border-collapse: collapse; margin: 1rem auto; box-shadow: 0 4px 12px rgba(0,0,0,0.1); background-color: #fff; }
th, td { border: 1px solid #e0e0e0; padding: 0.5rem; width: 85px; height: 85px; vertical-align: top; }
th { background-color: #f5f5f5; font-weight: 600; }
.today { background-color: #fffde7; border: 2px solid #ffc107; font-weight: bold; }
.holiday { background-color: #e3f2fd; cursor: pointer; transition: background-color 0.2s; }
.holiday:hover { background-color: #bbdefb; }
.holiday-labels { font-size: 0.75rem; color: #1e88e5; margin-top: 5px; line-height: 1.2; }
/* Navigation and Controls Styling */
.header-controls, .controls, .filter-controls { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin: 1rem 0; align-items: center; }
.nav-controls { display: flex; align-items: center; gap: 0.5rem; }
.filter-controls { margin-bottom: 1.5rem; background-color: #fff; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
.filter-controls label { font-size: 0.9rem; cursor: pointer; display: flex; align-items: center; gap: 4px; }
button, select {
padding: 0.6rem 1.2rem;
font-size: 0.9rem;
cursor: pointer;
border-radius: 6px;
border: 1px solid #ccc;
background-color: #fff;
transition: background-color 0.2s, box-shadow 0.2s;
}
select { padding: 0.5rem; }
button:hover { background-color: #f0f0f0; }
button:active { box-shadow: inset 0 2px 4px rgba(0,0,0,0.1); }
/* Modal Styles */
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5); justify-content: center; align-items: center; }
.modal-content { background-color: #fefefe; margin: auto; padding: 25px; border: 1px solid #888; width: 90%; max-width: 500px; border-radius: 10px; position: relative; box-shadow: 0 5px 15px rgba(0,0,0,0.3); text-align: left; }
.modal-close { color: #aaa; position: absolute; top: 10px; right: 20px; font-size: 28px; font-weight: bold; cursor: pointer; }
.modal-close:hover { color: #000; }
.modal-content h3 { margin-top: 0; }
.modal-content hr { border: 0; border-top: 1px solid #eee; margin: 15px 0; }
</style>
</head>
<body>
<h1>Kenat Calendar</h1>
<div id="calendar">Loading...</div>
<!-- Modal HTML -->
<div id="holidayModal" class="modal">
<div class="modal-content">
<span class="modal-close">×</span>
<div id="modalBody"></div>
</div>
</div>
<script type="module">
import Kenat, { MonthGrid, HolidayTags, toArabic } from '../../src/index.js';
import { monthNames } from '../../src/constants.js';
let monthGridInstance;
// Settings
let useGeez = false;
let weekdayLang = 'amharic';
let weekStart = 1;
let holidayFilter = null;
const initialDate = new Kenat().getEthiopian();
let currentYear = initialDate.year;
let currentMonth = initialDate.month;
// --- MODAL LOGIC ---
const modal = document.getElementById('holidayModal');
const modalBody = document.getElementById('modalBody');
const closeModal = document.querySelector('.modal-close');
function showHolidayModal(holidays) {
let content = '';
holidays.forEach((h, index) => {
content += `<h3>${h.name}</h3><p>${h.description}</p>`;
if (index < holidays.length - 1) content += '<hr>';
});
modalBody.innerHTML = content;
modal.style.display = 'flex';
}
closeModal.onclick = () => { modal.style.display = 'none'; }
window.onclick = (event) => { if (event.target == modal) modal.style.display = 'none'; }
// --- CALENDAR RENDERING ---
function renderCalendar(gridData) {
let { headers, days, year, month, monthName } = gridData;
const holidaysForDay = (item) => item.holidays.map(h => h.name).join(', ');
const yearForComparison = typeof year === 'string' ? toArabic(year) : year;
const yearOptions = Array.from({length: 201}, (_, i) => 1900 + i)
.map(y => `<option value="${y}" ${y === yearForComparison ? 'selected' : ''}>${y}</option>`).join('');
const monthOptions = monthNames.amharic
.map((name, i) => `<option value="${i + 1}" ${i + 1 === month ? 'selected' : ''}>${name}</option>`).join('');
let html = `
<div class="header-controls">
<button id="prevMonth">⬅️</button>
<div class="nav-controls">
<select id="monthSelector">${monthOptions}</select>
<select id="yearSelector">${yearOptions}</select>
</div>
<button id="nextMonth">➡️</button>
</div>
<div class="controls">
<button id="toggleGeez">Geez (${useGeez ? 'ON' : 'OFF'})</button>
<button id="toggleLang">Lang (${weekdayLang})</button>
<button id="toggleWeekStart">Start (${weekStart === 1 ? 'Mon' : 'Sun'})</button>
</div>
<div class="filter-controls" id="filterControls"></div>
<table><thead><tr>`;
html += headers.map(day => `<th>${day}</th>`).join('');
html += '</tr></thead><tbody>';
for (let i = 0; i < days.length; i += 7) {
html += '<tr>';
for (let j = 0; j < 7; j++) {
const item = days[i + j];
if (!item) {
html += '<td></td>';
} else {
const todayClass = item.isToday ? 'today' : '';
const isHoliday = item.holidays && item.holidays.length > 0;
const holidaysText = isHoliday ? holidaysForDay(item) : '';
const holidayAttr = isHoliday ? `data-day-index="${i + j}"` : '';
html += `<td class="${todayClass} ${isHoliday ? 'holiday' : ''}" ${holidayAttr}>
<strong>${item.ethiopian.day}</strong><br/>
<small>${item.gregorian.month}/${item.gregorian.day}</small>
${holidaysText ? `<div class="holiday-labels">${holidaysText}</div>` : ''}
</td>`;
}
}
html += '</tr>';
}
html += '</tbody></table>';
document.getElementById('calendar').innerHTML = html;
renderFilterControls();
attachEventListeners();
}
function renderFilterControls() {
const controlsContainer = document.getElementById('filterControls');
let controlsHtml = `<label><input type="checkbox" name="holidayTag" value="all" ${!holidayFilter ? 'checked' : ''}> All</label>`;
for (const key in HolidayTags) {
const value = HolidayTags[key];
const isChecked = holidayFilter && holidayFilter.includes(value);
controlsHtml += `<label><input type="checkbox" name="holidayTag" value="${value}" ${isChecked ? 'checked' : ''}> ${value}</label>`;
}
controlsContainer.innerHTML = controlsHtml;
}
function attachEventListeners() {
document.getElementById('prevMonth').onclick = () => {
const newGrid = monthGridInstance.down().generate();
currentYear = (typeof newGrid.year === 'string') ? toArabic(newGrid.year) : newGrid.year;
currentMonth = newGrid.month;
renderCalendar(newGrid);
};
document.getElementById('nextMonth').onclick = () => {
const newGrid = monthGridInstance.up().generate();
currentYear = (typeof newGrid.year === 'string') ? toArabic(newGrid.year) : newGrid.year;
currentMonth = newGrid.month;
renderCalendar(newGrid);
};
document.getElementById('monthSelector').onchange = (e) => { currentMonth = parseInt(e.target.value); rerender(); };
document.getElementById('yearSelector').onchange = (e) => { currentYear = parseInt(e.target.value); rerender(); };
document.getElementById('toggleGeez').onclick = () => { useGeez = !useGeez; rerender(); };
document.getElementById('toggleLang').onclick = () => { weekdayLang = weekdayLang === 'amharic' ? 'english' : 'amharic'; rerender(); };
document.getElementById('toggleWeekStart').onclick = () => { weekStart = weekStart === 1 ? 0 : 1; rerender(); };
document.querySelectorAll('.holiday').forEach(cell => {
cell.onclick = () => {
const dayIndex = parseInt(cell.getAttribute('data-day-index'));
const dayData = monthGridInstance.generate().days[dayIndex];
if (dayData && dayData.holidays.length > 0) {
showHolidayModal(dayData.holidays);
}
};
});
const allCheckbox = document.querySelector('input[name="holidayTag"][value="all"]');
const otherCheckboxes = document.querySelectorAll('input[name="holidayTag"]:not([value="all"])');
const updateFilter = () => {
const checkedValues = Array.from(otherCheckboxes).filter(i => i.checked).map(i => i.value);
if (checkedValues.length === 0) {
allCheckbox.checked = true;
holidayFilter = null;
} else {
allCheckbox.checked = false;
holidayFilter = checkedValues;
}
rerender();
};
allCheckbox.onchange = () => {
if (allCheckbox.checked) {
otherCheckboxes.forEach(cb => cb.checked = false);
holidayFilter = null;
rerender();
} else {
allCheckbox.checked = true;
}
};
otherCheckboxes.forEach(checkbox => { checkbox.onchange = updateFilter; });
}
function rerender() {
monthGridInstance = new MonthGrid({
year: currentYear,
month: currentMonth,
useGeez,
weekdayLang,
weekStart,
holidayFilter,
});
renderCalendar(monthGridInstance.generate());
}
// Initial render
rerender();
</script>
</body>
</html>