Skip to content

Styling pt BR

Edgar Mesquita edited this page Aug 14, 2026 · 3 revisions

Arquitetura de estilo do eQuantic.UI

🌐 Esta página em: English · Português

Filosofia central

O eQuantic.UI entrega uma experiência de desenvolvimento (DX) inspirada no Flutter enquanto aproveita a força inteira de cada alvo de renderização. A arquitetura de estilo se apoia em três pilares:

  1. Abstração: componentes autoram o que estilizar, como valores C# tipados, nunca strings CSS.
  2. Uma semântica, dois alvos: o mesmo vocabulário de estilo comanda valores em dp no nativo (Photon) e CSS no web.
  3. Performance: geração e deduplicação de classes em tempo de build, sem custo de runtime CSS-in-JS.

1. Estilizando componentes: C# tipado, sem plano CSS

O estilo de um componente é autorado inteiramente em C# tipado (BoxStyle, StyleDiff, tokens de design) e rebaixado pelo motor de estilo atômico: cada declaração comum vira UMA classe atômica deduplicada, idêntica byte a byte entre o SSR (C#) e a hidratação (TS); cores de tema referenciam as custom properties var(--eq-color-*). Estados de hover/foco, adaptação por classe de tamanho de janela, posicionamento sticky e transforms fazem todos parte do vocabulário, com zero JavaScript envolvido.

O hover é derivado, e é só para ponteiro

Desde 0.2.0-preview.24

A paleta de variante continua com cinco sub-tokens; o hover resolve por DERIVAÇÃO: o VariantColors.Hover é o ponto médio canal a canal de BasePressed (ColorToken.MidpointWith, a mesma cor onde color-mix(in srgb, Base 50%, Pressed) cai), e variantes discretas fazem hover em SurfaceSubtle. Componentes autoram isso como um diff comum de BoxStyle.Hover, então os dois realizadores já sabiam pintá-lo: o web como uma regra atômica :hover, o Photon pelo pipeline de ponteiro.

Duas regras acompanham a própria emissão. Toda regra atômica :hover é embrulhada em @media (hover: hover), para que o hover emulado e grudento de um browser de toque não ache regra nenhuma, e a cerca embrulha a regra, nunca o hash, então SSR e hidratação continuam comparando strings de classe idênticas. E no Photon, o hover é rastreado por CAMINHO de layout, como uma CADEIA de tudo que está sob o ponteiro, porque o :hover do CSS casa com todo ancestral: passar sobre o gatilho de um tooltip também faz hover no tooltip, e uma reconstrução de componente, que troca cada instância de nó, deixou de perder o hover que ela mesma repintou.

O que fica por cima

Desde 0.2.0-preview.23

O Photon pinta em ORDEM DE FILHO: um irmão posterior fica por cima, e não é preciso dizer nada. O CSS para de funcionar assim no momento em que caixas adquirem contextos de empilhamento, então o realizador web soletra o que o nativo dá de graça, nas palavras do design system, nunca nas do CSS.

Elevation decide o que está acima, não só quão profunda é a sombra. Ela desenhava uma sombra e nada mais, o que é meia frase: uma superfície elevada que qualquer coisa pintada depois cobre não está elevada. Os níveis 1–5 são o plano de CONTEÚDO, e são os únicos números que um autor escreve.

Chrome é um plano, não um cartão elevado. Qualquer coisa fixada (um cabeçalho Sticky, flutuante ou no fluxo) fica acima de toda elevação de conteúdo. Antes ela ficava um passo acima de nada, o que é um número competindo com outros números: dê à elevação a faixa 1–5 e um cartão meramente elevado passaria a empilhar acima do cabeçalho e rolaria por cima dele.

É por isso que Sticky não tem ZIndex e nunca vai ter. Um z-index é vocabulário CSS, e um autor que precisa escolher um está sendo obrigado a saber quais números todo o resto escolheu. A elevação diz quão alto, o realizador decide como isso se soletra, e chrome não está na mesma escala que conteúdo.

Uma exceção ao uma declaração, uma classe, e ela deixa de ser exceção assim que é nomeada: um prefixo de fabricante não é uma declaração própria, é a mesma escrita para outro motor. O par divide uma classe cuja regra nomeia os dois, com a propriedade padrão por último. Sozinho numa regra, o -webkit-backdrop-filter é descartado inteiro por um motor que só aceita o nome padrão (o insertRule deixa uma regra VAZIA no Chromium, medido), então a classe era computada, hasheada, emitida, posta no elemento, e não fazia nada. Desde 0.2.0-preview.21.

A face de código

Desde 0.2.0-preview.7

Um trecho monoespaçado (Text com Mono, um trecho rico, um CodeBlock) desenha numa pilha que o framework possui, e ela cai numa classe atômica, que vence uma regra body { font-family }, e cujo nome é um hash de conteúdo que muda entre builds. Então não havia onde pendurar a sua própria face de código. O gancho é uma variável:

