Esta página foi traduzida automaticamente e pode conter erros. Ler o original em inglês

Documentação

Tailwind

Os estilos do NSH seguem o Tailwind CSS 4. Em Rust, os utilitários do Tailwind são métodos de estilo tipados com os mesmos nomes e valores (.p_4() é p-4, .rounded_2xl() é rounded-2xl, ZINC.shade(900) é zinc-900). Em TSX, todo elemento aceita as próprias classes em class, resolvidas nativamente com base no tema padrão do Tailwind 4. Cada exemplo abaixo mostra primeiro os métodos tipados e depois as classes:

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

Os métodos tipados são convertidos nas mesmas regras de estilo para as quais as classes são compiladas, sem nenhuma string para interpretar. Para as classes, nenhum CSS é gerado e nada é executado em JavaScript: a string é passada ao núcleo, que interpreta cada string de classes distinta uma única vez (e a mantém em cache), a transforma em regras de estilo e as aplica. As variantes que dependem de estado (pressionado, modo escuro, largura da janela) são reaplicadas pelo núcleo quando esse estado muda, e os estados de pressionado e de foco são rastreados nativamente, de modo que o feedback de active: (.active(..) em Rust) não faz nenhuma ida e volta ao seu código.

As classes e a prop style produzem os mesmos estilos nativos e podem ser combinadas. Esta página trata do que é específico das classes e de seus equivalentes tipados. A referência do Tailwind lista todos os utilitários compatíveis.

#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 é uma string de classes. Pode ser dinâmica: class={`h-6 rounded bg-${color()}-500`} funciona, porque as classes são resolvidas em tempo de execução.
  • classList mapeia classes para condições: uma classe é aplicada enquanto seu valor for verdadeiro. É aplicada depois de class.
  • Em Rust, um método que recebe um closure ou um signal o acompanha (.bg(move || ..)), .style_when(condition, |s| ..) aplica estilos enquanto uma condição se mantém, e .when(condition, |v| ..) decide uma única vez, quando o elemento é criado.

Como os conflitos são resolvidos:

  1. As classes posteriores vencem as anteriores na mesma string: "p-2 p-4" é p-4. Não há necessidade de tailwind-merge: um componente pode acrescentar o class que seu chamador passa. Em Rust, os métodos posteriores também vencem.
  2. As classes com variante são aplicadas depois das simples, ordenadas pela ordem de variantes do Tailwind (hover: antes de active:, max-*: da mais larga para a mais estreita, breakpoints em ordem crescente, dark: por último), seja qual for sua posição na string. Como no Tailwind, a variante mais alta de uma classe decide, depois a seguinte mais alta, e uma classe com uma variante a mais vem depois: dark:hover:bg-a dark:bg-b é bg-a enquanto está com o ponteiro sobre ele no modo escuro, e max-md:p-2 max-lg:p-4 é p-2 abaixo de md. Como no CSS, odd:bg-a last:bg-b dá a uma última linha ímpar bg-a, porque o Tailwind ordena odd depois de last.
  3. style vence as classes. Uma chave de estilo é a propriedade CSS de seu nome, a mesma que as classes definem, portanto substitui apenas essa propriedade: style={{ paddingLeft: 8 }} substitui pl-4 e mantém pr-4.
  4. ! torna uma classe importante: !p-0.

Os componentes Rust (os de nsh/base e os do crate do próprio app) também aceitam class e classList a partir de TSX. Suas classes são mescladas depois dos estilos do próprio componente e vencem em caso de mesma precedência.

#Variantes

