Documentazione
Tailwind
Gli stili di NSH seguono Tailwind CSS 4. In Rust, le utility di Tailwind sono metodi di stile tipizzati con gli stessi nomi e valori (.p_4() è p-4, .rounded_2xl() è rounded-2xl, ZINC.shade(900) è zinc-900); in TSX, ogni elemento accetta le classi vere e proprie in class, risolte in modo nativo rispetto al tema predefinito di Tailwind 4. Ogni esempio qui sotto mostra prima i metodi tipizzati e poi le classi:
import { mount, Scroll, signal, Text, View } from "nsh";
function App() {
const count = signal(0);
const busy = signal(false);
const tab = signal("Day");
const tabs = ["Day", "Week", "Month"];
const buttons = () => (
<View class="flex-row flex-wrap gap-2">
<View
onPress={() => count.update((n) => n + 1)}
class="rounded-lg bg-lime-300 px-4 py-2 transition-colors duration-150 motion-reduce:transition-none hover:bg-lime-200 active:bg-lime-400 focus-visible:outline-2 focus-visible:outline-sky-500"
>
<Text class="font-medium text-lime-950">Pressed {count()}×</Text>
</View>
<View
onPress={() => busy.set(!busy())}
class="rounded-lg border border-black/10 bg-white px-4 py-2 shadow-sm transition motion-reduce:transition-none hover:bg-zinc-50 active:scale-95 active:bg-zinc-100 dark:border-[#2c2e33] dark:bg-[#24262a] dark:hover:bg-[#2a2c31] dark:active:bg-[#2d2f34] focus-visible:outline-2 focus-visible:outline-sky-500"
>
<Text class="font-medium text-[#15161a] dark:text-[#eceef1]">{busy() ? "Enable" : "Disable"}</Text>
</View>
<View disabled={busy()} onPress={() => count.set(0)} class="rounded-lg bg-emerald-400 px-4 py-2 enabled:hover:bg-emerald-300 active:bg-emerald-500 disabled:opacity-40 focus-visible:outline-2 focus-visible:outline-sky-500">
<Text class="font-medium text-emerald-950">Reset</Text>
</View>
</View>
);
const tabsRow = () => (
<View class="flex-row rounded-xl border border-black/5 bg-[#ecece7] p-1 dark:border-[#1f2124] dark:bg-[#0c0d0f]">
<Each of={tabs}>
{(t) => (
<View
onPress={() => tab.set(t())}
class="flex-1 items-center rounded-lg py-2 transition-colors motion-reduce:transition-none active:opacity-70 focus-visible:outline-2 focus-visible:outline-sky-500"
classList={{ "bg-white shadow-sm dark:bg-[#2a2c31]": tab() === t(), "hover:bg-black/5 dark:hover:bg-white/5": tab() !== t() }}
>
<Text class="text-sm font-medium" classList={{ "text-[#15161a] dark:text-[#eceef1]": tab() === t(), "text-[#565a61] dark:text-[#8d9198]": tab() !== t() }}>
{t()}
</Text>
</View>
)}
</Each>
</View>
);
const gradient = () => (
<View class="overflow-hidden rounded-2xl bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400 p-5 shadow-xl shadow-lime-300/20">
<Text class="text-xl font-bold text-zinc-950">{tab()} view</Text>
<Text class="mt-1 text-sm text-zinc-950/70">bg-linear-to-br from-lime-300 via-emerald-300 to-sky-400</Text>
</View>
);
const grid = () => (
<View class="grid grid-cols-3 gap-2">
<View class="col-span-2 h-14 items-center justify-center rounded-lg bg-sky-950">
<Text class="text-xs text-sky-200">col-span-2</Text>
</View>
<View class="h-14 rounded-lg bg-sky-900" />
<View class="h-14 rounded-lg bg-sky-800" />
<View class="row-span-2 rounded-lg bg-sky-400" />
<View class="h-14 rounded-lg bg-sky-700" />
<View class="h-14 rounded-lg bg-sky-600" />
<View class="h-14 rounded-lg bg-sky-500" />
</View>
);
const list = () => (
<View class="overflow-hidden rounded-xl border border-black/10 bg-white shadow-sm dark:border-[#2c2e33] dark:bg-[#18191c]">
<Each of={["Inbox", "Drafts", "Sent", "Archive"]}>
{(item) => (
<View class="flex-row items-center justify-between border-t border-black/5 px-4 py-3 first:border-t-0 even:bg-[#fafaf7] active:bg-lime-50 dark:border-[#1f2124] dark:even:bg-[#141518] dark:active:bg-[#24262a]">
<Text class="text-[#15161a] dark:text-[#eceef1]">{item()}</Text>
<Text class="text-[#8c9097] dark:text-[#5a5e65]">›</Text>
</View>
)}
</Each>
</View>
);
const motion = () => (
<View class="flex-row items-center justify-around py-2">
<View class="size-8 animate-spin motion-reduce:animate-none rounded-md bg-lime-300" />
<View class="size-8 animate-bounce motion-reduce:animate-none rounded-full bg-sky-400" />
<View class="h-8 w-14 animate-pulse motion-reduce:animate-none rounded-md bg-[#e1e1db] dark:bg-[#2c2e33]" />
</View>
);
return (
<Scroll class="flex-1 bg-[#f3f3ef] dark:bg-[#101113]">
<View class="gap-5 p-5 pt-6">
<Text class="text-2xl font-bold tracking-tight text-[#15161a] dark:text-[#eceef1]">Tailwind classes</Text>
{/* Two columns from `md:` on (768 points): breakpoints follow the window. */}
<View class="gap-5 md:flex-row md:items-start">
<View class="gap-5 md:min-w-0 md:flex-1">
{buttons()}
{tabsRow()}
{gradient()}
</View>
<View class="gap-5 md:min-w-0 md:flex-1">
{grid()}
{list()}
{motion()}
</View>
</View>
</View>
</Scroll>
);
}
mount(App);use nsh_core::element::palette::ZINC;
view()
.flex_row()
.items_center()
.gap_3()
.rounded_2xl()
.bg(AdaptiveColor::new(white(), ZINC.shade(800)))
.p_4()
.shadow_lg()
.active(|s| s.scale(0.98))
.child(
text("Tailwind on native views")
.text_base()
.font_weight(FontWeight::SEMIBOLD)
.text_color(AdaptiveColor::new(ZINC.shade(900), white())),
)<View class="flex-row items-center gap-3 rounded-2xl bg-white p-4 shadow-lg active:scale-[0.98] dark:bg-zinc-800">
<Text class="text-base font-semibold text-zinc-900 dark:text-white">Tailwind on native views</Text>
</View>I metodi tipizzati vengono tradotti nelle stesse regole di stile in cui si compilano le classi, senza alcuna stringa da analizzare. Per le classi, non viene generato alcun CSS e nulla viene eseguito in JavaScript: la stringa viene passata al core, che analizza una sola volta ogni stringa di classi distinta (e la memorizza in cache), la trasforma in regole di stile e le applica. Le varianti che dipendono dallo stato (pressed, dark mode, larghezza della finestra) vengono riapplicate dal core quando quello stato cambia, e gli stati di pressione e di focus sono tracciati in modo nativo, quindi il feedback active: (.active(..) in Rust) non richiede alcun passaggio di andata e ritorno verso il tuo codice.
Le classi e la prop style producono gli stessi stili nativi e possono essere combinate; questa pagina tratta ciò che è specifico delle classi e dei loro equivalenti tipizzati. Il riferimento Tailwind elenca tutte le utility supportate.
#class e classList
view()
.rounded_lg()
.px_4()
.py_2()
.transition_colors()
.bg(move || if selected.get() { INDIGO.shade(600) } else { ZINC.shade(200) })
.style_when(busy, |s| s.opacity(0.5))<View
class="rounded-lg px-4 py-2 transition-colors"
classList={{ "bg-indigo-600": selected(), "bg-zinc-200": !selected(), "opacity-50": busy() }}
/>classè una stringa di classi. Può essere dinamica:class={`h-6 rounded bg-${color()}-500`}funziona, perché le classi vengono risolte a run time.classListassocia le classi a condizioni: una classe si applica finché il suo valore è truthy. Viene applicata dopoclass.- In Rust, un metodo a cui viene passata una closure o un signal la segue (
.bg(move || ..)),.style_when(condition, |s| ..)applica gli stili finché una condizione è vera, e.when(condition, |v| ..)decide una sola volta, quando l’elemento viene creato.
Come vengono risolti i conflitti:
- Le classi successive prevalgono su quelle precedenti nella stessa stringa:
"p-2 p-4"èp-4. Non servetailwind-merge: un componente può aggiungere in coda ilclasspassato dal chiamante. Anche in Rust i metodi successivi prevalgono. - Le classi con variante si applicano dopo quelle semplici, ordinate secondo l’ordine delle varianti di Tailwind (
hover:prima diactive:,max-*:dal più ampio, breakpoint in ordine crescente,dark:per ultimo), indipendentemente dalla loro posizione nella stringa. Come in Tailwind, decide la variante più alta di una classe, poi la successiva più alta, e una classe con una variante in più viene dopo:dark:hover:bg-a dark:bg-bèbg-ain hover in dark mode, emax-md:p-2 max-lg:p-4èp-2sottomd. Come in CSS,odd:bg-a last:bg-bdà a una riga finale disparibg-a, perché Tailwind ordinaodddopolast. styleprevale sulle classi. Una chiave di stile è la proprietà CSS con il suo nome, quella che impostano le classi, quindi sostituisce solo quella proprietà:style={{ paddingLeft: 8 }}sostituiscepl-4e mantienepr-4.!rende importante una classe:!p-0.
I componenti Rust (quelli di nsh/base e quelli del crate dell’app) accettano class e classList anche da TSX; le tue classi vengono unite dopo gli stili propri del componente e prevalgono a parità di priorità.
#Varianti
| Variante | Si applica quando | Rust |
|---|---|---|
active: | La view è premuta | .active(|s| ..) |
hover: | Un puntatore è sopra la view (puntatori di iPad e Mac) | .hover(|s| ..) |
focus:, focus-visible: | Un input ha il focus | .focus(|s| ..) |
focus-within: | La view o un discendente ha il focus (il contenuto di un Overlay è un discendente della view in cui si trova) | .focus_within(..) |
disabled:, enabled: | La prop disabled dell’elemento è true / false | .disabled_style(|s| ..) |
selected: | L’elemento è selezionato (selected di un Button, accessibilityState.selected) | .selected_style(..) |
checked: | L’elemento è checked (accessibilityState.checked è true; "mixed" non è checked) | .checked_style(..) |
indeterminate: | L’elemento è indeterminate (accessibilityState.checked è "mixed") | .indeterminate_style(..) |
aria-expanded: | L’elemento è expanded (accessibilityState.expanded è true): il trigger di un Popover o di un Menu mentre il suo popup è aperto | .expanded_style(..) |
aria-pressed: | L’elemento è un toggle button attivo (accessibilityState.pressed è true; "mixed" non è premuto). Non active:, una pressione in corso | .aria_pressed(..), .pressed_style(..) |
aria-busy:, aria-checked:, aria-selected: | accessibilityState.busy, checked (true, non "mixed") o selected è true, come le varianti aria-* di Tailwind: dopo le pseudo-classi, nell’ordine dei loro nomi | |
data-<name>: | L’elemento ha l’attributo data-* name, come le varianti booleane data-* di Tailwind (&[data-range-start]): ciò che la parte di un componente mostra oltre ai suoi stati, ad esempio il giorno di un calendario all’inizio di un intervallo (data-range-start:). Dopo le varianti aria-*, nell’ordine dei loro nomi | Interactive::project_data(name, ..) ne imposta uno sulla parte di un componente |
group-active:, group-hover:, group-focus:, group-focus-within:, group-disabled:, group-selected:, group-checked:, group-indeterminate:, group-aria-expanded:, group-aria-pressed:, group-aria-busy:, group-aria-checked:, group-aria-selected:, group-data-<name>: | L’antenato più vicino con la classe group si trova in quello stato, oppure ha quell’attributo data-* | .group() sull’antenato; .group_active(..), .group_hover(..), .group_focus_within(..) all’interno |
group-active/name: … | Lo stesso, per un antenato contrassegnato con group/name | .group_named("name"); .group_active_named("name", ..), … |
first:, last:, odd:, even:, only: | La posizione dell’elemento stesso tra i suoi fratelli. Per colorare l’etichetta di una riga in base alla posizione della riga, metti last:text-* sulla riga: l’etichetta lo eredita | Rust semplice a partire dall’indice della riga (at.is_first(), at.is_last(), at.index() % 2) |
placeholder: | Stilizza il placeholder di un input (placeholder:text-zinc-400) | .placeholder_color(..) |
selection: | Colora l’evidenziazione della selezione di un input o di un testo selezionabile, e di quelli al suo interno (selection:bg-violet-200) | .selection_color(..) |
dark: | L’aspetto del sistema è scuro | Un AdaptiveColor, oppure .dark(|s| ..) |
sm:, md:, lg:, xl:, 2xl: | La finestra è larga almeno 640, 768, 1024, 1280, 1536 punti | .style_when(|| window().width >= px(768.0), |s| ..) |
max-sm:, max-md:, … | La finestra è più stretta del breakpoint | Lo stesso, con < |
min-[600px]:, max-[600px]: | Breakpoint arbitrari | Lo stesso |
portrait:, landscape: | L’orientamento della finestra | window().width > window().height |
ios:, android:, web: | La piattaforma | .when(os() == Os::Ios, |v| ..) |
rtl:, ltr: | La direzione del layout | .direction(LayoutDirection::..) su una view |
motion-safe:, motion-reduce: | L’impostazione di riduzione del movimento dell’utente | reduce_motion() |
Le varianti si combinano: dark:active:bg-zinc-700, md:landscape:flex-row. In Rust, un AdaptiveColor in uno stile .active(..) è dark:active:.
view()
.group()
.flex_row()
.items_center()
.gap_3()
.rounded_xl()
.bg(white())
.p_3()
.active(|s| s.bg(ZINC.shade(50)))
.child(text("Settings").flex_1().text_color(ZINC.shade(900)))
.child(
text("›")
.text_color(ZINC.shade(300))
.transition_transform()
.group_active(|s| s.translate_x(px(4.0))),
)<View class="group flex-row items-center gap-3 rounded-xl bg-white p-3 active:bg-zinc-50">
<Text class="flex-1 text-zinc-900">Settings</Text>
<Text class="text-zinc-300 transition-transform group-active:translate-x-1">›</Text>
</View>disabled su qualsiasi elemento (.disabled(..) in Rust) applica le classi disabled: (.disabled_style(..)) e rimuove i suoi gestori di pressione da ciò che è montato, quindi non mostra nemmeno alcun feedback di pressione:
view()
.disabled(saving)
.on_press(save)
.rounded_lg()
.bg(EMERALD.shade(500))
.px_4()
.py_2()
.active(|s| s.bg(EMERALD.shade(600)))
.disabled_style(|s| s.opacity(0.4))
.child(text("Save").font_weight(FontWeight::MEDIUM).text_color(white()))<View disabled={saving()} onPress={save} class="rounded-lg bg-emerald-500 px-4 py-2 active:bg-emerald-600 disabled:opacity-40">
<Text class="font-medium text-white">Save</Text>
</View>I breakpoint si confrontano con la finestra (la superficie in cui NSH esegue il rendering), non con il dispositivo: in una split view di iPad o in una finestra ridimensionabile, seguono la larghezza della finestra, come fa window().
#Differenze rispetto a Tailwind sul web
- Le unità sono punti.
p-4(.p_4()) vale 16 punti (1 passo di spaziatura = 4pt, 1rem = 16pt). Anche i valori arbitrari inpxsono in punti:p-[13px](.p(px(13.0))) vale 13pt. flexè una riga, come in CSS:class="flex"dispone i suoi figli in riga a meno che una classe di direzione dica altrimenti (in qualsiasi punto della stringa:flex-col flexè una colonna). Anchedisplay: "flex"di uno stile e.flex()di Rust sono righe allo stesso modo, e una view che non imposta alcun display mantiene la colonna predefinita di NSH.blockeflow-rootusano il layout a blocchi, e lo stesso vale perinlineeinline-block, come un browser li dispone in un genitore flex o grid. In Rust,.flex_row()e.flex_col()impostano la direzione.- Le classi di testo sulle view vengono ereditate.
text-*,font-*,leading-*,tracking-*e le altre utility di testo su una view (.text_color(..),.font_weight(..), … in Rust) passano agli elementi di testo e di input al suo interno, come fa l’ereditarietà CSS:<View class="text-white">rende bianche le sue etichette. - Ogni view è un box a sé.
overflow-x-hiddeneoverflow-y-hiddentagliano entrambi gli assi, come faoverflow-hidden(lo scorrimento è compito dell’elemento scroll);invisiblenasconde la view e tutto ciò che contiene;mix-blend-*fonde una view con tutto ciò che è disegnato dietro di essa; i boxblocktengono separati i propri margini da quelli dei figli; una viewabsoluteviene posizionata rispetto all’antenato posizionato più vicino, come in CSS;fixeddispone una view nella sua root view (gli altri elementi vengono posizionati comeabsolute). - Il testo mantiene gli spazi bianchi. Spazi e a capo in una stringa vengono disegnati come scritti, e
whitespace-*imposta il wrapping; il compilatore JSX comprime gli spazi bianchi del testo JSX letterale, come fa JSX. - Le transizioni animano gruppi di proprietà:
transitionanima colori, opacità, ombre e trasformazioni,transition-allanima anche i raggi degli angoli e gli spessori dei bordi; filtri, filtri di sfondo, gradienti e layout assumono i nuovi valori immediatamente. focus-visible:èfocus:: si applica ogni volta che l’elemento ha il focus.- Gli sfondi di stato si vedono sopra i gradienti. In CSS un gradiente copre
background-color, quindiactive:bg-*su una superficiebg-linear-*non si vedrebbe mai; in NSH un colore di sfondo daactive:,hover:,focus:ogroup-*:sostituisce il gradiente finché quello stato è attivo (a meno che lo stato imposti un proprio gradiente, comeactive:from-*). Lo stesso vale peractiveStylee per gli altri stili di stato, e per i colori di sfondo animati. - Le impostazioni di testo e di tocco sono chiavi di stile:
line-clamp-*impostanumberOfLines,text-ellipsisetext-clipimpostanotextOverflow,truncateentrambi,select-*impostauserSelectepointer-events-*impostapointerEvents, quindi funzionano nelle varianti di stato e perdono controstyleinline come ogni classe;caret-*,placeholder:text-*eselection:bg-*impostano allo stesso modo gli stilicaretColor,placeholderColoreselectionColor. In Rust sono metodiStyled(.number_of_lines(..),.truncate(),.text_overflow(..),.select_text(),.pointer_events(..), …).whitespace-nowrapmantiene un testo su una sola riga come classe.object-*impostano gli stiliobjectFiteobjectPositiondi immagini e video, che le chiavi di uno stile sostituiscono come qualsiasi altra. - L’insieme supportato: utility e varianti di Tailwind 4 che hanno un significato nativo. Le varianti
peer-*, i pseudo-elementi (before:,after:) e le proprietà arbitrarie ([mask-type:luminance]) vengono segnalati come classi sconosciute, così come ogni utility per cui NSH non ha un significato nativo, in modo che una classe non significhi mai qualcosa di diverso rispetto a un browser. Gli hint di rendering (antialiased,will-change-transform,appearance-none,box-border, …) sono accettati e non cambiano nulla.
Tutto il resto funziona come documentato da Tailwind 4: la palette di colori, i modificatori di opacità (bg-black/40), i valori arbitrari (bg-[#34c759], w-[72px], grid-cols-[1fr_2fr]; su p, px, py, pt, pr, pb, pl e sulle utility m anche le variabili di ambiente, pt-[env(safe-area-inset-top)], pb-[calc(env(keyboard-inset-height)+49px)], vedi Safe area e Keyboard), le frazioni (w-1/2, translate-x-1/2), ombre e ring, gradienti (bg-linear-to-r, bg-radial, bg-conic con from- / via- / to-), filtri, backdrop-blur-*, mix-blend-*, trasformazioni, transition-* e duration-*, animate-spin / ping / pulse / bounce, e grid, ciascuno con il proprio metodo tipizzato in Rust. I limiti di piattaforma di filtri e blending valgono anche per le classi.
#Tema
Le classi vengono risolte rispetto al tema predefinito di Tailwind 4. Estendilo in nsh.json sotto "tailwind"; le tue voci vengono aggiunte a quelle predefinite (oppure sostituiscono quella predefinita con lo stesso nome):
{
"tailwind": {
"colors": {
"brand": { "50": "#f5f3ff", "500": "#7c3aed", "600": "#6d28d9", "DEFAULT": "#7c3aed" },
"ink": "#1f2328"
},
"radius": { "card": 14 },
"fontFamily": { "display": "Pacifico" },
"breakpoints": { "tablet": 600 }
}
}
Con questo tema, bg-brand-500, text-brand (la sfumatura DEFAULT), border-ink, rounded-card, font-display e tablet:flex-row sono classi valide.
| Chiave | Valori | Genera |
|---|---|---|
colors | Un colore CSS, o un oggetto di sfumature ("DEFAULT" per il nome semplice) | bg-*, text-*, border-*, from-*, ring-*, … |
spacing | Punti per passo di spaziatura (predefinito 4) | p-*, m-*, gap-*, w-*, … |
rem | Punti per rem nei valori arbitrari (predefinito 16) | |
breakpoints | Punti | Varianti name: |
container | Punti | Dimensioni max-w-* |
fontSize | Punti, oppure [size, lineHeight] con l’altezza di riga multipla della dimensione | text-* |
fontWeight | 1–1000 | font-* |
fontFamily | Il nome di una famiglia (una famiglia di sistema o il nome di un font importato) | font-* |
tracking | Em | tracking-* |
leading | Multipli della dimensione del font | leading-* |
radius | Punti | rounded-* |
shadow, insetShadow, dropShadow | Un box-shadow CSS | shadow-*, inset-shadow-*, drop-shadow-* |
blur | Punti | blur-*, backdrop-blur-* |
perspective | Punti | perspective-* |
aspect | Un rapporto (1.5) | aspect-* |
ease | [x1, y1, x2, y2] | ease-* |
Le chiavi sconosciute e i valori non validi sono errori di build (nsh.json: tailwind: unknown theme key 'colours'). Il tema viene compilato nel bundle e applicato prima del primo frame.
Il tema di Tailwind dà forma alle tue classi. I metodi tipizzati mantengono le scale predefinite di Tailwind, così un componente Rust ha lo stesso aspetto in ogni app, e ricavano l’aspetto dell’app dai design token del suo tema, "theme" in nsh.json (Rust).
#Token del tema
Il tema dell’app ("theme" in nsh.json) contiene i suoi design token: i colori di shadcn/ui (background, foreground, primary, muted-foreground, border, ring, chart-1, sidebar, …), il suo radius, i suoi font font-sans e font-mono, e qualsiasi token aggiunto dall’app. Le classi li nominano come Tailwind nomina le variabili di un @theme CSS, i componenti Rust li leggono con theme() da nsh_base, e TSX li legge con token() da nsh, così una schermata TSX e i componenti Rust seguono lo stesso tema:
use nsh_base::theme;
view()
.rounded(move || theme().radius)
.border_1()
.border_color(move || theme().border)
.bg(move || theme().card)
.p_4()
.child(text("Balance").text_color(move || theme().card_foreground))
.child(text("Updated just now").text_color(move || theme().muted_foreground))
.child(
view()
.rounded(move || theme().radius)
.bg(move || theme().primary)
.px_4()
.py_2()
.active(|s| s.bg(move || theme().primary.opacity(0.9)))
.child(text("Continue").text_color(move || theme().primary_foreground)),
)<View class="rounded-(--radius) border border-border bg-card p-4">
<Text class="text-card-foreground">Balance</Text>
<Text class="text-muted-foreground">Updated just now</Text>
<View class="rounded-(--radius) bg-primary px-4 py-2 active:bg-primary/90">
<Text class="text-primary-foreground">Continue</Text>
</View>
</View>Il nome di un token è quello della sua variabile CSS senza --, come lo scrive il file del tema: muted-foreground, radius, chart-1. In Rust, i token di shadcn sono metodi di theme() (primary(), muted_foreground(), radius(), font_mono()), e ogni token è theme().color("up"), theme().number("radius-card") oppure theme().font("font-display").
| Forma | Accetta | Esempio |
|---|---|---|
| Un nome di colore in qualsiasi utility di colore | Un colore del tema, anche con opacità | bg-primary, text-muted-foreground/70, bg-up/30, border-t-border, ring-ring, from-accent, placeholder:text-muted-foreground |
(--name), la scorciatoia di variabile di Tailwind 4 | Un colore del tema o un numero | bg-(--primary), rounded-(--radius), p-(--gap-card) |
(color:--name), (length:--name), (number:--name), (family-name:--name) | Lo stesso, con tipo esplicito dove una utility accetta più tipi di valore | text-(color:--up), text-(length:--text-title), font-(family-name:--font-mono) |
font-sans, font-mono | I font del tema, quando li imposta | font-mono |
- I tuoi token. Ogni nome nel file del tema è un token, così come lo è ogni variabile di un
@themeCSS:"up": "#16a34a"generabg-up,text-upebg-up/30;"radius-card": "14px"generarounded-(--radius-card);"font-display": "Pacifico"generafont-(family-name:--font-display). Le lunghezze sono in punti;(number:--name)passa il numero semplice. - Font.
font-sansè il font dell’app: ogni testo che non imposta una propria famiglia viene disegnato con esso, come una pagina con--font-sansdi Tailwind.font-monoè il font difont-monoe del codice inTextView. Se non impostati, entrambi sono i font di sistema. - Entrambe le modalità di aspetto. Un token ha il valore chiaro e quello scuro del tema:
bg-primary,theme().primaryetoken("primary")cambiano automaticamente con l’aspetto, senza la classedark:. Un colore che il tema imposta in una sola modalità viene usato in entrambe. - I tuoi colori Tailwind hanno la precedenza. Un nome in
"tailwind"colorso nella palette di Tailwind corrisponde a quel colore, quindibg-primaryè un token del tema quando il tema Tailwind non ha alcun coloreprimary. - Il tema può cambiare mentre l’app è in esecuzione. Quando a un’app incorporata in un’app nativa viene fornito il tema di quest’ultima, e ogni volta che il tema cambia, le classi che nominano token, i binding Rust che leggono
theme()e il codice TSX che leggetoken()vengono aggiornati, le classi e i binding in modo nativo. I token che il tema dell’app ospitante non ha mantengono i valori del"theme"dell’app stessa (poi quelli del tema predefinito). Un host che aggiunge soltanto il proprio brand fornisce override del tema, ossia i token che il suo brand modifica, nello stesso formato di file (iOS, Android,AppView::set_theme_overridessui desktop). - Verificate come le altre classi. Un nome che non è un token del tema predefinito né del
"theme"dell’app è una classe sconosciuta durante la build (unknown class 'bg-chart-up'). Per usare un token fornito solo dall’app ospitante, assegnagli un valore anche nel"theme"dell’app: quel valore viene mostrato quando l’app viene eseguita da sola.
Le classi che nominano token vengono risolte una volta per tema, come qualsiasi altra classe: disegnarle costa quanto un colore della palette.
#token() in TSX
token(name) da nsh è il valore di un token nell’aspetto mostrato: un colore come #rrggbbaa, una lunghezza come numero di punti, un font come nome della sua famiglia. È reattivo, quindi il codice che lo legge in un componente, in un memo o in un effect viene eseguito di nuovo quando cambiano il tema o l’aspetto, il che è adatto a valori che vanno dove non arriva nessuna classe, come un canvas o un grafico:
import { Canvas, token } from "nsh";
<Canvas
style={{ height: 160 }}
draw={(cx, { width, height }) => {
cx.beginPath();
cx.moveTo(0, height);
cx.lineTo(width, 0);
cx.strokeStyle = token("chart-1");
cx.lineWidth = 2;
cx.stroke();
}}
/>
I nomi sono tipizzati: NSH scrive i token dell’app in .nsh/types/nsh/tokens.d.ts a partire dal suo tema, quindi token("up") è un string una volta che "up" è presente nel tema, e un nome che il tema non ha è un errore di tipo.
#Il controllo delle classi
nsh build, nsh dev, nsh run e nsh test verificano i nomi di classe statici nel tuo JSX (stringhe letterali in class e le chiavi di classList) con lo stesso parser e lo stesso tema usati dal runtime, e segnalano le classi che non esistono:
warn nsh::build App.tsx:12:36: unknown class `bg-brand-550`
I warning non fanno fallire la build. Le classi costruite a runtime (bg-${color()}-500) vengono verificate quando il runtime le incontra per la prima volta, e registra nel log ogni classe sconosciuta una sola volta (unknown classes: ...). I metodi tipizzati sono verificati dal compilatore Rust.
#Classi o stili?
Entrambi vengono risolti in modo nativo e hanno più o meno lo stesso costo. Le classi sono più brevi e offrono le varianti (stati di pressione, dark mode, breakpoint) senza codice. Gli stili inline accettano valori calcolati in JavaScript e valori animati nativi (opacity: fade, translate: [x, 0]). La maggior parte delle app TSX usa le classi per l’aspetto statico e style per ciò che è calcolato o animato. I componenti Rust usano metodi tipizzati per entrambi, e collegano i valori animati con .bind_opacity(..), .bind_translate_x(..) e gli altri binding.
#Vedi anche
- Styling: la prop style, i metodi tipizzati e i limiti degli effetti sulle varie piattaforme.
- Riferimento Tailwind: ogni utility e variante.
- Layout: flexbox e grid, disponibili anche come classi.