Refine calendar icon picker

This commit is contained in:
Rafael Foster
2026-04-27 21:53:18 -03:00
parent 1d1d2291e5
commit 33e4afc009
17 changed files with 181 additions and 29 deletions
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -805,7 +805,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -864,7 +864,7 @@
"offset1week": "1 semana antes", "offset1week": "1 semana antes",
"offset2weeks": "2 semanas antes", "offset2weeks": "2 semanas antes",
"offsetCustom": "Personalizado...", "offsetCustom": "Personalizado...",
"customAmountLabel": "Quantidade", "customAmountLabel": "Número",
"customUnitLabel": "Unidade", "customUnitLabel": "Unidade",
"customMinutes": "Minutos", "customMinutes": "Minutos",
"customHours": "Horas", "customHours": "Horas",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -805,7 +805,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+1 -1
View File
@@ -863,7 +863,7 @@
"offset1week": "1 week before", "offset1week": "1 week before",
"offset2weeks": "2 weeks before", "offset2weeks": "2 weeks before",
"offsetCustom": "Custom...", "offsetCustom": "Custom...",
"customAmountLabel": "Amount", "customAmountLabel": "Number",
"customUnitLabel": "Unit", "customUnitLabel": "Unit",
"customMinutes": "Minutes", "customMinutes": "Minutes",
"customHours": "Hours", "customHours": "Hours",
+70 -8
View File
@@ -991,6 +991,48 @@ function openEventModal({ mode, event = null, date = null, reminder = null }) {
bindDateInputs(panel); bindDateInputs(panel);
const iconInput = panel.querySelector('#modal-icon');
const iconTrigger = panel.querySelector('#modal-icon-trigger');
const iconGrid = panel.querySelector('#modal-icon-grid');
const selectIcon = (icon) => {
const nextIcon = eventIconName(icon);
if (iconInput) iconInput.value = nextIcon;
if (iconTrigger) {
iconTrigger.dataset.icon = nextIcon;
const iconEl = iconTrigger.querySelector('[data-lucide]');
iconEl?.setAttribute('data-lucide', nextIcon);
}
iconGrid?.querySelectorAll('.event-icon-picker__option').forEach((btn) => {
const active = btn.dataset.icon === nextIcon;
btn.classList.toggle('event-icon-picker__option--active', active);
btn.setAttribute('aria-checked', active ? 'true' : 'false');
});
if (window.lucide) lucide.createIcons();
};
iconTrigger?.addEventListener('click', () => {
if (!iconGrid) return;
iconGrid.hidden = !iconGrid.hidden;
iconTrigger.setAttribute('aria-expanded', iconGrid.hidden ? 'false' : 'true');
});
iconGrid?.addEventListener('click', (e) => {
const btn = e.target.closest('.event-icon-picker__option');
if (!btn) return;
selectIcon(btn.dataset.icon);
iconGrid.hidden = true;
iconTrigger?.setAttribute('aria-expanded', 'false');
iconTrigger?.focus();
});
document.addEventListener('click', function closeIconPicker(e) {
if (!panel.isConnected) {
document.removeEventListener('click', closeIconPicker);
return;
}
if (iconGrid?.hidden || iconGrid?.contains(e.target) || iconTrigger?.contains(e.target)) return;
iconGrid.hidden = true;
iconTrigger?.setAttribute('aria-expanded', 'false');
});
const reminderOffset = panel.querySelector('#modal-reminder-offset'); const reminderOffset = panel.querySelector('#modal-reminder-offset');
const reminderCustom = panel.querySelector('#modal-reminder-custom'); const reminderCustom = panel.querySelector('#modal-reminder-custom');
reminderOffset?.addEventListener('change', () => { reminderOffset?.addEventListener('change', () => {
@@ -1021,8 +1063,16 @@ function buildEventModalContent({ mode, event, date, reminder = null }) {
const endTime = isEdit && event.end_datetime && event.end_datetime.length > 10 const endTime = isEdit && event.end_datetime && event.end_datetime.length > 10
? localTime(event.end_datetime) : '10:00'; ? localTime(event.end_datetime) : '10:00';
const selectedIcon = eventIconName(isEdit ? event.icon : 'calendar'); const selectedIcon = eventIconName(isEdit ? event.icon : 'calendar');
const iconOpts = EVENT_ICONS.map((icon) => const iconButtons = EVENT_ICONS.map((icon) =>
`<option value="${icon.value}" ${selectedIcon === icon.value ? 'selected' : ''}>${esc(icon.label)}</option>` `<button type="button"
class="event-icon-picker__option ${selectedIcon === icon.value ? 'event-icon-picker__option--active' : ''}"
data-icon="${icon.value}"
role="radio"
aria-checked="${selectedIcon === icon.value ? 'true' : 'false'}"
aria-label="${esc(icon.label)}"
title="${esc(icon.label)}">
<i data-lucide="${icon.value}" aria-hidden="true"></i>
</button>`
).join(''); ).join('');
const userOpts = [ const userOpts = [
@@ -1033,16 +1083,28 @@ function buildEventModalContent({ mode, event, date, reminder = null }) {
].join(''); ].join('');
return ` return `
<div class="modal-grid modal-grid--event-title"> <div class="event-title-picker">
<div class="form-group"> <div class="form-group event-icon-picker">
<label class="form-label" for="modal-icon-trigger">${t('calendar.iconLabel')}</label>
<input type="hidden" id="modal-icon" value="${selectedIcon}">
<button type="button"
class="event-icon-picker__trigger"
id="modal-icon-trigger"
data-icon="${selectedIcon}"
aria-haspopup="true"
aria-expanded="false"
aria-label="${t('calendar.iconLabel')}">
<i data-lucide="${selectedIcon}" aria-hidden="true"></i>
</button>
</div>
<div class="form-group event-title-picker__title">
<label class="form-label" for="modal-title">${t('calendar.titleLabel')}</label> <label class="form-label" for="modal-title">${t('calendar.titleLabel')}</label>
<input type="text" class="form-input" id="modal-title" <input type="text" class="form-input" id="modal-title"
placeholder="${t('calendar.titlePlaceholder')}" value="${esc(isEdit ? event.title : '')}"> placeholder="${t('calendar.titlePlaceholder')}" value="${esc(isEdit ? event.title : '')}">
</div> </div>
<div class="form-group"> </div>
<label class="form-label" for="modal-icon">${t('calendar.iconLabel')}</label> <div class="event-icon-picker__grid" id="modal-icon-grid" role="radiogroup" aria-label="${t('calendar.iconLabel')}" hidden>
<select class="form-input" id="modal-icon">${iconOpts}</select> ${iconButtons}
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
+93 -3
View File
@@ -544,14 +544,104 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.modal-grid--event-title { .event-title-picker {
grid-template-columns: minmax(0, 1fr) 160px; display: grid;
grid-template-columns: 72px minmax(0, 1fr);
gap: var(--space-3);
align-items: end;
}
.event-icon-picker {
position: relative;
}
.event-title-picker__title {
min-width: 0;
}
.event-icon-picker__trigger {
width: 52px;
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text-primary);
cursor: pointer;
transition: border-color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast);
}
.event-icon-picker__trigger:hover,
.event-icon-picker__trigger:focus-visible {
border-color: var(--color-accent);
background: var(--color-accent-light);
}
.event-icon-picker__trigger:active {
transform: scale(0.98);
}
.event-icon-picker__trigger i {
width: 22px;
height: 22px;
}
.event-icon-picker__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
gap: var(--space-2);
margin: calc(var(--space-2) * -1) 0 var(--space-4) 0;
padding: var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface-2);
}
.event-icon-picker__grid[hidden] {
display: none !important;
}
.event-icon-picker__option {
height: 42px;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text-secondary);
cursor: pointer;
transition: border-color var(--transition-fast), color var(--transition-fast), background-color var(--transition-fast), transform var(--transition-fast);
}
.event-icon-picker__option:hover,
.event-icon-picker__option:focus-visible {
color: var(--color-text-primary);
border-color: var(--color-border);
transform: translateY(-1px);
}
.event-icon-picker__option--active {
color: var(--color-accent);
border-color: var(--color-accent);
background: var(--color-accent-light);
}
.event-icon-picker__option i {
width: 20px;
height: 20px;
} }
.reminder-custom { .reminder-custom {
margin-top: var(--space-3); margin-top: var(--space-3);
} }
.reminder-custom[hidden] {
display: none !important;
}
.agenda-event__meta { .agenda-event__meta {
font-size: var(--text-sm); font-size: var(--text-sm);
color: var(--color-text-secondary); color: var(--color-text-secondary);
@@ -706,7 +796,7 @@
} }
@media (max-width: 640px) { @media (max-width: 640px) {
.modal-grid--event-title { .event-title-picker {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }
+4 -4
View File
@@ -13,10 +13,10 @@
* → bypassCacheUntil (in-memory + Cache API für SW-Restart-Robustheit) * → bypassCacheUntil (in-memory + Cache API für SW-Restart-Robustheit)
*/ */
const SHELL_CACHE = 'oikos-shell-v57'; const SHELL_CACHE = 'oikos-shell-v58';
const PAGES_CACHE = 'oikos-pages-v52'; const PAGES_CACHE = 'oikos-pages-v53';
const LOCALES_CACHE = 'oikos-locales-v4'; const LOCALES_CACHE = 'oikos-locales-v5';
const ASSETS_CACHE = 'oikos-assets-v52'; const ASSETS_CACHE = 'oikos-assets-v53';
const BYPASS_CACHE = 'oikos-bypass-flag'; const BYPASS_CACHE = 'oikos-bypass-flag';
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, LOCALES_CACHE, ASSETS_CACHE]; const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, LOCALES_CACHE, ASSETS_CACHE];