Variante Aplica-se quando Rust
active: A view está pressionada .active(|s| ..)
hover: Um ponteiro está sobre a view (ponteiros do iPad e do Mac) .hover(|s| ..)
focus:, focus-visible: Um input está com foco .focus(|s| ..)
focus-within: A view ou um descendente está com foco (o conteúdo de um Overlay é descendente da view em que está) .focus_within(..)
disabled:, enabled: A prop disabled do elemento é verdadeira / falsa .disabled_style(|s| ..)
selected: O elemento está selecionado (em um Button: selected, accessibilityState.selected) .selected_style(..)
checked: O elemento está marcado (accessibilityState.checked é true; "mixed" não está marcado) .checked_style(..)
indeterminate: O elemento está indeterminado (accessibilityState.checked é "mixed") .indeterminate_style(..)
aria-expanded: O elemento está expandido (accessibilityState.expanded é true): o gatilho de um Popover ou de um Menu enquanto seu popup está aberto .expanded_style(..)
aria-pressed: O elemento é um botão de alternância que está ligado (accessibilityState.pressed é true; "mixed" não está pressionado). Diferente de active:, um pressionamento em andamento .aria_pressed(..), .pressed_style(..)
aria-busy:, aria-checked:, aria-selected: accessibilityState.busy, checked (true, não "mixed") ou selected é true, como as variantes aria-* do Tailwind: depois das pseudoclasses, na ordem de seus nomes
data-<name>: O elemento tem o atributo data-* name, como as variantes booleanas data-* do Tailwind (&[data-range-start]): o que a parte de um componente mostra além de seus estados, como o dia de um calendário no início de um intervalo (data-range-start:). Depois das variantes aria-*, na ordem de seus nomes Interactive::project_data(name, ..) define um na parte de um 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>: O ancestral mais próximo com a classe group está nesse estado, ou tem esse atributo data-* .group() no ancestral; .group_active(..), .group_hover(..), .group_focus_within(..) dentro
group-active/name: … O mesmo, para um ancestral marcado com group/name .group_named("name"); .group_active_named("name", ..), …
first:, last:, odd:, even:, only: A posição do próprio elemento entre seus irmãos. Para colorir o rótulo de uma linha pela posição da linha, coloque last:text-* na linha: o rótulo o herda Rust simples a partir do índice da linha (at.is_first(), at.is_last(), at.index() % 2)
placeholder: Estiliza o placeholder de um input (placeholder:text-zinc-400) .placeholder_color(..)
selection: Colore o destaque de seleção de um input ou de um texto selecionável, e dos que estão dentro do elemento (selection:bg-violet-200) .selection_color(..)
dark: A aparência do sistema é escura Um AdaptiveColor, ou .dark(|s| ..)
sm:, md:, lg:, xl:, 2xl: A janela tem pelo menos 640, 768, 1024, 1280, 1536 pontos de largura .style_when(|| window().width >= px(768.0), |s| ..)
max-sm:, max-md:, … A janela é mais estreita que o breakpoint O mesmo, com <
min-[600px]:, max-[600px]: Breakpoints arbitrários O mesmo
portrait:, landscape: A orientação da janela window().width > window().height
ios:, android:, web: A plataforma .when(os() == Os::Ios, |v| ..)
rtl:, ltr: A direção do layout .direction(LayoutDirection::..) em uma view
motion-safe:, motion-reduce: A configuração de reduzir movimento do usuário reduce_motion()

As variantes se combinam: dark:active:bg-zinc-700, md:landscape:flex-row. Em Rust, um AdaptiveColor em um estilo .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 em qualquer elemento (.disabled(..) em Rust) aplica as classes disabled: (.disabled_style(..)) e remove seus handlers de pressionamento do que está montado, de modo que também não mostra feedback de pressionamento:

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

Os breakpoints são comparados com a janela (a superfície na qual o NSH renderiza), não com o dispositivo: em uma split view do iPad ou em uma janela redimensionável, eles seguem a largura da janela, como window() faz.

