Basic more event handling for month view when there are too many events in a day given its height

This commit is contained in:
Andrew Gioia 2026-02-09 15:02:27 -05:00
parent 25515eccb9
commit 5f4cffd5aa
Signed by: andrew
GPG Key ID: FC09694A000800C8
2 changed files with 197 additions and 0 deletions

View File

@ -26,6 +26,7 @@
/* day element */ /* day element */
li { li {
@apply bg-white relative px-1 pt-8 border-t-md border-gray-900 overflow-y-auto; @apply bg-white relative px-1 pt-8 border-t-md border-gray-900 overflow-y-auto;
transition: scale 150ms ease-in-out;
/* day number */ /* day number */
&::before { &::before {
@ -54,6 +55,60 @@
@apply rounded-br-lg; @apply rounded-br-lg;
}*/ }*/
/* progressive "show more" button */
div.more-events {
@apply absolute bottom-0 h-6 bg-inherit z-2 flex items-center;
width: calc(100% - 0.5rem);
}
button.day-more {
@apply text-xs px-1 h-4;
}
&[data-event-visible="0"] {
.event:nth-child(n+1) { @apply hidden; }
}
&[data-event-visible="1"] {
.event:nth-child(n+2) { @apply hidden; }
}
&[data-event-visible="2"] {
.event:nth-child(n+3) { @apply hidden; }
}
&[data-event-visible="3"] {
.event:nth-child(n+4) { @apply hidden; }
}
&[data-event-visible="4"] {
.event:nth-child(n+5) { @apply hidden; }
}
&[data-event-visible="5"] {
.event:nth-child(n+6) { @apply hidden; }
}
&[data-event-visible="6"] {
.event:nth-child(n+7) { @apply hidden; }
}
&[data-event-visible="7"] {
.event:nth-child(n+8) { @apply hidden; }
}
&[data-event-visible="8"] {
.event:nth-child(n+9) { @apply hidden; }
}
&[data-event-visible="9"] {
.event:nth-child(n+10) { @apply hidden; }
}
&.is-expanded {
position: relative;
height: min-content;
padding-bottom: 1px;
z-index: 3;
border: 1.5px solid black;
border-radius: 0.5rem;
scale: 1.05;
width: 120%;
margin-left: -10%;
div.more-events {
@apply relative h-8;
}
}
/* events */ /* events */
.event { .event {
@apply flex items-center text-xs gap-1 px-1 py-px font-medium truncate rounded-sm bg-transparent; @apply flex items-center text-xs gap-1 px-1 py-px font-medium truncate rounded-sm bg-transparent;

View File

@ -9,6 +9,10 @@ const SELECTORS = {
colorPickerColor: '[data-colorpicker-color]', colorPickerColor: '[data-colorpicker-color]',
colorPickerHex: '[data-colorpicker-hex]', colorPickerHex: '[data-colorpicker-hex]',
colorPickerRandom: '[data-colorpicker-random]', colorPickerRandom: '[data-colorpicker-random]',
monthDay: '.calendar.month .day',
monthDayEvent: 'a.event',
monthDayMore: '[data-day-more]',
monthDayMoreWrap: '.more-events',
}; };
/** /**
@ -193,8 +197,127 @@ function initColorPickers(root = document) {
root.querySelectorAll(SELECTORS.colorPicker).forEach(wire); root.querySelectorAll(SELECTORS.colorPicker).forEach(wire);
} }
/**
* month view overflow handling (progressive enhancement)
*/
function initMonthOverflow(root = document) {
const days = root.querySelectorAll(SELECTORS.monthDay);
days.forEach((day) => updateMonthOverflow(day));
}
function ensureDayMoreButton(dayEl) {
let wrapper = dayEl.querySelector(SELECTORS.monthDayMoreWrap);
if (!wrapper) {
wrapper = document.createElement('div');
wrapper.className = 'more-events';
dayEl.appendChild(wrapper);
}
let button = wrapper.querySelector(SELECTORS.monthDayMore);
if (!button) {
button = document.createElement('button');
button.type = 'button';
button.className = 'day-more hidden';
button.setAttribute('data-day-more', '');
wrapper.appendChild(button);
}
return button;
}
function formatMoreLabel(dayEl, count) {
const template = dayEl.getAttribute('data-more-label') || ':count more';
return template.replace(':count', count);
}
function lessLabel(dayEl) {
return dayEl.getAttribute('data-less-label') || 'Show less';
}
function updateMonthOverflow(dayEl) {
if (!dayEl) return;
const events = Array.from(dayEl.querySelectorAll(SELECTORS.monthDayEvent))
.filter((el) => !el.classList.contains('hidden'));
const moreButton = ensureDayMoreButton(dayEl);
if (!events.length) {
moreButton.textContent = '';
moreButton.classList.add('hidden');
moreButton.removeAttribute('aria-expanded');
dayEl.classList.remove('day--event-overflow');
dayEl.setAttribute('data-event-visible', '0');
return;
}
if (dayEl.classList.contains('is-expanded')) {
moreButton.textContent = lessLabel(dayEl);
moreButton.classList.remove('hidden');
moreButton.setAttribute('aria-expanded', 'true');
dayEl.classList.remove('day--event-overflow');
dayEl.setAttribute('data-event-visible', String(events.length));
return;
}
const wrapper = moreButton.closest(SELECTORS.monthDayMoreWrap);
let wrapperHeight = wrapper ? wrapper.getBoundingClientRect().height : 0;
if (wrapperHeight === 0 && wrapper) {
const wasHidden = moreButton.classList.contains('hidden');
const prevVisibility = moreButton.style.visibility;
if (wasHidden) {
moreButton.classList.remove('hidden');
moreButton.style.visibility = 'hidden';
}
wrapperHeight = wrapper.getBoundingClientRect().height || 0;
if (wasHidden) {
moreButton.classList.add('hidden');
moreButton.style.visibility = prevVisibility;
}
}
const prevVisibility = dayEl.style.visibility;
dayEl.style.visibility = 'hidden';
dayEl.removeAttribute('data-event-visible');
dayEl.classList.remove('day--event-overflow');
const availableHeight = dayEl.clientHeight - wrapperHeight;
let hiddenCount = 0;
events.forEach((eventEl) => {
const bottom = eventEl.offsetTop + eventEl.offsetHeight;
if (bottom > availableHeight + 0.5) {
hiddenCount += 1;
}
});
dayEl.style.visibility = prevVisibility;
const visibleCount = Math.max(0, events.length - hiddenCount);
dayEl.setAttribute('data-event-visible', String(visibleCount));
if (hiddenCount > 0) {
moreButton.textContent = formatMoreLabel(dayEl, hiddenCount);
moreButton.classList.remove('hidden');
moreButton.setAttribute('aria-expanded', 'false');
dayEl.classList.add('day--event-overflow');
} else {
moreButton.textContent = '';
moreButton.classList.add('hidden');
moreButton.removeAttribute('aria-expanded');
dayEl.classList.remove('day--event-overflow');
}
}
/**
* initialization
*/
function initUI() { function initUI() {
initColorPickers(); initColorPickers();
initMonthOverflow();
} }
// initial bind // initial bind
@ -203,4 +326,23 @@ document.addEventListener('DOMContentLoaded', initUI);
// rebind in htmx for swapped content // rebind in htmx for swapped content
document.addEventListener('htmx:afterSwap', (e) => { document.addEventListener('htmx:afterSwap', (e) => {
initColorPickers(e.target); initColorPickers(e.target);
initMonthOverflow(e.target);
});
document.addEventListener('click', (event) => {
const button = event.target.closest(SELECTORS.monthDayMore);
if (!button) return;
const dayEl = button.closest(SELECTORS.monthDay);
if (!dayEl) return;
dayEl.classList.toggle('is-expanded');
updateMonthOverflow(dayEl);
});
let monthResizeTimer;
window.addEventListener('resize', () => {
if (!document.querySelector(SELECTORS.monthDay)) return;
window.clearTimeout(monthResizeTimer);
monthResizeTimer = window.setTimeout(() => initMonthOverflow(), 100);
}); });