:root { --eq-font-mono: 'JetBrains Mono', ui-monospace, monospace; }

Todo trecho mono a segue, com a pilha da plataforma como fallback quando ninguém a define.

O medidor lê a mesma variável, e isso não é um detalhe de implementação que dá para ignorar se você publica um editor de código: o CodeEditor posiciona o cursor a partir de um avanço de coluna medido, então medir numa face e desenhar em outra põe o cursor ao lado do caractere em que ele está, em vez de embaixo dele.

O motor completo (leis, pseudo-estados, classes de tamanho, a folha de estilo gerada) está documentado em DesignSystem. O vocabulário abstrato em si (Box, Row/Column, Grid, Stack, …) está documentado em Componentes write-once.

Tematizar = fornecer um IAppTheme

Temas também são C# tipado (cores como pares ColorToken claro/escuro, papéis de tipo, escala de forma, elevação) selecionados numa linha e ponteados do SSR para o cliente:

builder.Services.AddUI(options =>
{
    options.ScanAssembly(typeof(Program).Assembly)
           .UseTheme(PhotonTheme.Instance);          // ou MaterialTheme.FromSeed(color)
});

Duas implementações vêm na caixa: PhotonTheme (o padrão) e MaterialTheme (Material 3 de verdade, incluindo cor dinâmica a partir de uma semente). Troque o tema e todo componente se rebranda, servidor e cliente, web e nativo. A troca claro/escuro em tempo de execução é um serviço (IThemeController); veja DesignSystem.


Uma borda em algumas arestas

Desde 0.2.0-preview.13

new BoxStyle
{
    BorderWidth = 1,
    BorderColor = theme.Border,
    BorderSides = BorderSides.Top,          // um filete acima de uma seção
}

new BoxStyle
{
    BorderWidth = 3,
    BorderColor = theme.Colors(Variant.Primary).Base,
    BorderSides = BorderSides.Start,        // uma barra de acento ao lado de um destaque
}

BorderSides.All por padrão, então toda caixa escrita antes disso renderiza byte por byte igual: uma borda completa continua emitindo o atalho de uma declaração e a sua classe atômica única.

Nenhuma dessas é um Divider. Um Divider é um irmão entre duas coisas; estas pertencem à própria caixa, e o único jeito de desenhar uma era um Row embrulhando um Box de um dp, o que é uma mentira de layout sobre o que o design queria dizer.

Start e End em vez de Left e Right, casando com EdgeInsets: eles espelham numa leitura da direita para a esquerda, e uma barra de acento que fica à esquerda quando o texto flui para o outro lado está do lado errado dele.

Cerca. Com raio de canto, uma borda parcial difere levemente entre os alvos no canto onde uma aresta presente encontra uma ausente: o web faz meia-esquadria, o Photon deixa quadrado. Em raio 0 (um filete, uma barra de acento, uma célula de tabela, que é para o que bordas parciais servem) os dois são idênticos. Use BorderSides.All para um contorno arredondado.

2. A saída de emergência do HTML

A camada web de baixo nível (eQuantic.UI.Core) espelha o DOM 1:1. Todo HtmlElement expõe os atributos que o browser tem:

public abstract class HtmlElement : IComponent {
    /// <summary>Classes CSS cruas (separadas por espaço).</summary>
    public string? ClassName { get; set; }

    /// <summary>Estilos inline para valores dinâmicos (ex.: coordenadas, cores vindas do banco).</summary>
    public HtmlStyle? Style { get; set; }
}

Esse contrato significa que a camada web não impõe nenhum framework CSS, ela renderiza atributos HTML. O DynamicElement vai além e renderiza qualquer tag com quaisquer atributos. Classes CSS padrão de qualquer folha de estilo funcionam nativamente:

new DynamicElement("aside") { ClassName = "my-sidebar" }

3. Trazendo CSS externo

O framework publica exatamente um motor de estilo, o pipeline C# tipado acima. Não há framework CSS embutido nem camada de adaptação: uma página que precisa de markup escrito à mão usa a saída de emergência (HtmlElement/DynamicElement + ClassName), e qualquer folha de estilo que o app traga (o CSS dele, um framework utilitário que ele mesmo constrói) é referenciada como em qualquer projeto web:

builder.Services.AddUI(options =>
{
    options.ConfigureHtmlShell(shell => shell.AddStylesheet("/css/app.css"));
});

O framework não gera nem processa esse CSS; é assunto do build do próprio app.

4. Papéis dos pacotes

Pacote Propósito
eQuantic.UI.Primitives Estilos tipados, tokens de design, o vocabulário abstrato: a fonte da verdade do estilo.
eQuantic.UI.Core A camada espelho do DOM: HtmlElement, ClassName, HtmlStyle, DynamicElement.
eQuantic.UI.Web O realizador web + o motor de estilo atômico (StyleAtomizer) + a folha de estilo gerada.

Clone this wiki locally