#O que difere do Tailwind na web

  • As unidades são pontos. p-4 (.p_4()) equivale a 16 pontos (1 passo de espaçamento = 4pt, 1rem = 16pt). Valores arbitrários em px também são pontos: p-[13px] (.p(px(13.0))) é 13pt.
  • flex é uma linha, como no CSS: class="flex" dispõe seus filhos em uma linha, a menos que uma classe de direção diga o contrário (em qualquer posição da string: flex-col flex é uma coluna). O display: "flex" de um estilo e o .flex() do Rust são linhas da mesma forma, e uma view que não define display mantém a coluna padrão do NSH. block e flow-root usam layout de bloco, assim como inline e inline-block, como um navegador os dispõe em um pai flex ou grid. Em Rust, .flex_row() e .flex_col() definem a direção.
  • As classes de texto em views são herdadas. text-*, font-*, leading-*, tracking-* e os demais utilitários de texto em uma view (.text_color(..), .font_weight(..), … em Rust) passam para os elementos de texto e de input dentro dela, como a herança do CSS faz: <View class="text-white"> deixa seus rótulos brancos.
  • Toda view é uma caixa própria. overflow-x-hidden e overflow-y-hidden recortam os dois eixos, como overflow-hidden faz (a rolagem é do elemento scroll); invisible oculta a view e tudo o que está nela; mix-blend-* mistura uma view com tudo o que foi desenhado atrás dela; as caixas block mantêm separadas suas próprias margens e as de seus filhos; uma view absolute é posicionada em seu ancestral posicionado mais próximo, como no CSS; fixed dispõe uma view em sua view raiz (os outros elementos são posicionados como absolute).
  • O texto mantém seus espaços em branco. Espaços e quebras de linha em uma string são desenhados como foram escritos, e whitespace-* define a quebra de linha. O compilador JSX colapsa os espaços em branco do texto JSX literal, como o JSX faz.
  • As transições animam grupos de propriedades: transition anima cores, opacidade, sombras e transformações, e transition-all também raios de cantos e larguras de borda. Filtros, filtros de fundo, gradientes e layout assumem seus novos valores de uma só vez.
  • focus-visible: é focus:: aplica-se sempre que o elemento tem foco.
  • Os fundos de estado aparecem sobre os gradientes. No CSS, um gradiente cobre background-color, então active:bg-* em uma superfície bg-linear-* nunca seria visto. No NSH, uma cor de fundo de active:, hover:, focus: ou group-*: substitui o gradiente enquanto esse estado se mantém (a menos que o estado defina um gradiente próprio, como active:from-*). O mesmo vale para activeStyle e os demais estilos de estado, e para as cores de fundo animadas.
  • As configurações de texto e de toque são chaves de estilo: line-clamp-* define numberOfLines, text-ellipsis e text-clip definem textOverflow, truncate ambos, select-* define userSelect e pointer-events-* define pointerEvents, de modo que funcionam em variantes de estado e perdem para o style inline, como toda classe. caret-*, placeholder:text-* e selection:bg-* definem os estilos caretColor, placeholderColor e selectionColor da mesma forma. Em Rust, são métodos de Styled (.number_of_lines(..), .truncate(), .text_overflow(..), .select_text(), .pointer_events(..), …). whitespace-nowrap mantém um texto em uma única linha como classe. object-* definem os estilos objectFit e objectPosition de imagens e vídeos, que as chaves de um style substituem como quaisquer outras.
  • O conjunto compatível: utilitários e variantes do Tailwind 4 que têm um significado nativo. As variantes peer-*, os pseudoelementos (before:, after:) e as propriedades arbitrárias ([mask-type:luminance]) são reportados como classes desconhecidas, assim como todo utilitário para o qual o NSH não tem significado nativo, de modo que uma classe nunca significa algo diferente do que significa em um navegador. Dicas de renderização (antialiased, will-change-transform, appearance-none, box-border, …) são aceitas e não mudam nada.

