Calendar
Selector de dată, în două trepte: .calendar — ancorat de un câmp, într-un popover — și .calendar.lg — de sine stătător, cu controale obișnuite în antet. Selecția de interval e trei stări pe .calendar-day, pe aceeași grilă.
Markup canonic · components/calendar.json
Mărimi · .calendar și .calendar.lg
.calendar · 34px
.calendar.lg · 44px
Treptele diferă prin antet. Implicit: .calendar-select — select nativ fără bordură și fără fundal — plus navigare .btn.ghost.sm.icon-only; luni abreviate. Pe lg: .select pentru lună și an, nume întregi, navigare .btn.outline.icon-only. Treapta mare nu adaugă nicio clasă nouă — e compoziție.
| Treaptă | Lună / an | Înainte / înapoi | Celulă | Când |
|---|---|---|---|---|
.calendarimplicit | .calendar-select — fără bordură, 30px, luni abreviate (Aug) | .btn.ghost.sm.icon-only 30px | 34×34 · 13px | Ancorat de un declanșator: câmp de dată, filtru, buton de perioadă. Obligatoriu înăuntrul unui .popover. |
.calendar.lg | .select 40px, nume întregi (August) | .btn.outline.icon-only 40px | 44×44 · 15px | Calendarul e conținutul: ecran de rezervare, panou de filtre, agendă. 44px e și ținta de atins pe touch. |
În popover, lângă un câmp
.popover .calendar pune --cal-pad: 0 și scoate rama și fundalul: chrome-ul e al popoverului, padding-ul vine din .popover.content. Declanșatorul e .select-anchor.block + .select-trigger (components/select.html) — până la v8.0 era un .input readonly într-un div cu position:relative scris inline. Înăuntru, luna și anul rămân <select> native: un popover în popover n-are poveste de închidere.
Scurtături · .calendar-presets
.calendar · scurtături
.calendar.lg · scurtături
Model shadcn: rând sub grilă, despărțit de o linie, butoane outline pe cea mai mică treaptă, flex: 1. La ei Button variant="outline" size="sm" în CardFooter cu border-t; aici cea mai mică treaptă din DS e .btn.xs (26px).
Reguli: date relative unice pe un calendar de o zi; scurtături de interval („Ultimele 7 zile") doar unde se aplică și stările de interval. Presetul mută și luna afișată. Rândul e opțional și contextual, 3–5 bucăți; niciodată .btn.primary.
Interval · .is-range-start · .in-range · .is-range-end
.calendar · 10–21 aug
O zi, sau două clickuri pentru interval.
.calendar.lg · peste hotarul lunii
Trei stări pe .calendar-day, aceeași grilă, nicio clasă pe rădăcină — deci identic pe ambele trepte. Capetele: --c-accent plin, text alb. Interiorul: bandă --c-accent-tint.
Banda stă pe ::before și se întinde cu jumătate de gap în ambele direcții — două jumătăți acoperă gapul grilei, fără aritmetică pe :nth-child. Pictare: fundal buton → ::before (bandă) → ::after (discul capătului) → cifră. isolation: isolate pe celulă e obligatoriu: altfel z-index: -1 iese sub fundalul alb al calendarului și banda dispare.
| Situație | Clasă | Tratament |
|---|---|---|
| capătul de început / sfârșit | .is-range-start · .is-range-end | --c-accent plin pe ::after, text alb, 600 · banda pleacă spre interior de la 50% |
| zi din interior | .in-range | bandă --c-accent-tint, text --c-ink (16.41:1) |
| zi din altă lună, în interval | .is-muted.in-range | text --c-accent-deep — 4.58:1 pe tint; mist ar da 2.08, fog 3.88 |
| azi, în interval | .is-today.in-range | doar greutatea 600 — inelul e un inset shadow, s-ar picta sub bandă |
| hover în interior | — | inel --c-accent 1px: banda ocupă fundalul, iar un click acolo începe un interval nou |
| interval de o zi | .is-selected | când capetele coincid nu e interval — e o zi selectată |
Contractul de interacțiune (starea o ține ecranul, DS-ul dă clasele): primul click pune începutul, al doilea îl închide; capetele inversate se normalizează; un click în interiorul unui interval închis începe unul nou; pe hover se aplică aceleași clase, deci previzualizarea și rezultatul sunt identice; un preset de interval scrie ambele capete și mută luna. O zi :disabled nu poate fi în interval — ecranul refuză al doilea capăt.
.calendar-foot — opțional, recomandat cu interval: .t13 la stânga, .btn.link de golire la dreapta.
Stari
| Stare | Clasă | Tratament |
|---|---|---|
| zi normală | — | ink pe transparent, hover bej-1 |
| altă lună | .is-muted | --c-mist |
| azi | .is-today | inel circular --c-input-border + weight 600 |
| selectată | .is-selected | disc --c-accent plin, text alb |
| indisponibilă | :disabled | --c-mist, cursor not-allowed — și niciodată .in-range |
Stările sunt aceleași pe ambele trepte — se schimbă doar mărimea celulei. Stările de interval sunt în blocul Interval. Select-ul fără bordură are hover bej-1 și focus pe outline fog (n-are bordură care să poarte halo-ul, ca .input).
Metrici
.calendar padding 14px · alb · 1px --c-rule-card · --r-lg · 280px lat
.calendar-grid 7 coloane de 34px · gap 2px
.calendar-day 34x34 · cerc (border-radius 50%) · 13px tabular
.calendar-dow 11px/500 · 0.06em · uppercase · --c-fog · o literă
.calendar-picker grupul lună+an, flex:1, gap 12px
.calendar-select 30px · fără bordură · 14px/600 · săgeata de la .select
luna abreviată (Ian, Feb, …)
navigarea lunii .btn.ghost.sm.icon-only 30px
.calendar.lg padding 20px · --r-xl · 374px lat
.calendar.lg grid 7 coloane de 44px · gap 4px
.calendar.lg day 44x44 · 15px tabular (44px = ținta de atins pe touch)
.calendar.lg dow 12px · două litere (LU MA MI JO VI SÂ DU)
luna nume întreg
lună / an .select 40px — flex 1 1 auto + width auto
navigarea lunii .btn.outline.icon-only 40px
.calendar-presets rând opțional sub grilă · linie --c-rule-soft ·
.btn.outline.xs (26px) · flex-wrap, flex:1 pe buton
gap 6px / 12px sus — pe lg 8px / 16px
.calendar-foot rând opțional · .t13 (max 62%) + .btn.link · aceeași linie
interval .is-range-start / .in-range / .is-range-end pe .calendar-day
banda ::before, întinsă cu var(--cal-gap)/2 în ambele direcții
discul capătului ::after, --c-accent, cerc — pictat peste bandă
celula position:relative + isolation:isolate (obligatoriu)
treapta = 3 valori --cal-cell · --cal-gap · --cal-pad
lățimea calc(7 × cell + 6 × gap + 2 × pad + 2px) — o dă grila
.popover .calendar --cal-pad: 0 · fără ramă și fără fundal — chrome-ul e al popoverului