Select
Alegere dintr-o listă închisă. Forma implicită e componenta React cu meniu popover — react/select.tsx, Radix îmbrăcat în clasele DS: .select-trigger poartă valoarea, .popover ține lista, iar rândul ales primește bifa de brand .popover-check. Aceeași formă se scrie de mână în HTML cu .select-anchor + .select-trigger + .popover. <select class="select"> nativ rămâne o variantă secundară — validă și nedepreciată, dar pe locul doi. Aceleași dimensiuni, stări și contexte ca .input pe toate.
React · react/select.tsx — forma implicită
Înlocuiește components/ui/select.tsx din shadcn fără să atingi call-site-urile: același API, alt strat de stil.
<Select defaultValue="q" onValueChange={setPeriod}>
<SelectTrigger className="block"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="m">Luna curentă</SelectItem>
<SelectItem value="q">Trimestrul curent</SelectItem>
</SelectContent>
</Select>
Import din @/components/ui/select. .block ține lățimea câmpului — nu există .select-anchor, fiindcă Radix portalează lista. Mai există SelectGroup, SelectLabel și SelectSeparator, toate cu semnătura din shadcn.
bono-ds.css și nu re-declară stil în TypeScript. Un rând scris în utilitare ar fi a doua definiție a aceluiași lucru, iar prima schimbare de token ar ajunge la HTML și nu la React — exact deriva pentru care există scripts/check-stamp-drift.py. scripts/build-catalog.py pică pe utilitare în react/. Detalii în react/README.md.Dependență: npm i @radix-ui/react-select.
Ce vine din Radix: rolul de listbox, navigarea cu săgeți, type-ahead-ul, Esc, focus trap-ul, aria-expanded pe trigger (de care atârnă rotirea chevronului din CSS) și poziționarea. Ce vine din DS: suprafața, rândurile, starea de ales. .popover.popper e modificatorul care predă coordonatele — Radix își pune poziția pe wrapper-ul lui, iar ancorarea proprie a DS-ului ar deplasa meniul dacă ar rămâne activă.
Markup canonic · components/select.json
Varianta secundară · <select class="select">
Nativul nu e depreciat — e al doilea. Rămâne răspunsul corect când lista e text simplu și unul dintre astea contează: nu există React pe suprafață (pagini statice, e-mail, referințele -aligned), vrei roata nativă de pe mobil, sau ai nevoie de zero JS. Antetul calendarului îl folosește exact din motivul ăsta.
.select stilizează doar cutia închisă. Dacă lista trebuie să arate a Bono — sau să poarte grupuri, rânduri secundare ori starea de ales cu bifă — e forma cu popover.A · Select alb pe card bej-1 (context Dublin)
Stările, pe forma implicită — .select-anchor + .select-trigger, adică exact ce randează react/select.tsx. <select> nativ are aceleași stări și aceleași metrici; e varianta secundară, demonstrată separat în blocul ei.
Focus state
aria-expanded="true", care arată la fel ca focus: border --c-fog + halo neutru. Un al doilea tratament n-ar spune nimic în plus.
Forma cu popover · .select-anchor + .select-trigger
Aceeași alegere dintr-o listă închisă, alt mecanism de deschidere: un <button> care poartă valoarea și deschide .popover. Nu e .btn — un buton propune o acțiune și își declară intenția în variantă; ăsta poartă o valoare și trebuie citit ca un câmp, deci împrumută înălțimea, chenarul, radiusul și halo-ul neutru ale lui .select.
| Folosești | Când | De ce |
|---|---|---|
.select — implicit | Opțiunile sunt text simplu | Lista nativă dă gratis tastatura, roata de pe mobil, cititoarele de ecran și un singur tab stop. Nu se plătește nimic pentru ea. |
.select-trigger | Lista nu poate fi text: o grilă de calendar, un drapel, un rând secundar (cod fiscal, email), grupuri cu etichetă | <option> e redat de OS și nu primește markup. Când lista are structură, suprafața trebuie să fie a DS-ului. |
.typeahead | Utilizatorul filtrează scriind | Câmpul rămâne .input; vezi docs/components/typeahead.md. |
aria-expanded="true") — deschis arată ca focus, aceeași bordură fog și același haloMărimi și variante
.select, plus o treaptă de 30px pentru antete.select într-un .fieldStări
--c-mistopacity: 0 (n-a mai rămas niciun meniu de promis)Cei trei consumatori
Primitiva există fiindcă aceeași formă era desenată de trei ori: un .input readonly într-un div cu position:relative scris inline (câmpul de dată), o bară de filtre care urmează, și .cur-select-trigger (moneda, v6.1 — de azi alias).
.calendar, adică exact lista care nu poate fi <option>Grila întreagă → components/calendar.html. Înăuntrul popoverului, luna și anul rămân <select> native — un popover în popover n-are poveste de închidere.
.sm, aliniate, valoarea nealeasă pe .placeholder.select-trigger.lg în .amount-row la erou, .sm.bare în addon-ul unui .input-group la mărime normală (Money input). Rândul de sumă îi dă greutatea 600; restul, lista cu bifă inclusiv, vine din primitivă.cur-select și .cur-select-trigger rămân valide ca alias-uri — aceleași reguli, nu un al doilea corp de declarații. Se scot cel mai devreme la N+2.
Reguli — select
SVG inline în background-image. appearance: none ascunde nativul. Padding-right 36px (md) / 40px (lg).
Focus, error, disabled, tonal, lg — aceleași clase, aceleași reguli vizuale.
Label .field-label + select .select + help .field-help — structură identică cu input.
Elementele <option> sunt redate de OS — nu le stiliza. Textul lor moștenește fontul paginii.
.select-trigger cere un motiv: lista nu poate fi text simplu. Lista nativă dă gratis tastatura, roata de pe mobil și un singur tab stop.
Un buton propune o acțiune; ăsta poartă o valoare. Împrumută scara lui .select, nu a lui .btn.
Starea se citește din atributul care e oricum obligatoriu. .open pe ancoră e alias vechi — două locuri unde scrie „e deschis" înseamnă unul care poate minți.
Înăuntrul unui .popover deschis, alegerea rămâne nativă. Click-ul din afară care închide meniul interior îl închide și pe cel exterior.
Obligatoriu aria-expanded, aria-haspopup și focus înapoi în trigger pe Esc — cel mai frecvent defect al componentei.
.select-anchor, nu style="position:relative". O clasă se auditează, un stil inline nu.