Todo o resto funciona como documentado pelo Tailwind 4: a paleta de cores, os modificadores de opacidade (bg-black/40), os valores arbitrários (bg-[#34c759], w-[72px], grid-cols-[1fr_2fr]; em p, px, py, pt, pr, pb, pl e nos utilitários m também as variáveis de ambiente, pt-[env(safe-area-inset-top)], pb-[calc(env(keyboard-inset-height)+49px)], veja Área segura e Teclado), as frações (w-1/2, translate-x-1/2), sombras e rings, gradientes (bg-linear-to-r, bg-radial, bg-conic com from- / via- / to-), filtros, backdrop-blur-*, mix-blend-*, transformações, transition-* e duration-*, animate-spin / ping / pulse / bounce e grid, cada um com seu método tipado em Rust. Os limites de plataforma de filtros e blending também se aplicam às classes.

#Tema

As classes são resolvidas com base no tema padrão do Tailwind 4. Estenda-o em nsh.json sob "tailwind"; suas entradas são adicionadas às padrão (ou substituem a padrão de mesmo nome):

{
  "tailwind": {
    "colors": {
      "brand": { "50": "#f5f3ff", "500": "#7c3aed", "600": "#6d28d9", "DEFAULT": "#7c3aed" },
      "ink": "#1f2328"
    },
    "radius": { "card": 14 },
    "fontFamily": { "display": "Pacifico" },
    "breakpoints": { "tablet": 600 }
  }
}

Com este tema, bg-brand-500, text-brand (o tom DEFAULT), border-ink, rounded-card, font-display e tablet:flex-row são classes válidas.

Chave Valores Gera
colors Uma cor CSS, ou um objeto de tons ("DEFAULT" para o nome simples) bg-*, text-*, border-*, from-*, ring-*, …
spacing Pontos por passo de espaçamento (padrão 4) p-*, m-*, gap-*, w-*, …
rem Pontos por rem em valores arbitrários (padrão 16)
breakpoints Pontos Variantes name:
container Pontos Tamanhos max-w-*
fontSize Pontos, ou [size, lineHeight] com a altura de linha como múltiplo do tamanho text-*
fontWeight 1–1000 font-*
fontFamily Um nome de família (uma família do sistema ou o nome de uma fonte importada) font-*
tracking Ems tracking-*
leading Múltiplos do tamanho da fonte leading-*
radius Pontos rounded-*
shadow, insetShadow, dropShadow Um box-shadow CSS shadow-*, inset-shadow-*, drop-shadow-*
blur Pontos blur-*, backdrop-blur-*
perspective Pontos perspective-*
aspect Uma proporção (1.5) aspect-*
ease [x1, y1, x2, y2] ease-*

Chaves desconhecidas e valores inválidos são erros de build (nsh.json: tailwind: unknown theme key 'colours'). O tema é compilado no bundle e aplicado antes do primeiro frame.

O tema do Tailwind molda suas classes. Os métodos tipados mantêm as escalas padrão do Tailwind, de modo que um componente Rust tem a mesma aparência em todo app, e obtêm a aparência do app a partir dos design tokens de seu tema, "theme" em nsh.json (Rust).

#Tokens de tema

O tema do app ("theme" em nsh.json) guarda seus tokens de design: as cores do shadcn/ui (background, foreground, primary, muted-foreground, border, ring, chart-1, sidebar, …), seu radius, suas fontes font-sans e font-mono, e qualquer token que o app adicione. As classes os nomeiam como o Tailwind nomeia as variáveis de um @theme CSS, os componentes Rust os leem com theme() de nsh_base, e o TSX os lê com token() de nsh, de modo que uma tela TSX e os componentes Rust seguem o mesmo 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)),
    )

O nome de um token é o da sua variável CSS sem --, como o arquivo de tema o escreve: muted-foreground, radius, chart-1. Em Rust, os tokens do shadcn são métodos de theme() (primary(), muted_foreground(), radius(), font_mono()), e todo token é theme().color("up"), theme().number("radius-card") ou theme().font("font-display").

