feat: implement interactive calendar view grids and confirmation action patterns
This commit is contained in:
+194
-64
@@ -113,10 +113,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const valueInput = picker.querySelector('.datepicker-value');
|
||||
const monthYearLabel = picker.querySelector('.datepicker-month-year');
|
||||
const daysContainer = picker.querySelector('.datepicker-days');
|
||||
if (!valueInput || !monthYearLabel || !daysContainer) return;
|
||||
const weekdaysRow = picker.querySelector('.datepicker-weekdays');
|
||||
if (!valueInput || !daysContainer) return;
|
||||
|
||||
let currentYear = parseInt(picker.dataset.year);
|
||||
let currentMonth = parseInt(picker.dataset.month); // 0-indexed
|
||||
let view = picker.dataset.view || 'days';
|
||||
picker.dataset.view = view;
|
||||
|
||||
if (isNaN(currentYear) || isNaN(currentMonth)) {
|
||||
const val = valueInput.value;
|
||||
@@ -131,59 +134,116 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const today = new Date();
|
||||
|
||||
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
monthYearLabel.textContent = `${monthNames[currentMonth]} ${currentYear}`;
|
||||
|
||||
// Update header label
|
||||
if (monthYearLabel) {
|
||||
if (view === 'days') {
|
||||
monthYearLabel.textContent = `${monthNames[currentMonth]} ${currentYear}`;
|
||||
} else if (view === 'months') {
|
||||
monthYearLabel.textContent = `${currentYear}`;
|
||||
} else if (view === 'years') {
|
||||
const startYear = currentYear - (currentYear % 12);
|
||||
monthYearLabel.textContent = `${startYear} - ${startYear + 11}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle weekdays row visibility
|
||||
if (weekdaysRow) {
|
||||
if (view === 'days') {
|
||||
weekdaysRow.classList.remove('hidden');
|
||||
} else {
|
||||
weekdaysRow.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// Reset grid columns class based on active view mode
|
||||
if (view === 'days') {
|
||||
daysContainer.classList.remove('grid-cols-3');
|
||||
daysContainer.classList.add('grid-cols-7');
|
||||
} else {
|
||||
daysContainer.classList.remove('grid-cols-7');
|
||||
daysContainer.classList.add('grid-cols-3');
|
||||
}
|
||||
|
||||
daysContainer.innerHTML = '';
|
||||
|
||||
const firstDayIndex = new Date(currentYear, currentMonth, 1).getDay();
|
||||
const totalDays = new Date(currentYear, currentMonth + 1, 0).getDate();
|
||||
const prevTotalDays = new Date(currentYear, currentMonth, 0).getDate();
|
||||
if (view === 'days') {
|
||||
const firstDayIndex = new Date(currentYear, currentMonth, 1).getDay();
|
||||
const totalDays = new Date(currentYear, currentMonth + 1, 0).getDate();
|
||||
const prevTotalDays = new Date(currentYear, currentMonth, 0).getDate();
|
||||
|
||||
// Prev month padding
|
||||
for (let i = firstDayIndex - 1; i >= 0; i--) {
|
||||
const dayNum = prevTotalDays - i;
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.disabled = true;
|
||||
btn.className = 'py-1 text-center text-xs text-slate-700 cursor-not-allowed font-medium';
|
||||
btn.textContent = dayNum;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
// Prev month padding
|
||||
for (let i = firstDayIndex - 1; i >= 0; i--) {
|
||||
const dayNum = prevTotalDays - i;
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.disabled = true;
|
||||
btn.className = 'py-1 text-center text-xs text-slate-700 cursor-not-allowed font-medium';
|
||||
btn.textContent = dayNum;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
|
||||
// Current month days
|
||||
for (let day = 1; day <= totalDays; day++) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
const yearStr = currentYear;
|
||||
const monthStr = String(currentMonth + 1).padStart(2, '0');
|
||||
const dayStr = String(day).padStart(2, '0');
|
||||
const dateStr = `${yearStr}-${monthStr}-${dayStr}`;
|
||||
// Current month days
|
||||
for (let day = 1; day <= totalDays; day++) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
const yearStr = currentYear;
|
||||
const monthStr = String(currentMonth + 1).padStart(2, '0');
|
||||
const dayStr = String(day).padStart(2, '0');
|
||||
const dateStr = `${yearStr}-${monthStr}-${dayStr}`;
|
||||
|
||||
const isSelected = selectedDateVal === dateStr;
|
||||
const isToday = today.getFullYear() === currentYear && today.getMonth() === currentMonth && today.getDate() === day;
|
||||
const isSelected = selectedDateVal === dateStr;
|
||||
const isToday = today.getFullYear() === currentYear && today.getMonth() === currentMonth && today.getDate() === day;
|
||||
|
||||
btn.className = `py-1 text-xs rounded-lg font-semibold hover:bg-accent hover:text-accent-foreground transition flex items-center justify-center h-7 w-7 mx-auto ${
|
||||
isSelected ? 'bg-indigo-600 text-white hover:bg-indigo-650' :
|
||||
isToday ? 'border border-sky-500/50 text-sky-400' : 'text-slate-350'
|
||||
}`;
|
||||
btn.dataset.date = dateStr;
|
||||
btn.textContent = day;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
btn.className = `py-1 text-xs rounded-lg font-semibold hover:bg-accent hover:text-accent-foreground transition flex items-center justify-center h-7 w-7 mx-auto ${
|
||||
isSelected ? 'bg-indigo-600 text-white hover:bg-indigo-650' :
|
||||
isToday ? 'border border-sky-500/50 text-sky-400' : 'text-slate-350'
|
||||
}`;
|
||||
btn.dataset.date = dateStr;
|
||||
btn.textContent = day;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
|
||||
// Next month padding
|
||||
const totalGrids = 42;
|
||||
const currentGrids = firstDayIndex + totalDays;
|
||||
for (let i = 1; i <= (totalGrids - currentGrids); i++) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.disabled = true;
|
||||
btn.className = 'py-1 text-center text-xs text-slate-700 cursor-not-allowed font-medium';
|
||||
btn.textContent = i;
|
||||
daysContainer.appendChild(btn);
|
||||
// Next month padding
|
||||
const totalGrids = 42;
|
||||
const currentGrids = firstDayIndex + totalDays;
|
||||
for (let i = 1; i <= (totalGrids - currentGrids); i++) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.disabled = true;
|
||||
btn.className = 'py-1 text-center text-xs text-slate-700 cursor-not-allowed font-medium';
|
||||
btn.textContent = i;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
} else if (view === 'months') {
|
||||
const shortMonthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
for (let m = 0; m < 12; m++) {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = `py-3.5 text-xs rounded-xl font-semibold transition text-center hover:bg-accent hover:text-accent-foreground ${
|
||||
m === currentMonth ? 'bg-indigo-600 text-white font-bold' : 'text-slate-350'
|
||||
}`;
|
||||
btn.dataset.monthVal = m;
|
||||
btn.textContent = shortMonthNames[m];
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
} else if (view === 'years') {
|
||||
const startYear = currentYear - (currentYear % 12);
|
||||
for (let y = 0; y < 12; y++) {
|
||||
const yearNum = startYear + y;
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = `py-3.5 text-xs rounded-xl font-semibold transition text-center hover:bg-accent hover:text-accent-foreground ${
|
||||
yearNum === currentYear ? 'bg-indigo-600 text-white font-bold' : 'text-slate-350'
|
||||
}`;
|
||||
btn.dataset.yearVal = yearNum;
|
||||
btn.textContent = yearNum;
|
||||
daysContainer.appendChild(btn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize custom date pickers
|
||||
document.querySelectorAll('.custom-datepicker').forEach(picker => {
|
||||
renderCalendar(picker);
|
||||
@@ -205,41 +265,85 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (pop !== popover) pop.classList.add('hidden');
|
||||
});
|
||||
|
||||
popover.classList.toggle('hidden');
|
||||
const isHidden = popover.classList.toggle('hidden');
|
||||
if (!isHidden) {
|
||||
picker.dataset.view = 'days';
|
||||
renderCalendar(picker);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prev Month
|
||||
// Prev Month / Year / Decade
|
||||
const prevBtn = event.target.closest('.datepicker-prev');
|
||||
if (prevBtn) {
|
||||
const picker = prevBtn.closest('.custom-datepicker');
|
||||
let currentMonth = parseInt(picker.dataset.month);
|
||||
let currentYear = parseInt(picker.dataset.year);
|
||||
currentMonth--;
|
||||
if (currentMonth < 0) {
|
||||
currentMonth = 11;
|
||||
currentYear--;
|
||||
if (picker) {
|
||||
let currentMonth = parseInt(picker.dataset.month);
|
||||
let currentYear = parseInt(picker.dataset.year);
|
||||
const view = picker.dataset.view || 'days';
|
||||
|
||||
if (view === 'days') {
|
||||
currentMonth--;
|
||||
if (currentMonth < 0) {
|
||||
currentMonth = 11;
|
||||
currentYear--;
|
||||
}
|
||||
picker.dataset.month = currentMonth;
|
||||
picker.dataset.year = currentYear;
|
||||
} else if (view === 'months') {
|
||||
currentYear--;
|
||||
picker.dataset.year = currentYear;
|
||||
} else if (view === 'years') {
|
||||
currentYear -= 12;
|
||||
picker.dataset.year = currentYear;
|
||||
}
|
||||
renderCalendar(picker);
|
||||
}
|
||||
picker.dataset.month = currentMonth;
|
||||
picker.dataset.year = currentYear;
|
||||
renderCalendar(picker);
|
||||
return;
|
||||
}
|
||||
|
||||
// Next Month
|
||||
// Next Month / Year / Decade
|
||||
const nextBtn = event.target.closest('.datepicker-next');
|
||||
if (nextBtn) {
|
||||
const picker = nextBtn.closest('.custom-datepicker');
|
||||
let currentMonth = parseInt(picker.dataset.month);
|
||||
let currentYear = parseInt(picker.dataset.year);
|
||||
currentMonth++;
|
||||
if (currentMonth > 11) {
|
||||
currentMonth = 0;
|
||||
currentYear++;
|
||||
if (picker) {
|
||||
let currentMonth = parseInt(picker.dataset.month);
|
||||
let currentYear = parseInt(picker.dataset.year);
|
||||
const view = picker.dataset.view || 'days';
|
||||
|
||||
if (view === 'days') {
|
||||
currentMonth++;
|
||||
if (currentMonth > 11) {
|
||||
currentMonth = 0;
|
||||
currentYear++;
|
||||
}
|
||||
picker.dataset.month = currentMonth;
|
||||
picker.dataset.year = currentYear;
|
||||
} else if (view === 'months') {
|
||||
currentYear++;
|
||||
picker.dataset.year = currentYear;
|
||||
} else if (view === 'years') {
|
||||
currentYear += 12;
|
||||
picker.dataset.year = currentYear;
|
||||
}
|
||||
renderCalendar(picker);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Header click (switch view mode)
|
||||
const headerBtn = event.target.closest('.datepicker-month-year');
|
||||
if (headerBtn) {
|
||||
const picker = headerBtn.closest('.custom-datepicker');
|
||||
if (picker) {
|
||||
const view = picker.dataset.view || 'days';
|
||||
if (view === 'days') {
|
||||
picker.dataset.view = 'months';
|
||||
} else if (view === 'months') {
|
||||
picker.dataset.view = 'years';
|
||||
}
|
||||
renderCalendar(picker);
|
||||
}
|
||||
picker.dataset.month = currentMonth;
|
||||
picker.dataset.year = currentYear;
|
||||
renderCalendar(picker);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -260,6 +364,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Month click
|
||||
const monthBtn = event.target.closest('.datepicker-days button[data-month-val]');
|
||||
if (monthBtn) {
|
||||
const picker = monthBtn.closest('.custom-datepicker');
|
||||
if (picker) {
|
||||
const monthVal = parseInt(monthBtn.dataset.monthVal);
|
||||
picker.dataset.month = monthVal;
|
||||
picker.dataset.view = 'days';
|
||||
renderCalendar(picker);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Year click
|
||||
const yearBtn = event.target.closest('.datepicker-days button[data-year-val]');
|
||||
if (yearBtn) {
|
||||
const picker = yearBtn.closest('.custom-datepicker');
|
||||
if (picker) {
|
||||
const yearVal = parseInt(yearBtn.dataset.yearVal);
|
||||
picker.dataset.year = yearVal;
|
||||
picker.dataset.view = 'months';
|
||||
renderCalendar(picker);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Close outside
|
||||
const openPickerPopover = document.querySelector('.datepicker-popover:not(.hidden)');
|
||||
if (openPickerPopover && !event.target.closest('.custom-datepicker')) {
|
||||
|
||||
Reference in New Issue
Block a user