Componentes
O Nerdify fornece um sistema de componentes para construir interfaces dinâmicas. Os componentes são definidos no backend (Ruby) e renderizados automaticamente no frontend (Angular).
Tipos de Componentes
O Nerdify suporta os seguintes tipos de componentes:
| Tipo | Descrição |
|---|---|
container | Container genérico para agrupar outros componentes |
fieldset | Visual de fieldset (diferente de fieldset real) |
button | Botão de ação |
text | Texto estático ou dinâmico |
icon | Ícone Material Design |
image | Imagem |
input | Campo de entrada (viewonly ou editável) |
card | Card com estrutura visual |
embed | Incorpora outro recurso/página |
list | Lista de itens |
name | Exibe o nome do recurso |
Container
O container é o componente mais versátil, usado para agrupar e organizar outros componentes.
component :container, size: 12, styles: { padding: { left: 2, right: 2 } } do
component :text, name: :title, type: :h4
component :text, name: :description
end
Opções do Container
| Opção | Tipo | Descrição |
|---|---|---|
size | Integer | Largura em colunas (1-12) |
styles | Hash | Estilos CSS personalizados |
render_if | String | Condição para renderizar (JavaScript) |
click | Hash | Ação ao clicar |
position | Symbol | Posição (:right, :left) |
Exemplo com Layout Complexo
component :container, name: "header", size: 12 do
component :container, size: 6 do
component :text, name: :name, type: :h3
end
component :container, size: 6, position: :right do
component :text, name: :total, styles: { color: "primary" }
end
end
Text
Exibe texto estático (label) ou dinâmico (valor do campo).
# Texto dinâmico (valor do campo)
component :text, name: :customer_name
# Texto estático (label traduzido)
component :text, name: :title, static: true
# Com tipo de heading
component :text, name: :name, type: :h4
Opções do Text
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Nome do campo ou chave de tradução |
type | Symbol | Tipo: :h1 a :h6, :small, :span, :p |
static | Boolean | Se true, exibe o label traduzido |
styles | Hash | Estilos personalizados |
tooltip | Boolean/Symbol | Exibe tooltip com informação adicional |
Estilos Comuns
component :text, name: :value, styles: {
color: "primary", # primary, danger, success, warning, info
font_weight: 5, # 1-9
font_size: 6, # 1-9
nowrap: true, # Não quebra linha
align: { body: "center" } # Alinhamento
}
Icon
Exibe ícones do Material Design.
component :icon, name: :shopping_cart
# Com estilos
component :icon, name: :star, styles: {
color: "warning",
font_size: 4
}
# Ícone outlined
component :icon, name: :info, icon_type: :outlined
Opções do Icon
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Nome do ícone Material |
icon_type | Symbol | :filled (padrão) ou :outlined |
static | Boolean | Se true, ícone fixo sem valor do campo |
styles | Hash | Estilos personalizados |
tooltip | Symbol | Tooltip com tradução do campo |
click | Hash | Ação ao clicar |
Image
Exibe uma imagem.
component :image, name: :photo_url
# Com tamanho personalizado
component :image, name: :avatar, image_size: 4, styles: {
rounded: { top_left: 5, top_right: 5, bottom_left: 5, bottom_right: 5 }
}
Opções da Image
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Campo que contém a URL da imagem |
image_size | Integer | Tamanho da imagem (1-12) |
styles | Hash | Estilos (rounded, padding, etc) |
Input
Exibe um campo de input, geralmente em modo viewonly.
# Campo viewonly
component :input, input_type: :money, name: :total, view: :viewonly, label: false
# Campo select viewonly
component :input, input_type: :select, name: :status, view: :viewonly, layout: :inline
Opções do Input
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Nome do campo |
input_type | Symbol | Tipo: :text, :money, :select, etc |
view | Symbol | :viewonly para apenas visualização |
label | Boolean | Exibe ou oculta o label |
layout | Symbol | :inline para layout horizontal |
size | Integer/Nil | Largura em colunas |
Embed
Incorpora outro recurso ou página dentro do componente atual.
component :embed, name: :pets, path: "/customers/:resource.id/pets", size: 12
# Com detecção de mudanças
component :embed, name: :activities, path: "/activities/:resource.id", detect_changes: true
Opções do Embed
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Identificador do embed |
path | String | Caminho do recurso (suporta interpolação) |
size | Integer | Largura em colunas |
detect_changes | Boolean | Recarrega quando há mudanças |
Interpolação de Path
Use :resource.campo para interpolar valores:
path: "/customers/:resource.id/pets"
path: "/sales/:resource.sale_id/items"
Button
Cria um botão de ação.
component :button, name: :save, click: { submit: "form" }
# Com estilos
component :button, name: :cancel, click: { close: :dialog }, styles: {
background: "transparent",
color: "danger"
}
Opções do Button
| Opção | Tipo | Descrição |
|---|---|---|
name | Symbol | Nome/label do botão |
click | Hash | Ação ao clicar |
styles | Hash | Estilos personalizados |
icon | Symbol | Ícone do botão |
Card
Cria um card com estrutura visual.
component :card, name: :info_card do
component :text, name: :title, type: :h5
component :text, name: :description
end
Estilos Comuns
Cores
styles: { color: "primary" } # Cor do texto
styles: { background: "light" } # Cor de fundo
Valores: primary, secondary, success, danger, warning, info, light, dark, white, transparent
Espaçamento
styles: {
padding: { left: 2, right: 2, top: 1, bottom: 1 },
margin: { left: 1, right: 1 }
}
Valores: 0-5 (unidades de espaçamento Bootstrap)
Bordas
styles: {
border: { top: 1, right: 1, bottom: 1, left: 1 },
rounded: { top_left: 2, top_right: 2, bottom_left: 2, bottom_right: 2 }
}
Alinhamento
styles: {
align: { body: "center" }, # center, left, right
vertical_align: { top: "start" }, # start, center, end
orientation: { body: "vertical" } # vertical, horizontal
}
Outros
styles: {
font_weight: 5, # 1-9
font_size: 6, # 1-9
opacity: 50, # 0-100
nowrap: true, # Não quebra linha
height: 100 # Altura em porcentagem
}
Renderização Condicional
Use render_if para exibir componentes condicionalmente:
# Condição baseada no recurso
component :text, name: :discount, render_if: "resource.has_discount"
# Condição com comparação
component :icon, name: :star, render_if: "resource.stars.length > 0"
# Negação
component :text, name: :empty, render_if: "!resource.items || resource.items.length == 0"
A expressão é avaliada no frontend (JavaScript).
Ações de Click
Navegação
click: { redirect_to: "/customers/:resource.id" }
click: { redirect_to: ":resource.phone_link", target: "_blank" }
Abrir Dialog
click: { redirect_to: "edit", open_in: :dialog }
Fechar Dialog
click: { close: :dialog }
Submit de Formulário
click: { submit: "form_name" }
Requisição HTTP
click: {
put: ":resource.id/approve",
success: { toast: :success, update: "resource" },
error: { toast: :error }
}
Exemplo Completo
fieldset :customer_show, size: 12 do
component :container, size: 3, styles: { border: { right: 1 } } do
component :container, styles: { align: { body: "center" }, padding: { top: 3 } } do
component :image, name: :photo_url, image_size: 4, styles: {
rounded: { top_left: 5, top_right: 5, bottom_left: 5, bottom_right: 5 }
}
component :text, type: :h4, name: :name
component :container do
component :icon, name: :star, static: true, styles: { color: "warning" },
render_if: "resource.stars.length > 0"
component :icon, name: :star, static: true, styles: { color: "info", opacity: 50 },
render_if: "resource.stars.length == 0"
end
component :container do
component :icon, name: :phone, static: true, styles: { background: "light" },
render_if: "resource.phone && resource.phone.length > 0",
click: { redirect_to: ":resource.phone_link", target: "_blank" },
tooltip: :phone
end
end
component :container, size: 12, styles: { border: { top: 1 } } do
component :text, type: :small, name: :total_sales, static: true, styles: { font_weight: 5 }
component :input, input_type: :money, name: :ltv, view: :viewonly, label: false,
styles: { color: "primary" }
end
end
fieldset :customer_tabs, size: 9 do
fieldset :about, tab: :about, size: 12 do
field :name, presence: true
field :email
field :phone
end
fieldset :sales, tab: :sales, size: 12 do
component :embed, name: :sales, path: "/customers/:resource.id/sales", size: 12
end
end
end
Boas Práticas
Faça
- Use
containerpara agrupar componentes relacionados - Aproveite
render_ifpara interfaces dinâmicas - Use
stylesconsistentes em toda a aplicação - Documente componentes complexos
Evite
- Aninhar muitos níveis de containers
- Duplicar estilos - extraia para constantes
- Misturar lógica de negócio nos componentes
- Criar componentes muito genéricos