fix(calendar): remove overlay swap and keep in-place zoom flow
This commit is contained in:
@@ -79,10 +79,6 @@ defineProps<{
|
||||
weekDays: WeekDay[];
|
||||
calendarPrimeDayToken: (dayKey: string) => string;
|
||||
selectedDayEvents: CalendarEvent[];
|
||||
calendarZoomOverlay: { active: boolean };
|
||||
setCalendarZoomOverlayRef: (element: HTMLDivElement | null) => void;
|
||||
calendarZoomOverlayStyle: Record<string, string>;
|
||||
calendarZoomGhost: { title: string; subtitle?: string } | null;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@@ -199,134 +195,116 @@ defineProps<{
|
||||
{{ formatDay(item.first.start) }} · {{ item.first.title }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
v-if="item.monthIndex === calendarCursorMonth && (calendarView === 'month' || calendarView === 'agenda')"
|
||||
class="mt-3 space-y-1"
|
||||
>
|
||||
<div class="grid grid-cols-7 gap-1 text-center text-xs font-semibold text-base-content/60">
|
||||
<span>Sun</span>
|
||||
<span>Mon</span>
|
||||
<span>Tue</span>
|
||||
<span>Wed</span>
|
||||
<span>Thu</span>
|
||||
<span>Fri</span>
|
||||
<span>Sat</span>
|
||||
</div>
|
||||
<div v-if="item.monthIndex === calendarCursorMonth" class="mt-3">
|
||||
<div v-show="calendarView === 'month' || calendarView === 'agenda'" class="space-y-1">
|
||||
<div class="grid grid-cols-7 gap-1 text-center text-xs font-semibold text-base-content/60">
|
||||
<span>Sun</span>
|
||||
<span>Mon</span>
|
||||
<span>Tue</span>
|
||||
<span>Wed</span>
|
||||
<span>Thu</span>
|
||||
<span>Fri</span>
|
||||
<span>Sat</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="row in monthRows"
|
||||
:key="row.key"
|
||||
class="group relative calendar-hover-targetable"
|
||||
:class="[
|
||||
calendarHoveredWeekStartKey === row.startKey ? 'calendar-hover-target' : '',
|
||||
calendarZoomPrimeToken === calendarPrimeWeekToken(row.startKey) ? 'calendar-zoom-prime-active' : '',
|
||||
]"
|
||||
:style="calendarPrimeStyle(calendarPrimeWeekToken(row.startKey))"
|
||||
:data-calendar-week-start-key="row.startKey"
|
||||
@mouseenter="setCalendarHoveredWeekStartKey(row.startKey)"
|
||||
>
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<button
|
||||
v-for="cell in row.cells"
|
||||
:key="cell.key"
|
||||
class="group relative min-h-24 rounded-lg border p-1 text-left"
|
||||
:class="[
|
||||
cell.inMonth ? 'border-base-300 bg-base-100' : 'border-base-200 bg-base-200/40 text-base-content/40',
|
||||
selectedDateKey === cell.key ? 'border-primary bg-primary/5' : '',
|
||||
monthCellHasFocusedEvent(cell.events) ? 'border-success/60 bg-success/10' : '',
|
||||
]"
|
||||
:data-calendar-day-key="cell.key"
|
||||
@mouseenter="setCalendarHoveredDayKey(cell.key)"
|
||||
@click="pickDate(cell.key)"
|
||||
>
|
||||
<p class="mb-1 text-xs font-semibold">{{ cell.day }}</p>
|
||||
<div class="space-y-1">
|
||||
<div
|
||||
v-for="row in monthRows"
|
||||
:key="row.key"
|
||||
class="group relative calendar-hover-targetable"
|
||||
:class="[
|
||||
calendarHoveredWeekStartKey === row.startKey ? 'calendar-hover-target' : '',
|
||||
calendarZoomPrimeToken === calendarPrimeWeekToken(row.startKey) ? 'calendar-zoom-prime-active' : '',
|
||||
]"
|
||||
:style="calendarPrimeStyle(calendarPrimeWeekToken(row.startKey))"
|
||||
:data-calendar-week-start-key="row.startKey"
|
||||
@mouseenter="setCalendarHoveredWeekStartKey(row.startKey)"
|
||||
>
|
||||
<div class="grid grid-cols-7 gap-1">
|
||||
<button
|
||||
v-for="event in monthCellEvents(cell.events)"
|
||||
:key="event.id"
|
||||
class="block w-full truncate rounded px-1 text-left text-[10px] text-base-content/70 transition hover:underline"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 text-success-content ring-1 ring-success/40' : ''"
|
||||
@click.stop="openThreadFromCalendarItem(event)"
|
||||
v-for="cell in row.cells"
|
||||
:key="cell.key"
|
||||
class="group relative min-h-24 rounded-lg border p-1 text-left"
|
||||
:class="[
|
||||
cell.inMonth ? 'border-base-300 bg-base-100' : 'border-base-200 bg-base-200/40 text-base-content/40',
|
||||
selectedDateKey === cell.key ? 'border-primary bg-primary/5' : '',
|
||||
monthCellHasFocusedEvent(cell.events) ? 'border-success/60 bg-success/10' : '',
|
||||
]"
|
||||
:data-calendar-day-key="cell.key"
|
||||
@mouseenter="setCalendarHoveredDayKey(cell.key)"
|
||||
@click="pickDate(cell.key)"
|
||||
>
|
||||
{{ formatTime(event.start) }} {{ event.title }}
|
||||
<p class="mb-1 text-xs font-semibold">{{ cell.day }}</p>
|
||||
<button
|
||||
v-for="event in monthCellEvents(cell.events)"
|
||||
:key="event.id"
|
||||
class="block w-full truncate rounded px-1 text-left text-[10px] text-base-content/70 transition hover:underline"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 text-success-content ring-1 ring-success/40' : ''"
|
||||
@click.stop="openThreadFromCalendarItem(event)"
|
||||
>
|
||||
{{ formatTime(event.start) }} {{ event.title }}
|
||||
</button>
|
||||
</button>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="item.monthIndex === calendarCursorMonth && calendarView === 'week'"
|
||||
class="mt-3 calendar-week-scroll overflow-x-auto pb-1"
|
||||
>
|
||||
<div class="calendar-week-grid">
|
||||
<article
|
||||
v-for="day in weekDays"
|
||||
:key="day.key"
|
||||
class="group relative flex min-h-[18rem] flex-col rounded-xl border border-base-300 bg-base-100 p-2.5 cursor-zoom-in calendar-hover-targetable"
|
||||
:class="[
|
||||
selectedDateKey === day.key ? 'border-primary bg-primary/5' : '',
|
||||
calendarHoveredDayKey === day.key ? 'calendar-hover-target' : '',
|
||||
calendarZoomPrimeToken === calendarPrimeDayToken(day.key) ? 'calendar-zoom-prime-active' : '',
|
||||
]"
|
||||
:style="calendarPrimeStyle(calendarPrimeDayToken(day.key))"
|
||||
:data-calendar-day-key="day.key"
|
||||
@mouseenter="setCalendarHoveredDayKey(day.key)"
|
||||
@click="pickDate(day.key)"
|
||||
>
|
||||
<div class="mb-2 flex items-start justify-between gap-2">
|
||||
<p class="text-sm font-semibold leading-tight">{{ day.label }} {{ day.day }}</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<button
|
||||
v-for="event in day.events"
|
||||
:key="event.id"
|
||||
class="block w-full rounded-md px-2 py-1.5 text-left text-xs"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 ring-1 ring-success/45' : 'bg-base-200 hover:bg-base-300/80'"
|
||||
@click.stop="openThreadFromCalendarItem(event)"
|
||||
>
|
||||
{{ formatTime(event.start) }} - {{ event.title }} ({{ event.contact }})
|
||||
</button>
|
||||
<p v-if="day.events.length === 0" class="pt-1 text-xs text-base-content/50">No events</p>
|
||||
</div>
|
||||
</article>
|
||||
<div v-show="calendarView === 'week'" class="calendar-week-scroll overflow-x-auto pb-1">
|
||||
<div class="calendar-week-grid">
|
||||
<article
|
||||
v-for="day in weekDays"
|
||||
:key="day.key"
|
||||
class="group relative flex min-h-[18rem] flex-col rounded-xl border border-base-300 bg-base-100 p-2.5 cursor-zoom-in calendar-hover-targetable"
|
||||
:class="[
|
||||
selectedDateKey === day.key ? 'border-primary bg-primary/5' : '',
|
||||
calendarHoveredDayKey === day.key ? 'calendar-hover-target' : '',
|
||||
calendarZoomPrimeToken === calendarPrimeDayToken(day.key) ? 'calendar-zoom-prime-active' : '',
|
||||
]"
|
||||
:style="calendarPrimeStyle(calendarPrimeDayToken(day.key))"
|
||||
:data-calendar-day-key="day.key"
|
||||
@mouseenter="setCalendarHoveredDayKey(day.key)"
|
||||
@click="pickDate(day.key)"
|
||||
>
|
||||
<div class="mb-2 flex items-start justify-between gap-2">
|
||||
<p class="text-sm font-semibold leading-tight">{{ day.label }} {{ day.day }}</p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<button
|
||||
v-for="event in day.events"
|
||||
:key="event.id"
|
||||
class="block w-full rounded-md px-2 py-1.5 text-left text-xs"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'bg-success/20 ring-1 ring-success/45' : 'bg-base-200 hover:bg-base-300/80'"
|
||||
@click.stop="openThreadFromCalendarItem(event)"
|
||||
>
|
||||
{{ formatTime(event.start) }} - {{ event.title }} ({{ event.contact }})
|
||||
</button>
|
||||
<p v-if="day.events.length === 0" class="pt-1 text-xs text-base-content/50">No events</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="item.monthIndex === calendarCursorMonth && calendarView === 'day'"
|
||||
class="mt-3 space-y-2"
|
||||
>
|
||||
<button
|
||||
v-for="event in selectedDayEvents"
|
||||
:key="event.id"
|
||||
class="block w-full rounded-xl border border-base-300 p-3 text-left transition hover:bg-base-200/60"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'border-success/60 bg-success/10' : ''"
|
||||
@click="openThreadFromCalendarItem(event)"
|
||||
>
|
||||
<p class="font-medium">{{ event.title }}</p>
|
||||
<p class="text-xs text-base-content/60">{{ event.contact }}</p>
|
||||
<p class="text-xs text-base-content/60">{{ formatTime(event.start) }} - {{ formatTime(event.end) }}</p>
|
||||
<p class="mt-1 text-sm text-base-content/80">{{ event.note }}</p>
|
||||
</button>
|
||||
<p v-if="selectedDayEvents.length === 0" class="text-sm text-base-content/60">No events on this day.</p>
|
||||
<div v-show="calendarView === 'day'" class="space-y-2">
|
||||
<button
|
||||
v-for="event in selectedDayEvents"
|
||||
:key="event.id"
|
||||
class="block w-full rounded-xl border border-base-300 p-3 text-left transition hover:bg-base-200/60"
|
||||
:class="isReviewHighlightedEvent(event.id) ? 'border-success/60 bg-success/10' : ''"
|
||||
@click="openThreadFromCalendarItem(event)"
|
||||
>
|
||||
<p class="font-medium">{{ event.title }}</p>
|
||||
<p class="text-xs text-base-content/60">{{ event.contact }}</p>
|
||||
<p class="text-xs text-base-content/60">{{ formatTime(event.start) }} - {{ formatTime(event.end) }}</p>
|
||||
<p class="mt-1 text-sm text-base-content/80">{{ event.note }}</p>
|
||||
</button>
|
||||
<p v-if="selectedDayEvents.length === 0" class="text-sm text-base-content/60">No events on this day.</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="calendarZoomOverlay.active"
|
||||
:ref="setCalendarZoomOverlayRef"
|
||||
class="calendar-zoom-overlay"
|
||||
:style="calendarZoomOverlayStyle"
|
||||
>
|
||||
<div v-if="calendarZoomGhost" class="calendar-zoom-overlay-content">
|
||||
<p class="calendar-zoom-overlay-title">{{ calendarZoomGhost.title }}</p>
|
||||
<p v-if="calendarZoomGhost.subtitle" class="calendar-zoom-overlay-subtitle">{{ calendarZoomGhost.subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -492,44 +470,6 @@ defineProps<{
|
||||
background: color-mix(in oklab, var(--color-base-content) 85%, transparent);
|
||||
}
|
||||
|
||||
.calendar-zoom-overlay {
|
||||
position: absolute;
|
||||
z-index: 18;
|
||||
border: 1px solid color-mix(in oklab, var(--color-primary) 62%, transparent);
|
||||
border-radius: 14px;
|
||||
background:
|
||||
radial-gradient(circle at 20% 20%, color-mix(in oklab, var(--color-primary) 30%, transparent), transparent 55%),
|
||||
color-mix(in oklab, var(--color-base-100) 84%, transparent);
|
||||
box-shadow:
|
||||
0 0 0 1px color-mix(in oklab, var(--color-primary) 42%, transparent) inset,
|
||||
0 18px 38px rgba(18, 30, 58, 0.28);
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calendar-zoom-overlay-content {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 4px;
|
||||
padding: 14px;
|
||||
color: color-mix(in oklab, var(--color-base-content) 90%, transparent);
|
||||
}
|
||||
|
||||
.calendar-zoom-overlay-title {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calendar-zoom-overlay-subtitle {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.calendar-content-wrap {
|
||||
padding-left: 32px;
|
||||
|
||||
Reference in New Issue
Block a user