Forma Recebe Exemplo
Um nome de cor em qualquer utilitário de cor Uma cor do tema, também com opacidade bg-primary, text-muted-foreground/70, bg-up/30, border-t-border, ring-ring, from-accent, placeholder:text-muted-foreground
(--name), o atalho de variável do Tailwind 4 Uma cor do tema ou um número bg-(--primary), rounded-(--radius), p-(--gap-card)
(color:--name), (length:--name), (number:--name), (family-name:--name) O mesmo, com tipo, quando um utilitário aceita vários tipos de valor text-(color:--up), text-(length:--text-title), font-(family-name:--font-mono)
font-sans, font-mono As fontes do tema, quando ele as define font-mono
  • Seus próprios tokens. Todo nome no arquivo de tema é um token, assim como toda variável de um @theme CSS: "up": "#16a34a" gera bg-up, text-up e bg-up/30; "radius-card": "14px" gera rounded-(--radius-card); "font-display": "Pacifico" gera font-(family-name:--font-display). Os comprimentos são em pontos; (number:--name) passa o número puro.
  • Fontes. font-sans é a fonte do app: todo texto que não define uma família própria é desenhado com ela, como uma página é com o --font-sans do Tailwind. font-mono é a fonte de font-mono e do código em TextView. Quando não definidas, ambas são as fontes do sistema.
  • Ambas as aparências. Um token tem o valor claro e o escuro do tema: bg-primary, theme().primary e token("primary") mudam sozinhos com a aparência, sem a classe dark:. Uma cor que o tema define em apenas uma aparência é usada nas duas.
  • Suas cores do Tailwind prevalecem. Um nome em "tailwind" colors ou na paleta do Tailwind é essa cor, portanto bg-primary é um token de tema quando o tema do Tailwind não tem a cor primary.
  • O tema pode mudar enquanto o app executa. Quando um app embutido em um app nativo recebe o tema desse app, e sempre que o tema muda, as classes que nomeiam tokens, os bindings Rust que leem theme() e o código TSX que lê token() são atualizados, sendo as classes e os bindings de forma nativa. Os tokens que o tema do app hospedeiro não tem mantêm os valores do "theme" do próprio app (e, depois, os do tema padrão). Um host que apenas adiciona sua marca fornece sobrescritas de tema, ou seja, os tokens que sua marca altera, no mesmo formato de arquivo (iOS, Android, AppView::set_theme_overrides nos desktops).
  • Verificadas como as outras classes. Um nome que não é token do tema padrão nem do "theme" do app é uma classe desconhecida na compilação (unknown class 'bg-chart-up'). Para usar um token que só o app hospedeiro fornece, dê a ele um valor também no "theme" do app: esse valor aparece quando o app executa sozinho.

As classes que nomeiam tokens são resolvidas uma vez por tema, como qualquer outra classe: desenhá-las custa o mesmo que uma cor da paleta.

#token() em TSX

token(name) de nsh é o valor de um token na aparência exibida: uma cor como #rrggbbaa, um comprimento como um número de pontos, uma fonte como o nome de sua família. É reativo, então o código que o lê em um componente, em um memo ou em um effect é executado novamente quando o tema ou a aparência muda, o que serve para valores que vão onde nenhuma classe vai, como um canvas ou um gráfico:

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();
  }}
/>

Os nomes são tipados: o NSH escreve os tokens do app em .nsh/types/nsh/tokens.d.ts a partir do tema, de modo que token("up") é um string assim que "up" está no tema, e um nome que o tema não tem é um erro de tipo.

#A verificação de classes

nsh build, nsh dev, nsh run e nsh test verificam os nomes de classe estáticos no seu JSX (literais de string em class e as chaves de classList) com o mesmo parser e o mesmo tema que o runtime usa, e avisam sobre classes que não existem:

 warn nsh::build App.tsx:12:36: unknown class `bg-brand-550`

Os avisos não fazem a compilação falhar. As classes construídas em tempo de execução (bg-${color()}-500) são verificadas quando o runtime as vê pela primeira vez, e ele registra cada classe desconhecida uma única vez (unknown classes: ...). Os métodos tipados são verificados pelo compilador Rust.

#Classes ou estilos?

Ambos são resolvidos nativamente e custam aproximadamente o mesmo. As classes são mais curtas e oferecem variantes (estados pressionados, modo escuro, breakpoints) sem código. Os estilos inline aceitam valores calculados em JavaScript e valores animados nativos (opacity: fade, translate: [x, 0]). A maioria dos apps TSX usa classes para a aparência estática e style para o que é calculado ou animado. Os componentes Rust usam métodos tipados para ambos e vinculam valores animados com .bind_opacity(..), .bind_translate_x(..) e os demais bindings.

#Relacionados

  • Estilização: a prop de estilo, os métodos tipados e os limites de plataforma dos efeitos.
  • Referência do Tailwind: todos os utilitários e variantes.
  • Layout: flexbox e grid, também disponíveis como classes.