Questa pagina è stata tradotta automaticamente e può contenere errori. Leggi l'originale in inglese

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:

guide-tailwind.tsx
guide-tailwind.tsxLive
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())),
    )

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))
  • class è una stringa di classi. Può essere dinamica: class={`h-6 rounded bg-${color()}-500`} funziona, perché le classi vengono risolte a run time.
  • classList associa le classi a condizioni: una classe si applica finché il suo valore è truthy. Viene applicata dopo class.
  • 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:

  1. Le classi successive prevalgono su quelle precedenti nella stessa stringa: "p-2 p-4" è p-4. Non serve tailwind-merge: un componente può aggiungere in coda il class passato dal chiamante. Anche in Rust i metodi successivi prevalgono.
  2. Le classi con variante si applicano dopo quelle semplici, ordinate secondo l’ordine delle varianti di Tailwind (hover: prima di active:, 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-a in hover in dark mode, e max-md:p-2 max-lg:p-4 è p-2 sotto md. Come in CSS, odd:bg-a last:bg-b dà a una riga finale dispari bg-a, perché Tailwind ordina odd dopo last.
  3. style prevale 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 }} sostituisce pl-4 e mantiene pr-4.
  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

VarianteSi applica quandoRust
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 nomiInteractive::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 ereditaRust 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 è scuroUn 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 breakpointLo stesso, con <
min-[600px]:, max-[600px]:Breakpoint arbitrariLo stesso
portrait:, landscape:L’orientamento della finestrawindow().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’utentereduce_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))),
    )

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()))

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 in px sono 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). Anche display: "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. block e flow-root usano il layout a blocchi, e lo stesso vale per inline e inline-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-hidden e overflow-y-hidden tagliano entrambi gli assi, come fa overflow-hidden (lo scorrimento è compito dell’elemento scroll); invisible nasconde la view e tutto ciò che contiene; mix-blend-* fonde una view con tutto ciò che è disegnato dietro di essa; i box block tengono separati i propri margini da quelli dei figli; una view absolute viene posizionata rispetto all’antenato posizionato più vicino, come in CSS; fixed dispone una view nella sua root view (gli altri elementi vengono posizionati come absolute).
  • 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à: transition anima colori, opacità, ombre e trasformazioni, transition-all anima 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, quindi active:bg-* su una superficie bg-linear-* non si vedrebbe mai; in NSH un colore di sfondo da active:, hover:, focus: o group-*: sostituisce il gradiente finché quello stato è attivo (a meno che lo stato imposti un proprio gradiente, come active:from-*). Lo stesso vale per activeStyle e 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-* imposta numberOfLines, text-ellipsis e text-clip impostano textOverflow, truncate entrambi, select-* imposta userSelect e pointer-events-* imposta pointerEvents, quindi funzionano nelle varianti di stato e perdono contro style inline come ogni classe; caret-*, placeholder:text-* e selection:bg-* impostano allo stesso modo gli stili caretColor, placeholderColor e selectionColor. In Rust sono metodi Styled (.number_of_lines(..), .truncate(), .text_overflow(..), .select_text(), .pointer_events(..), …). whitespace-nowrap mantiene un testo su una sola riga come classe. object-* impostano gli stili objectFit e objectPosition di 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.

ChiaveValoriGenera
colorsUn colore CSS, o un oggetto di sfumature ("DEFAULT" per il nome semplice)bg-*, text-*, border-*, from-*, ring-*, …
spacingPunti per passo di spaziatura (predefinito 4)p-*, m-*, gap-*, w-*, …
remPunti per rem nei valori arbitrari (predefinito 16)
breakpointsPuntiVarianti name:
containerPuntiDimensioni max-w-*
fontSizePunti, oppure [size, lineHeight] con l’altezza di riga multipla della dimensionetext-*
fontWeight1–1000font-*
fontFamilyIl nome di una famiglia (una famiglia di sistema o il nome di un font importato)font-*
trackingEmtracking-*
leadingMultipli della dimensione del fontleading-*
radiusPuntirounded-*
shadow, insetShadow, dropShadowUn box-shadow CSSshadow-*, inset-shadow-*, drop-shadow-*
blurPuntiblur-*, backdrop-blur-*
perspectivePuntiperspective-*
aspectUn 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)),
    )

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").

FormaAccettaEsempio
Un nome di colore in qualsiasi utility di coloreUn 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 4Un colore del tema o un numerobg-(--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 valoretext-(color:--up), text-(length:--text-title), font-(family-name:--font-mono)
font-sans, font-monoI font del tema, quando li impostafont-mono
  • I tuoi token. Ogni nome nel file del tema è un token, così come lo è ogni variabile di un @theme CSS: "up": "#16a34a" genera bg-up, text-up e bg-up/30; "radius-card": "14px" genera rounded-(--radius-card); "font-display": "Pacifico" genera font-(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-sans di Tailwind. font-mono è il font di font-mono e del codice in TextView. 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().primary e token("primary") cambiano automaticamente con l’aspetto, senza la classe dark:. 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" colors o nella palette di Tailwind corrisponde a quel colore, quindi bg-primary è un token del tema quando il tema Tailwind non ha alcun colore primary.
  • 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 legge token() 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_overrides sui 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.