WoodyLabBuild with UIMS

Documentazione · src/engine/styling

Stili e classi

Da dove nascono le classi, e perché non esiste una safelist da mantenere. Tre file nel motore e due store che Tailwind legge direttamente.

Le classi le genera Tailwind, dagli store

Non esiste una safelist. Tailwind fa @source direttamente sui file dello Studio — db.viewmodes.json e db.styles.json — e genera esattamente le classi che i viewMode e gli stili usano davvero.

Due conseguenze. In produzione il CSS è minimale per costruzione: nessuno deve ricordarsi di potarlo. E le varianti arbitrarie ([&_p]:m-0, hover:text-primary) funzionano senza doverle enumerare, perché Tailwind le vede scritte nel file.

È anche il motivo per cui gli store sono file e non righe di database, ed è la dipendenza più importante da non rompere: spostare la forma in database significherebbe reintrodurre la safelist il giorno stesso.

Il `className` è configurazione, non relazione

Il campo className di un viewMode o di un’istanza è una stringa di classi Tailwind. Non è validato contro un elenco, e non perché ci si fidi: le classi vivono negli store, che sono la stessa sorgente da cui Tailwind le genera. Se le scrivi lì, esistono.

Sull’ISTANZA di un blocco il className si somma a quello del viewMode. Serve per lo scostamento locale — una spaziatura, un colore su questa sezione e non sulle altre. Quando lo stesso scostamento compare tre volte, non è più uno scostamento: è uno stile, e va nella style guide.

styling/resolveStyles.ts

la composizione libera passa verbatim: nessuna safelist contro cui validare

styling/resolveViewModeStyles.ts

gli stili dichiarati dal viewMode e dai suoi slot

La style guide: uno stile nominato, un punto solo

Gli aspetti RICORRENTI — ombra, raggio, superficie, bordo, tipografia — non si scrivono come classi nel className di ogni viewMode. Si definiscono una volta come stili nominati in db.styles.json e si richiamano per nome dal viewMode o dallo slot.

Il guadagno è la propagazione: modifichi lo stile in un punto e cambiano tutti i viewMode che lo includono. Lo stile diventa la fonte di verità dell’aspetto, invece della classe copiata in trenta posti — che è la forma in cui l’aspetto si perde nel tempo.

Ogni stile ha una categoria, e la categoria È un ASSE: superficie, inchiostro, bordo, raggio, elevazione, trama, luce, rango tipografico, movimento, filtro. Un asse è una decisione sola, e uno stile ne occupa uno — se ne dichiara due non è uno stile, è una composizione che si è presa un nome, e chi voleva una delle due cose non ce l’ha.

Ogni asse si copre per intero, anche i gradini che oggi non servono a nessuno: la scala è il vocabolario, e un vocabolario con dei buchi costringe a scrivere una utility. Un modificatore invece non si genera — l’opacità è già di Tailwind, in cento valori — e si compone al volo.

I colori restano nella palette, i font nei settings, le variabili dei componenti nel loro tab: nessun doppione.

I quattro livelli

L’aspetto passa per quattro strati, e nessuno ripete quello sotto. IL VALORE vive nella palette o nelle variabili base. IL MECCANISMO sono le utility di Tailwind, collegate alle nostre variabili dal ponte @theme. IL VOCABOLARIO è uno stile per ogni valore di ogni asse — textXs, roundedLg, surfacePrimary. IL RUOLO è una composizione di nomi del terzo livello con il nome che userebbe una persona: eyebrow, panel, imgPolaroid.

Si chiama 3+1: i primi tre sono il sistema di token, il quarto è d’appoggio. Togli il quarto e tutto continua a funzionare; togli il terzo e crolla tutto.

Il terzo livello è spesso uno a uno con una utility — textXs È text-xs — e non è un difetto. Non aggiunge un valore, aggiunge un POSTO: il giorno che deve portare anche una spaziatura si scrive lì, e nessuno dei chiamanti se ne accorge. È anche ciò che rende meccanica la regola per cui un viewMode scrive struttura e mai aspetto: se l’aspetto si può dire solo citando uno stile, qualunque utility d’aspetto in un className è una violazione, senza casi da discutere.

