Photon Design System🌐 Esta página em: English · Português O design system por trás dos componentes write-once: uma especificação completa de tokens/componentes (preservada no repositório em docs/design/Photon-Design-System.dc.html, a fonte da verdade) implementada como tokens C# tipados em eQuantic.UI.Primitives e consumida por todos os alvos de realização. Camadas de tokens (§01–§09)
•
Cores: toda cor é um ColorToken par claro/escuro; componentes nunca guardam cores cruas. Variantes interativas resolvem cinco sub-tokens (Base, OnBase, Pressed, que é um token real e não uma sobreposição, Subtle, OnSubtle). Desabilitado não é uma cor: é um grupo com 38% de opacidade.
•
Escala tipográfica: dirigida por papéis (Display … BodyM, Caption, Label) com clamps de Dynamic Type por papel, mais a ponta densa onde todo chrome de desktop vive: TitleSmall (15/700, o degrau entre Label 13 e Title 20), LabelSmall (11, para trilhos de status, leituras de inspetor, legendas de espécime) e Overline (10/800, com tracking, a sobrancelha em maiúsculas acima de um grupo). Uma escala que para em 12dp é uma escala de toque; uma sidebar, uma toolbar e uma barra de status vivem todas abaixo dela, e sem esses degraus cada uma sai um quinto grande demais. Um estilo também carrega sua face: TypeStyle.Mono seleciona a monoespaçada (a do próprio sistema, SF Mono num Mac), e TypeStyle.Italic o corte inclinado (desde a 0.2.0-preview.28 é um EIXO, então negrito itálico e código itálico existem os dois), que o medidor, o rasterizador e o cache de raster honram porque já chaveiam pelo estilo.
•
Caixas de linha seguem o tamanho: TypeStyle.WithSize(size) escala a altura de linha na mesma proporção (o line-height sem unidade do CSS), e TypeStyle.OfSize(size, weight) deriva uma no 1.25× tipográfico. Remendar só o tamanho deixa um glifo maior na caixa antiga, e é assim que uma descendente termina fora da linha dela.
•
Espaçamento (Space.S1–S16, base 4dp, pertencente ao gap, margem não existe), raio (Radius.Xs–Full, clampado pelo engine), tamanhos de ícone (whitelist §07 16/20/24/32; tamanhos arbitrários lançam), toque (contrato de alvo ≥48dp), elevação (uma sombra analítica por nó), movimento (durações + curvas + mola).
Densidade: o mesmo botão, 32dp sob um polegar e 26dp sob um mouse
Density (Comfortable | Compact) é uma propriedade do alvo, nunca do local da chamada: um app nunca pede um botão menor no Mac; o Mac é que pede um app mais denso. É a densidade do Material e o control size da Apple, e é o que mantém UMA árvore honesta num telefone e num desktop.
Sizing.Height, PaddingX, LabelSize e HitTarget resolvem por ela (Small 32→26, Medium 40→32, Large 48→40, XLarge 56→48), e a escada de seleção também (SwitchWidth/SwitchHeight/ SwitchThumb/SwitchTravel, SelectionBox, RadioDot). Sob um ponteiro o alvo de toque para de inflar: o mínimo do §08 é um contrato de dedo, e numa toolbar de botões de 26dp aquelas margens invisíveis se sobreporiam umas às outras.
Ela chega aos componentes por ComponentContext.Density, a mesma porta pela qual o tema chega, então nenhuma assinatura de Build mudou. Quem decide é o alvo: o shell do macOS diz Compact; o boot do web lê (pointer: fine).
A ESCADA VIVE NA CAMADA DE TOKENS, SEMPRE Um componente nunca guarda uma cópia privada da própria tabela de tamanhos, porque uma cópia não consegue seguir a densidade que o alvo pede. Sizing.Avatar(size) e os degraus de seleção existem para que quem chama leia o mesmo número que o componente desenha.
Fidelidade à spec é testada, não desejada
•
Valores de token são pinados; o contraste WCAG é recalculado nos testes para cada par afirmado.
•
Métricas de componente vêm das tabelas da spec (ex.: a tabela de tamanhos do Button: alturas 32/40/48/56) e são afirmadas em todos os eixos: pins do realizer web em C#, imagens golden nativas (claro + escuro), execução de fixture transpilada no vitest.
•
As regras do resolvedor de estilo (variantes derivadas Outline/Ghost/Link, pressed como troca de token, anel duplo de foco) vivem em C# neutro de alvo (ButtonStyles).
A regra "gerado, nunca escrito à mão"
Artefatos client-side que carregam valores do design system são gerados da fonte única em C# e pinados byte a byte na CI (um desvio falha o build; fluxos por variável de ambiente regeneram):
Artefato
Gerador
Regenerar com
Stylesheet normativo (custom properties, .eq-type-*, .eq-elevation-*, vars de motion)
PhotonCssGenerator
(função pura, testada por valor)
design-system.generated.ts (tokens, objeto de tema, tabela de tamanhos do Button)
DesignSystemTsGenerator
EQ_UPDATE_DESIGN_TS=1
icons.generated.ts (dados de path dos glifos)
IconTsGenerator
EQ_UPDATE_ICONS_TS=1
Módulos de componente transpilados embarcados
saída viva do eqc
EQ_UPDATE_TRANSPILED=1
A mesma regra sustenta a hidratação: o SSR (realizer C#) e o lowering do cliente (TS) são mantidos byte-idênticos por literais cross-pinadas nas duas suítes de teste.
Pipeline de ícones (spec A10)
Um enum Icons curado (23 glifos e crescendo: Menu, o gatilho de navegação do shell compacto, chegou na 0.2.0-preview.26) com os dados de path vivendo uma vez no IconRegistry em C# (máscaras alpha de path única 24×24): o web emite <svg fill="currentColor"> inline (o tom monta no token de cor exatamente como o texto faz), o lado TS consome o módulo gerado, e o futuro atlas de glifos nativo rasteriza do mesmo registro. Outline ↔ preenchido são glifos distintos; ícones ignoram Dynamic Type.
Estilo sem CSS: o engine atômico
A autoria é 100% C# tipado (BoxStyle, StyleDiff, tokens), e não existe um plano CSS. Três leis governam a realização web:
1.
Uma semântica: o mesmo vocabulário de estilo dirige valores dp no Photon e CSS no web.
2.
Sem teto de expressividade: Wrap, Grid, AspectRatio, AlignSelf, Opacity de grupo, Transform2D estático, diffs de estado Hover/Focus, adaptatividade por classe de tamanho de janela (AdaptiveNode), Sticky, ZIndex, scroll nos dois eixos.
3.
Sem reescrita por construção: toda declaração regular desce para UMA classe atômica deduplicada (eq- + hash FNV-1a de prop:valor); cores de tema referenciam var(--eq-color-*); o SSR junta as regras da página num único <style id="eq-atomic"> que o cliente adota por identidade (hidratação = igualdade de classe, garantida de ponta a ponta pelo teste e2e de identidade). ~118 regras / 4,8 KB cobriram o showroom inteiro.
Pseudo-estados descem para regras atômicas de pseudo-variante (:hover/:focus-visible, zero JS); classes de tamanho para classes fixas com media gate. O StyleAtomizer em C# e o style-atomizer.ts em TS são gêmeos byte-idênticos (fixture cross-pinada).
Tema = fornecer um IAppTheme
Uma interface (cores como pares ColorToken claro/escuro, papéis de tipo, escala de forma, specs de elevação, opacidade de desabilitado), selecionada via AddUI(...).UseTheme(...) e transportada do SSR para o cliente.
Trocar claro/escuro em tempo de execução é um SERVIÇO: IThemeController (Mode + Apply) é tomado por construtor como qualquer capacidade, e o componente nunca aprende qual alvo respondeu. O runner nativo repinta a janela contra a outra paleta; o browser vira uma declaração color-scheme (a paleta web inteira é autorada como pares light-dark(), então a troca não custa re-render). ITextClipboard é oferecido do mesmo jeito para o botão de copiar de uma página. Duas implementações acompanham: PhotonTheme (o padrão) e MaterialTheme (Material 3 de verdade: baseline MaterialTheme.Instance ou cor dinâmica FromSeed(cor) via um port CAM16/HCT validado contra os valores do Google). A forma é dirigida pelo tema; a mesma árvore de componentes se rebrandiza com uma linha.