/* ============================================================================= * 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; } }