frontend: switch calendar zoom to slider and add intuitive expand icons

This commit is contained in:
Ruslan Bakiev
2026-02-21 15:26:15 +07:00
parent 5bff62b62f
commit d19be19b87

View File

@@ -1909,13 +1909,6 @@ const calendarViewOptions: { value: CalendarView; label: string }[] = [
{ value: "agenda", label: "Agenda" },
];
const calendarZoomOptions: Array<{ value: number; label: string }> = [
{ value: 1, label: "400%" },
{ value: 2, label: "250%" },
{ value: 3, label: "100%" },
{ value: 4, label: "50%" },
];
const calendarZoomLevel = computed<number>({
get() {
if (calendarView.value === "day") return 1;
@@ -1941,6 +1934,13 @@ const calendarZoomLevel = computed<number>({
},
});
const calendarZoomLabel = computed(() => {
if (calendarView.value === "day") return "Day";
if (calendarView.value === "week") return "Week";
if (calendarView.value === "month" || calendarView.value === "agenda") return "Month";
return "Year";
});
const monthCells = computed(() => {
const year = calendarCursor.value.getFullYear();
const month = calendarCursor.value.getMonth();
@@ -3649,15 +3649,20 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<div class="justify-self-end flex items-center gap-2">
<span class="text-[10px] uppercase tracking-wide text-base-content/60">Zoom</span>
<select v-model.number="calendarZoomLevel" class="select select-bordered select-xs w-24">
<option
v-for="option in calendarZoomOptions"
:key="`calendar-zoom-${option.value}`"
:value="option.value"
<div class="flex items-center gap-2 rounded-lg border border-base-300 bg-base-100 px-2 py-1">
<input
v-model.number="calendarZoomLevel"
type="range"
min="1"
max="4"
step="1"
class="range range-xs w-24 calendar-zoom-range"
aria-label="Calendar zoom level"
>
{{ option.label }}
</option>
</select>
<span class="min-w-[3.2rem] text-right text-[10px] font-semibold uppercase tracking-wide text-base-content/70">
{{ calendarZoomLabel }}
</span>
</div>
</div>
</div>
@@ -3711,10 +3716,14 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<button
type="button"
class="calendar-hover-jump calendar-hover-jump-row"
title="Open week view"
title="Zoom to week"
aria-label="Zoom to week"
@click.stop="openWeekView(row.startKey)"
>
Week
<svg viewBox="0 0 20 20" class="h-3.5 w-3.5 fill-none stroke-current" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="8.5" cy="8.5" r="4.5" />
<path d="M12 12l4.2 4.2" />
</svg>
</button>
<div class="grid grid-cols-7 gap-1">
<button
@@ -3729,13 +3738,17 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
@click="pickDate(cell.key)"
>
<p class="mb-1 text-xs font-semibold">{{ cell.day }}</p>
<span
<button
type="button"
class="calendar-hover-jump"
title="Open day view"
title="Expand to day"
aria-label="Expand to day"
@click.stop="openDayView(cell.key)"
>
Day
</span>
<svg viewBox="0 0 20 20" class="h-3.5 w-3.5 fill-none stroke-current" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M7 13L3 17M13 7l4-4M3 13V17h4M17 7V3h-4" />
</svg>
</button>
<button
v-for="event in monthCellEvents(cell.events)"
:key="event.id"
@@ -3763,10 +3776,13 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
<button
type="button"
class="calendar-hover-jump calendar-hover-jump-week"
title="Open day view"
title="Expand day line"
aria-label="Expand day line"
@click.stop="openDayView(day.key)"
>
Day
<svg viewBox="0 0 20 20" class="h-3.5 w-3.5 fill-none stroke-current" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M10 3v14M6 7l4-4 4 4M6 13l4 4 4-4" />
</svg>
</button>
<div class="space-y-1">
<button
@@ -4924,16 +4940,14 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 36px;
height: 20px;
padding: 0 8px;
border-radius: 999px;
min-width: 26px;
width: 26px;
height: 26px;
padding: 0;
border-radius: 8px;
border: 1px solid color-mix(in oklab, var(--color-primary) 35%, transparent);
background: color-mix(in oklab, var(--color-base-100) 86%, transparent);
color: color-mix(in oklab, var(--color-base-content) 78%, transparent);
font-size: 10px;
font-weight: 600;
letter-spacing: 0.02em;
opacity: 0;
pointer-events: none;
transition: opacity 120ms ease, transform 120ms ease, background-color 120ms ease;
@@ -4948,6 +4962,14 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
transform: translateY(0);
}
.calendar-hover-jump:focus-visible {
opacity: 1;
pointer-events: auto;
transform: translateY(0);
outline: 2px solid color-mix(in oklab, var(--color-primary) 58%, transparent);
outline-offset: 1px;
}
.calendar-hover-jump:hover {
background: color-mix(in oklab, var(--color-primary) 12%, var(--color-base-100));
}
@@ -4956,7 +4978,11 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
.calendar-hover-jump-row {
top: 8px;
right: 10px;
min-width: 46px;
}
.calendar-zoom-range {
--range-shdw: color-mix(in oklab, var(--color-primary) 72%, white 8%);
--range-bg: color-mix(in oklab, var(--color-base-300) 85%, white 10%);
}
@media (max-width: 960px) {