160 lines
5.5 KiB
CSS
160 lines
5.5 KiB
CSS
/* =============================================================================
|
||
* Calendar Year Picker Component Styles
|
||
*
|
||
* Internal component used inside Calendar / RangeCalendar to provide a
|
||
* toggleable year selector. Uses CSS :has() on the parent .calendar to
|
||
* drive crossfade transitions.
|
||
*
|
||
* The year grid is absolutely positioned and sized via JS to match the
|
||
* day grid's exact area. This keeps the calendar's original block layout
|
||
* completely untouched.
|
||
*
|
||
* BEM naming: calendar-year-picker__element, calendar-year-picker--modifier
|
||
* ============================================================================= */
|
||
|
||
/* -----------------------------------------------------------------------------
|
||
* Positioning Context
|
||
* -------------------------------------------------------------------------- */
|
||
.calendar:has(.calendar-year-picker__year-grid),
|
||
.range-calendar:has(.calendar-year-picker__year-grid) {
|
||
position: relative;
|
||
}
|
||
|
||
/* -----------------------------------------------------------------------------
|
||
* Crossfade + Blur Transitions
|
||
*
|
||
* The crossfade is staggered to avoid both grids being semi-transparent at
|
||
* the same time (ghosting). When opening, the day grid fades out first,
|
||
* then the year grid fades in. When closing, the year grid fades out first,
|
||
* then the day grid fades in (via transition-delay).
|
||
*
|
||
* -------------------------------------------------------------------------- */
|
||
|
||
/* Day grid – default visible */
|
||
.calendar:has(.calendar-year-picker__year-grid) > [data-slot="calendar-grid"],
|
||
.range-calendar:has(.calendar-year-picker__year-grid) > [data-slot="range-calendar-grid"] {
|
||
will-change: opacity;
|
||
transition:
|
||
opacity 150ms var(--ease-out),
|
||
visibility 0ms linear;
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
/* Day grid – hidden when year picker is open (fade + blur out) */
|
||
.calendar:has(.calendar-year-picker__year-grid[data-open="true"]) > [data-slot="calendar-grid"],
|
||
.range-calendar:has(.calendar-year-picker__year-grid[data-open="true"])
|
||
> [data-slot="range-calendar-grid"] {
|
||
@apply pointer-events-none opacity-0;
|
||
visibility: hidden;
|
||
transition:
|
||
opacity 150ms var(--ease-out),
|
||
visibility 0ms linear 150ms;
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
/* -----------------------------------------------------------------------------
|
||
* Trigger Button
|
||
* -------------------------------------------------------------------------- */
|
||
.calendar-year-picker__trigger {
|
||
@apply flex flex-1 items-center justify-start gap-1 rounded-lg outline-none;
|
||
|
||
cursor: var(--cursor-interactive);
|
||
touch-action: manipulation;
|
||
|
||
/* Focus state */
|
||
&:focus-visible {
|
||
@apply status-focused;
|
||
}
|
||
}
|
||
|
||
.calendar-year-picker__trigger-heading {
|
||
@apply text-sm font-medium;
|
||
|
||
transition: color 150ms var(--ease-out);
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
.calendar-year-picker__trigger-indicator {
|
||
@apply text-xs text-accent-soft-foreground;
|
||
|
||
transition: transform 150ms var(--ease-out);
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
/* Rotate chevron when open */
|
||
.calendar-year-picker__trigger[data-open="true"] .calendar-year-picker__trigger-indicator {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
.calendar-year-picker__trigger[data-open="true"] .calendar-year-picker__trigger-heading {
|
||
@apply text-accent-soft-foreground;
|
||
}
|
||
|
||
/* -----------------------------------------------------------------------------
|
||
* Year Grid Container
|
||
*
|
||
* Absolutely positioned over the day grid area. The exact `top` and `height`
|
||
* are set via JS to match the sibling day grid element.
|
||
* -------------------------------------------------------------------------- */
|
||
.calendar-year-picker__year-grid {
|
||
@apply pointer-events-none absolute right-0 left-0 grid content-start gap-1 overflow-y-auto p-1 opacity-0 scrollbar;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
|
||
/* When closing: instant (no transition) */
|
||
will-change: opacity;
|
||
}
|
||
|
||
.calendar-year-picker__year-grid[data-open="true"] {
|
||
@apply pointer-events-auto opacity-100;
|
||
|
||
/* When opening: slight delay so the day grid starts fading out first */
|
||
transition: opacity 200ms var(--ease-out) 50ms;
|
||
@apply motion-reduce:transition-none;
|
||
}
|
||
|
||
/* -----------------------------------------------------------------------------
|
||
* Year Cell
|
||
* -------------------------------------------------------------------------- */
|
||
.calendar-year-picker__year-cell {
|
||
@apply relative inline-flex h-8 items-center justify-center rounded-3xl px-2.5 text-sm font-medium outline-none select-none no-highlight;
|
||
touch-action: manipulation;
|
||
|
||
/**
|
||
* Transitions – same as .tag
|
||
* CRITICAL: motion-reduce must be AFTER transition for correct override specificity
|
||
*/
|
||
transition:
|
||
color 100ms var(--ease-smooth),
|
||
scale 100ms var(--ease-smooth),
|
||
opacity 100ms var(--ease-smooth),
|
||
background-color 100ms var(--ease-smooth),
|
||
box-shadow 100ms var(--ease-out);
|
||
@apply origin-center transform-gpu motion-reduce:transition-none;
|
||
|
||
cursor: var(--cursor-interactive);
|
||
|
||
/* Hover state (when not selected) – only on devices that support hover */
|
||
@media (hover: hover) and (pointer: fine) {
|
||
&:is(:hover, [data-hovered="true"]):not([data-selected="true"]) {
|
||
@apply bg-default text-default-foreground;
|
||
}
|
||
}
|
||
|
||
/* Selected state (current year) – same as .tag[data-selected] */
|
||
&[data-selected="true"],
|
||
&[aria-selected="true"] {
|
||
@apply bg-accent text-accent-foreground;
|
||
|
||
@media (hover: hover) and (pointer: fine) {
|
||
&:is(:hover, [data-hovered="true"]) {
|
||
@apply bg-accent-hover;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Focus state */
|
||
&:focus-visible {
|
||
@apply status-focused;
|
||
}
|
||
}
|