gbr/ui/theme
💎✨ GBR: UI Theme Module
🤺 theme.gleam = Vocabulário Visual e Design Token Algébrico.
Aqui temos um módulo muito especial cheio de tipos algébricos para representarmos matematicamente o mundo externo e como manipulamos o tema visual dos nossos componentes.
Aqui iremos encontrar as variantes do tema, a aparência dos componentes, o estado em que eles estão, seu tamanho, etc.
IDEIA: Que esta biblioteca e vocabulário sejam universais para desenvolver componentes UI para qualquer interface
Objetivos
- Utilizar o mesmo vocabulário para web, mobile, desktop, etc.
- Utilizar as mesmas funções
corepara web, mobile, etc. - Transportar o estado da UI sem comprometer a experiência de quem está visualizando os componentes no dispositivo.
- Ter tipos algébricos puros (ADT) que possibilitem desenvolver componentes visuais e uma experiência rica para quem está visualizando no dispositivo.
Arquitetura: Type-Safe Styled Systems
CVA (Class Variance Authority)
- UIVariant (Identidade): Responde à pergunta “Qual é o propósito dessa peça na interface?”. É a ação principal? É um aviso? É uma ação destrutiva? A identidade não muda se o usuário mexer o mouse.
- UIAppearance (Aparência) dita como a “tinta” é aplicada no componente
- Filled: Fundo pintado, texto branco/contraste.
- Light (ou Soft): Fundo bem clarinho, texto escuro.
- Ghost: Sem fundo, com borda. (Alguns chamam de Outlined).
- UIState (Interação): Responde à pergunta “O que o usuário (ou a rede) está fazendo com essa peça AGORA?”. Ele está com o mouse em cima? Ele clicou? A rede está lenta e está carregando? O botão foi desativado?
🏆 Meta final para o theme.gleam
Se transformar em um motor gráfico capaz de descrever QUALQUER componente de interface no planeta. Estrutura final da nossa ontologia:
- O Espaço (Geometria): UISize e UIShape
- A Alma (Semântica): UIVariant
- A Pintura (Material): UIAppearance
- A Luz e A Física: UIElevation e UIStacking
- A Posição: UIDirection
- O Tempo: UIState
- A Herança: UIAncestor
Teremos 8 dimensões base para representarmos visualmente um componente na interface do dispositivo.
A ordem no código para construir um elemento do zero até a pintura final:
Estrutura Base (Invisível): Display (flex, grid), alinhamento, transições (transition-all).
- Dimensão 1 - Size (Espaço): padding, height, text-size. (Cria a caixa).
- Dimensão 2 - Shape (Forma): border-radius. (Molda a caixa).
- Dimensão 3 - Elevation (Física): shadow, z-index. (Realça a caixa).
- Dimensão 4 - Designs (Identidade): Fusão de Semântica + Pintura + Estado. Elas não podem ser calculadas separadamente. A Cor (UIVariant) depende do preenchimento (UIAppearance) que reage a um determinado estado (UIState).
Regra da Propriedade do CSS
- Componente é dono de si mesmo: Ele dita o seu próprio padding,
background, text-color e border-radius. Se o usuário quer um botão menor,
ele deve usar a ADT
theme.SizeSm. Se a ADT não atende, ele deve construir o botão usando usando o componente headless (core). - Usuário é dono do espaço exterior (DOM): O argumento
attributesserve EXCLUSIVAMENTE para injetar:- Margens: mt-4, mb-2 (porque o botão não sabe se ele está perto ou longe de outro elemento).
- Posicionamento: absolute, z-index.
- Metadados do DOM: id=“meu-botao”, aria-label, data-testid.
- Eventos extras: on_mouse_enter, on_blur.
🔥 O Cálculo da Trindade (O Coração da Pintura)
A “Fusão” da pintura acontece cruzando as 3 dimensões:
- UIVariant (Cor) x UIAppearance (Preenchimento) x UIState (tempo):
- Matemática: 9 (Variantes) * 8 (Aparências) * 7 (Estados)
- Total: 504 combinações visuais únicas!
✨ A Magia do Gleam: Graças ao curinga (_), você não precisa escrever
504 blocos de regras em CSS puro. Você mapeia apenas os 10 ou 15 caminhos
felizes que o seu design aprova, e usa o _, _, _ -> fallback(...) para
devorar as outras combinações impossíveis/indesejadas em uma linha só!
🌌 O Cálculo do Universo (As 8 Dimensões)
Se nós pegarmos um único elemento genérico (como um div atômico) e
permitirmos que o desenvolvedor configure livremente as 8 dimensões, qual
será o tamanho da nossa “Ontologia de UI”?
- Matemática: 9 * 8 * 7 * 7 * 5 * 6 * 8 * 6
- Total Exato: 5.080.320 de estados possíveis.
Mais de 5 MILHÕES de formas de desenhar um componente! 🤯
Explicando o sufixo Default e Ancestor
Para todos tipos de tema, inclusive os (size, shape, elevation, stacking),
temos dois sufixos importantes Ancestor e Default, segue um exemplo
usando o UIVariant:
- O VariantAncestor (A Herança): Ele significa “Eu não tenho cor própria, olhe para o meu pai e faça o que ele mandar (ou padrão do dispositivo)” (no CSS, isso é o inherit ou o currentColor).
- O VariantDefault (O Reset/Neutro): Ele significa “Eu quero a cor padrão original deste componente, não importa onde eu esteja”.
Types
Direção de um elemento esquerda, direita, etc.
pub type UIAbsolute {
Axis(horizontal: UIAlignment, vertical: UIAlignment)
AxisX(UIAlignment)
AxisY(UIAlignment)
}
Constructors
-
Axis(horizontal: UIAlignment, vertical: UIAlignment) -
AxisX(UIAlignment) -
AxisY(UIAlignment)
Representa as opções de alinhamento em um eixo genérico
pub type UIAlignment {
Start
End
Center
SpaceBetween
SpaceAround
SpaceEvenly
Stretch
}
Constructors
-
Starte.g. flex-start
-
Ende.g. flex-end
-
Centere.g. center
-
SpaceBetweene.g. space-between
-
SpaceArounde.g. space-around
-
SpaceEvenlye.g. space-evenly
-
Stretche.g. stretch
Aparência de um elemento o seu estilo.
pub type UIAppearance {
AppearanceDefault
AppearanceFilled
AppearanceGhost
AppearanceLight
AppearanceOutline
}
Constructors
-
AppearanceDefault -
AppearanceFilledApresentam fundo de cor sólida, ideal para ações primárias devido à alta visibilidade.
-
AppearanceGhostTenha um fundo transparente sem borda e com rótulo de texto. Eles são adequados para ações secundárias, pois são menos proeminentes visualmente do que a aparencia sólida.
-
AppearanceLightAo sobrepor várias sombras desfocadas com cores brilhantes, você pode criar um efeito luminoso
-
AppearanceOutlineTenha um fundo transparente com borda e rótulo de texto. Eles são adequados para ações secundárias, pois são menos proeminentes visualmente do que a aparencia sólida.
Como controlar a sensação de elevação dos elementos. (sombra)
pub type UIElevation {
ElevationFlat(option.Option(#(UISize, UILayout)))
ElevationInner(option.Option(#(UISize, UILayout)))
ElevationThin(option.Option(#(UISize, UILayout)))
ElevationLow(option.Option(#(UISize, UILayout)))
ElevationMedium(option.Option(#(UISize, UILayout)))
ElevationHigh(option.Option(#(UISize, UILayout)))
}
Constructors
-
ElevationFlat(option.Option(#(UISize, UILayout)))Grudado no chão (Sem sombra)
-
ElevationInner(option.Option(#(UISize, UILayout)))Afundado (Sombra interna, útil para inputs)
-
ElevationThin(option.Option(#(UISize, UILayout)))Ultra fino (Botões, Badges, etc)
-
ElevationLow(option.Option(#(UISize, UILayout)))Levemente levantado (Cards, Dropdowns sutis)
-
ElevationMedium(option.Option(#(UISize, UILayout)))Flutuando (Modais, Menus flutuantes)
-
ElevationHigh(option.Option(#(UISize, UILayout)))Voando alto (Tooltips, Notificações Toast)
Representa a união de justify-content (main) e align-content (cross).
pub type UIFlow {
Main(justify: UIAlignment)
CrossItems(align: UIAlignment)
CrossContent(align: UIAlignment)
Flow(
main: UIAlignment,
cross_content: UIAlignment,
cross_items: UIAlignment,
)
FlowItems(main: UIAlignment, cross_items: UIAlignment)
FlowContent(main: UIAlignment, cross_content: UIAlignment)
}
Constructors
-
Main(justify: UIAlignment)Layout de fluxo principal referencia ao justify-*.
-
CrossItems(align: UIAlignment)Layout de fluxo principal referencia ao items-*.
-
CrossContent(align: UIAlignment)Layout de fluxo principal referencia ao content-*.
-
Flow( main: UIAlignment, cross_content: UIAlignment, cross_items: UIAlignment, )Layout de fluxo referenciando o eixo main, cross content e cross items.
-
FlowItems(main: UIAlignment, cross_items: UIAlignment)Layout de fluxo referenciando o eixo main e cross items.
-
FlowContent(main: UIAlignment, cross_content: UIAlignment)Layout de fluxo referenciando o eixo main e cross content.
Define a estratégia de posicionamento no layout.
pub type UILayout {
LayoutFlow(UIFlow)
LayoutAbsolute(UIAbsolute)
}
Constructors
-
LayoutFlow(UIFlow) -
LayoutAbsolute(UIAbsolute)
Formato da superfície de um elemento.
O “quão redondo” é o elemento não depende do tamanho
pub type UIShape {
Shape(#(UISize, UILayout))
ShapeRounded
ShapePill
ShapeCircle
ShapeSharp
}
Constructors
-
Arredondamento
-
ShapeRoundedBordas arredondadas perfeito para botões
-
ShapePillBordas totalmente arredondadas (Design iOS/Mobile)
-
ShapeCircleCírculo perfeito (Para avatares e icon_only)
-
ShapeSharpQuadrado perfeito (0px radius)
Escala do tamanho de um elemento.
- Altura, Largura, Fonte e Espaçamento Interno (Padding).
pub type UISize {
SizeXxl
SizeXl
SizeLg
SizeMd
SizeSm
SizeXs
SizeXxs
}
Constructors
-
SizeXxl2xl
-
SizeXlxl
-
SizeLglg
-
SizeMdmd
-
SizeSmsm
-
SizeXsxs
-
SizeXxs2xs
Controlar o empilhamento dos elementos no eixo Z.
pub type UIStacking {
StackBase
StackFloat
StackSticky
StackDropdown
StackOverlay
StackModal
StackToast
StackTooltip
}
Constructors
-
StackBasez-0
-
StackFloatz-10
-
StackStickyz-20
-
StackDropdownz-30
-
StackOverlayz-40
-
StackModalz-50
-
StackToastz-60
-
StackTooltipz-70
Estado de um elemento.
pub type UIState {
StateIdle
StateLoading
StateDisabled
StatePressed
}
Constructors
-
StateIdleIntocado ou parado (Padrão)
-
StateLoadingAguardando processamento
-
StateDisabledDesligado ou não acessível
-
StatePressedSendo precionado
Dados para construir um tema a partir dos tipos de tema, os design tokens.
- painter: Dados do pintor do tema, os design tokens em ADTs.
- builder: Dados do motor para converter os tipos Gleam em design tokens específicos para a interface visual utilizada.
Exemplo
Abaixo temos um código utilizando o sistema de tipos Gleam para representar
o tema de um elemento HTML <div>. Utilizamos como estrutura de dados para
nossos design tokens finais, uma tupla List(#(String, True)), compatível
com a função lustre attribute.classes(), que aplica os tokens tailwind
import lustre/attribute as a
import lustre/element/html as h
import gbr/ui/theme
pub fn main() {
let builder_variant = fn (variant) {
[
#("bg-amber-700", theme.is_primary(variant)),
#("bg-gray-500", theme.is_not_primary(variant)),
]
}
theme.new()
|> theme.with_variant(theme.primary())
|> theme.with_builder_variant(builder_variant)
|> theme.view(fn (tokens) {
h.div([a.classes(tokens)], [h.text("Olá mundo temático!")])
})
}
tokens: Representar os tokens finais, possibilita ser qualquer estrutura de dados, é um tipo genérico.
pub opaque type UITheme(tokens)
Variante semântica, conhecido como tema, de um elemento.
pub type UIVariant {
VariantDefault
VariantPrimary
VariantSecondary
VariantTertiary
VariantSuccess
VariantWarning
VariantError
VariantInfo
}
Constructors
-
VariantDefault -
VariantPrimaryA variante principal do tema.
-
VariantSecondaryA variante secundaria do tema.
-
VariantTertiaryA variante de fallback do tema.
-
VariantSuccessA variante de sucesso do tema.
-
VariantWarningA variante de alerta do tema.
-
VariantErrorA variante de erro do tema.
-
VariantInfoA variante de info do tema.
Values
pub fn alignment_rotate(align: UIAlignment) -> UIAlignment
Rotaciona o alinhamento de um tema de layout. (rotate-180)
pub fn filled() -> UIAppearance
pub fn ghost() -> UIAppearance
pub fn is_not_primary(variant: UIVariant) -> Bool
pub fn is_primary(variant: UIVariant) -> Bool
pub fn layout_absolute(axis: UIAbsolute) -> UILayout
pub fn layout_absolute_axis(
horizontal: UIAlignment,
vertical: UIAlignment,
) -> UILayout
pub fn layout_flow_content(
main: UIAlignment,
cross_content: UIAlignment,
) -> UILayout
pub fn layout_flow_cross_content(main: UIAlignment) -> UILayout
pub fn layout_flow_cross_items(main: UIAlignment) -> UILayout
pub fn layout_flow_items(
main: UIAlignment,
cross_items: UIAlignment,
) -> UILayout
pub fn layout_flow_main(main: UIAlignment) -> UILayout
pub fn light() -> UIAppearance
pub fn new() -> UITheme(tokens)
NOVO THEME BUILDER
Criar novo tema e motor de elementos visuais estilizados.
- theme: Cria um tema padrão.
- builder: Cria um construtor de temas padrão, uma lista do tipo genérico.
pub fn paint(theme: UITheme(token)) -> List(token)
PAINT THEME
Converte o tema em tokens de design, utilizando o construtor de tokens.
- theme: O tema que será convertido.
- builder: O construtor de tokens que será utilizado.
- with: O tema base que será utilizado.
pub fn rounded_absolute(
size: UISize,
absolute: UIAbsolute,
) -> UIShape
pub fn shape_circle() -> UIShape
pub fn shape_pill() -> UIShape
pub fn shape_rounded() -> UIShape
pub fn shape_sharp() -> UIShape
pub fn view(
apply theme: UITheme(token),
in to_element: fn(List(token)) -> a,
) -> a
Construtor de uma visualização de um elemento injetado, aplicando o tema passado como argumento da função e a base dos tokens do estilo do elemento.
- theme: Os dados do tema a ser aplicado ao elemento injetado.
- build: Os dados de como construir os design tokens a partir do tema.
- with: Base de estilos, design tokens, para ser aplicado ao elemento.
- to: Função para injetar o construtor de um elemento visual genérico.
a: Tipo fantasma que representa o elemento sendo criado e estilizado.
pub fn with_appearance(
theme: UITheme(tokens),
appearance appearance: UIAppearance,
) -> UITheme(tokens)
pub fn with_base_to_tokens(
theme: UITheme(token),
base_to_tokens: fn() -> List(token),
) -> UITheme(token)
Converte para tokens iniciais, padrão, de estilização.
pub fn with_design(
theme: UITheme(tokens),
variant variant: UIVariant,
appearance appearance: UIAppearance,
state state: UIState,
) -> UITheme(tokens)
pub fn with_design_to_tokens(
theme: UITheme(token),
design_to_tokens: fn(UIVariant, UIAppearance, UIState) -> List(
token,
),
) -> UITheme(token)
Converte uma variante do tema em tokens.
pub fn with_elevation(
theme: UITheme(tokens),
elevation: option.Option(UIElevation),
) -> UITheme(tokens)
pub fn with_elevation_default(
theme: UITheme(token),
elevation: UIElevation,
) -> UITheme(token)
Elevação padrão, caso o tema não contenha um determinado.
pub fn with_elevation_to_tokens(
theme: UITheme(token),
elevation_to_tokens: fn(UIElevation) -> List(token),
) -> UITheme(token)
Converte uma elevação do tema em tokens.
pub fn with_layout(
theme: UITheme(tokens),
layout layout: option.Option(UILayout),
) -> UITheme(tokens)
pub fn with_layout_to_tokens(
theme: UITheme(token),
layout_to_tokens: fn(UILayout) -> List(token),
) -> UITheme(token)
Converte um tamanho do tema em tokens.
pub fn with_shape(
theme: UITheme(tokens),
shape shape: option.Option(UIShape),
) -> UITheme(tokens)
pub fn with_shape_default(
theme: UITheme(token),
shape: UIShape,
) -> UITheme(token)
Superfície padrão, caso o tema não contenha um determinado.
pub fn with_shape_to_tokens(
theme: UITheme(token),
shape_to_tokens: fn(UIShape) -> List(token),
) -> UITheme(token)
Converte uma superfície visual do tema em tokens.
pub fn with_size(
theme: UITheme(tokens),
size size: option.Option(UISize),
) -> UITheme(tokens)
pub fn with_size_default(
theme: UITheme(token),
size: UISize,
) -> UITheme(token)
Tamanho padrão, caso o tema não contenha um tamanho determinado.
pub fn with_size_to_tokens(
theme: UITheme(token),
size_to_tokens: fn(UISize) -> List(token),
) -> UITheme(token)
Converte um tamanho do tema em tokens.
pub fn with_stacking(
theme: UITheme(tokens),
stacking stacking: option.Option(UIStacking),
) -> UITheme(tokens)
pub fn with_stacking_default(
theme: UITheme(token),
stacking: UIStacking,
) -> UITheme(token)
Empilhamento padrão, caso o tema não contenha um determinado.
pub fn with_stacking_to_tokens(
theme: UITheme(token),
stacking_to_tokens: fn(UIStacking) -> List(token),
) -> UITheme(token)
Converte uma pilha visual do tema em tokens.
pub fn without_elevation(
theme: UITheme(tokens),
) -> UITheme(tokens)
Remove a elevação do elemento