feat(calendar): move labels outside card borders for visual continuity
- Month names moved above month cards in year view (outside article border) Hidden in non-year views; toolbar shows context label instead - Day labels (Mon 24, Tue 25) moved above day columns in week view - Each card/column wrapped in flex container: title above, card below - New .calendar-card-title CSS: 11px, 55% opacity, subtle label above card - No duplicate headers: toolbar is the single source of current context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -183,37 +183,46 @@ defineProps<{
|
|||||||
class="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3 auto-rows-fr"
|
class="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3 auto-rows-fr"
|
||||||
:style="calendarViewportHeight > 0 ? { minHeight: `${calendarView === 'year' ? Math.max(420, calendarViewportHeight) : calendarViewportHeight}px` } : undefined"
|
:style="calendarViewportHeight > 0 ? { minHeight: `${calendarView === 'year' ? Math.max(420, calendarViewportHeight) : calendarViewportHeight}px` } : undefined"
|
||||||
>
|
>
|
||||||
<article
|
<div
|
||||||
v-for="item in yearMonths"
|
v-for="item in yearMonths"
|
||||||
:key="`year-month-${item.monthIndex}`"
|
:key="`year-month-${item.monthIndex}`"
|
||||||
v-show="calendarView === 'year' || item.monthIndex === calendarCursorMonth"
|
v-show="calendarView === 'year' || item.monthIndex === calendarCursorMonth"
|
||||||
class="group relative rounded-xl border border-base-300 p-3 text-left transition calendar-hover-targetable"
|
|
||||||
:class="[
|
:class="[
|
||||||
calendarView === 'year'
|
calendarView === 'year' ? 'flex flex-col h-full' : 'sm:col-span-2 xl:col-span-3 flex flex-col',
|
||||||
? 'h-full hover:border-primary/50 hover:bg-primary/5 cursor-zoom-in'
|
|
||||||
: 'cursor-default bg-base-100 sm:col-span-2 xl:col-span-3 flex flex-col',
|
|
||||||
calendarHoveredMonthIndex === item.monthIndex ? 'calendar-hover-target' : '',
|
|
||||||
calendarZoomPrimeToken === calendarPrimeMonthToken(item.monthIndex) ? 'calendar-zoom-prime-active' : '',
|
|
||||||
]"
|
]"
|
||||||
:style="{
|
|
||||||
...calendarPrimeStyle(calendarPrimeMonthToken(item.monthIndex)),
|
|
||||||
...(calendarView !== 'year' && item.monthIndex === calendarCursorMonth && calendarViewportHeight > 0
|
|
||||||
? { minHeight: `${calendarViewportHeight}px` }
|
|
||||||
: {}),
|
|
||||||
}"
|
|
||||||
:data-calendar-month-index="item.monthIndex"
|
|
||||||
@mouseenter="setCalendarHoveredMonthIndex(item.monthIndex)"
|
|
||||||
@click="calendarView === 'year' ? zoomToMonth(item.monthIndex) : undefined"
|
|
||||||
>
|
>
|
||||||
<p class="font-medium">{{ item.label }}</p>
|
<p
|
||||||
<p class="text-xs text-base-content/60">{{ item.count }} events</p>
|
v-if="calendarView === 'year'"
|
||||||
<button
|
class="calendar-card-title"
|
||||||
v-if="calendarView === 'year' && item.first"
|
>{{ item.label }}</p>
|
||||||
class="mt-1 block w-full text-left text-xs text-base-content/70 hover:underline"
|
|
||||||
@click.stop="openThreadFromCalendarItem(item.first)"
|
<article
|
||||||
|
class="group relative rounded-xl border border-base-300 p-3 text-left transition calendar-hover-targetable flex-1"
|
||||||
|
:class="[
|
||||||
|
calendarView === 'year'
|
||||||
|
? 'hover:border-primary/50 hover:bg-primary/5 cursor-zoom-in'
|
||||||
|
: 'cursor-default bg-base-100 flex flex-col',
|
||||||
|
calendarHoveredMonthIndex === item.monthIndex ? 'calendar-hover-target' : '',
|
||||||
|
calendarZoomPrimeToken === calendarPrimeMonthToken(item.monthIndex) ? 'calendar-zoom-prime-active' : '',
|
||||||
|
]"
|
||||||
|
:style="{
|
||||||
|
...calendarPrimeStyle(calendarPrimeMonthToken(item.monthIndex)),
|
||||||
|
...(calendarView !== 'year' && item.monthIndex === calendarCursorMonth && calendarViewportHeight > 0
|
||||||
|
? { minHeight: `${calendarViewportHeight}px` }
|
||||||
|
: {}),
|
||||||
|
}"
|
||||||
|
:data-calendar-month-index="item.monthIndex"
|
||||||
|
@mouseenter="setCalendarHoveredMonthIndex(item.monthIndex)"
|
||||||
|
@click="calendarView === 'year' ? zoomToMonth(item.monthIndex) : undefined"
|
||||||
>
|
>
|
||||||
{{ formatDay(item.first.start) }} · {{ item.first.title }}
|
<p v-if="calendarView === 'year'" class="text-xs text-base-content/60">{{ item.count }} events</p>
|
||||||
</button>
|
<button
|
||||||
|
v-if="calendarView === 'year' && item.first"
|
||||||
|
class="mt-1 block w-full text-left text-xs text-base-content/70 hover:underline"
|
||||||
|
@click.stop="openThreadFromCalendarItem(item.first)"
|
||||||
|
>
|
||||||
|
{{ formatDay(item.first.start) }} · {{ item.first.title }}
|
||||||
|
</button>
|
||||||
|
|
||||||
<div v-if="item.monthIndex === calendarCursorMonth" class="mt-3 calendar-depth-stack">
|
<div v-if="item.monthIndex === calendarCursorMonth" class="mt-3 calendar-depth-stack">
|
||||||
<div
|
<div
|
||||||
@@ -284,23 +293,24 @@ defineProps<{
|
|||||||
:class="calendarView === 'week' ? 'calendar-depth-layer-active' : 'calendar-depth-layer-hidden'"
|
:class="calendarView === 'week' ? 'calendar-depth-layer-active' : 'calendar-depth-layer-hidden'"
|
||||||
>
|
>
|
||||||
<div class="calendar-week-grid">
|
<div class="calendar-week-grid">
|
||||||
<article
|
<div
|
||||||
v-for="day in weekDays"
|
v-for="day in weekDays"
|
||||||
:key="day.key"
|
:key="day.key"
|
||||||
class="group relative flex h-full min-h-full flex-col rounded-xl border border-base-300 bg-base-100 p-2.5 cursor-zoom-in calendar-hover-targetable"
|
class="flex flex-col min-h-full"
|
||||||
: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="calendar-card-title text-center">{{ day.label }} {{ day.day }}</p>
|
||||||
<p class="text-sm font-semibold leading-tight">{{ day.label }} {{ day.day }}</p>
|
<article
|
||||||
</div>
|
class="group relative flex flex-1 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="space-y-1.5">
|
<div class="space-y-1.5">
|
||||||
<button
|
<button
|
||||||
v-for="event in day.events"
|
v-for="event in day.events"
|
||||||
@@ -314,6 +324,7 @@ defineProps<{
|
|||||||
<p v-if="day.events.length === 0" class="pt-1 text-xs text-base-content/50">No events</p>
|
<p v-if="day.events.length === 0" class="pt-1 text-xs text-base-content/50">No events</p>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -338,6 +349,7 @@ defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -445,6 +457,14 @@ defineProps<{
|
|||||||
right: 4px;
|
right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-card-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: color-mix(in oklab, var(--color-base-content) 55%, transparent);
|
||||||
|
padding: 0 4px 2px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-week-number {
|
.calendar-week-number {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Reference in New Issue
Block a user