Due modi di scrivere uno stile

Uno stile può portare CSS grezzo: genera una regola .us-<nome>, usa var(--color-*) per seguire la palette, ed è la strada giusta per ombre, superfici e gradienti — cose che in Tailwind si scriverebbero male.

Oppure può portare classi Tailwind (shadow-sm, rounded-md), e allora si ragiona nel vocabolario della propria style guide. Queste le genera Tailwind, che scansiona db.styles.json. Il CSS grezzo non passa da Tailwind: è una regola scritta a build in .uims/uims-styles.css.

Il token us-<nome> funziona in qualunque className: viene espanso nelle classi dello stile che nomina. Un token che nomina uno stile css, o uno che non esiste, viene lasciato com’è — nel primo caso è già una classe valida, nel secondo è innocuo.

L’espansione è RICORSIVA, ed è ciò che rende il quarto livello davvero un livello: us-eyebrow cita us-textXs us-weightSemibold …, e quelli si espandono a loro volta fino alle utility vere. Con un giro solo una composizione poteva citare uno stile css — già una classe — ma non uno classes, il cui nome sarebbe arrivato nel DOM senza una regola dietro. Otto giri di guardia, perché due composizioni che si citano a vicenda si fermino invece di non tornare mai.

E la scelta fra le due forme non è un gusto: tailwind-merge sa risolvere un conflitto fra due utility — l’ultima vince — ma contro una regola .us-* è cieco. Quindi classes ovunque le utility bastino, e css solo dove non arrivano; e lì ogni stile occupa un asse, perché nessuno lo protegge dal collidere. L’unico posto dove l’ordine si sfrutta di proposito è l’inchiostro, scritto in css e collocato DOPO le superfici, così un accento deliberato vince sull’inchiostro che il fondo dichiara.

styling/resolveClassName.ts

espande i token us-<nome> nelle classi dello stile riferito

I temi non sono un foglio di stile in più

Un tema è una palette più dei font. Le variabili del tema attivo diventano CSS var in .uims/uims-theme-vars.css, generato a build e importato da styles.css: cambiare tema ri-stila tutto senza toccare né il contenuto né i viewMode.

La differenziazione STRUTTURALE — bordi, ombre, spaziature, tipografia — sta in parte nei themeStyles dei viewMode, indicizzati per nome di tema. È la distinzione che tiene i temi leggeri: un catalogo di temi non è un catalogo di copie della style guide.

La palette ha quaranta chiavi: dieci ruoli, ognuno con la variante scura e con il proprio INCHIOSTRO. Il fondo e il suo inchiostro sono un incarico solo — chi compone scrive us-surfacePrimary e riceve tutti e due, e non può sbagliare l’accoppiata perché non ha modo di separarla. E un tema dichiara la palette INTERA: non c’è eredità sui colori, un ruolo omesso non degrada, sparisce.

Per il raggio la strada è un’altra, ed è quella verso cui il resto si muoverà: un tema dichiara --el-radius fra le sue vars e gli elementi lo consumano. Prima serviva un override per tema su OGNI bottone — duecentoquarantotto voci che ricopiavano l’intera lista di stili per cambiare un angolo — e ora è una riga sola: un bottone nuovo eredita senza che nessuno scriva niente.

src/theme/index.ts

le variabili complete di un tema: base ⊕ palette ⊕ font ⊕ vars

Le variabili complete di un tema

// base ⊕ palette ⊕ font ⊕ override propri.
// Corto e legittimo su movimento e ombre; sui COLORI no: un ruolo omesso sparisce.
src/utils/generateCssVars.ts

le variabili di un tema rese CSS var — le consuma il generatore gen:css

src/utils/hexToRgb.ts

la conversione che serve alle var colore con opacità