Componentes de visualização simples para Rails 5.1+, projetados para combinar bem com elemental_styleguide. Os dois juntos são inspirados nas obras de Brad Frost e pelos pensamentos por trás do guia de estilo do Lonely Planet Rizzo.
Adicione esta linha ao GemFile do seu aplicativo:
gem "elemental_components"E depois execute:
$ bundleOs exemplos fornecidos aqui usarão as convenções de nomeação de BEM.
Os componentes vivem em app/components . Gerar um componente executando:
$ bin/rails g elemental_components:component alertIsso criará os seguintes arquivos:
app/
components/
alert/
_alert.html.erb
alert.css
alert.js
alert_component.rb
O gerador também toma --skip-css e --skip-js Opções.
Vamos adicionar alguma marcação e CSS:
<% # app/components/alert/_alert.html.erb %>
< div class =" alert alert--primary " role =" alert " >
Message
</ div > /* app/components/alert/alert.css */
. alert {
padding : 1 rem ;
}
. alert--primary {
background : blue;
}
. alert--success {
background : green;
}
. alert--danger {
background : red;
} Este componente agora pode ser renderizado usando o ajudante component :
<%= component "alert" %> Para exigir ativos como CSS, exigem -os manualmente no manifesto, por exemplo, application.css :
/*
*= require alert/alert
*/ Ou requer components , que por sua vez exigirão os ativos para todos os componentes:
/*
*= require elemental_components
*/Existem duas maneiras de passar dados para componentes: atributos e blocos de conteúdo. Os atributos são úteis para dados como IDs, modificadores e estruturas de dados (modelos etc.). Os blocos de conteúdo são úteis quando você precisa injetar conteúdo HTML em componentes.
Vamos definir alguns atributos para o componente que acabamos de criar:
# app/components/alert_component.rb %>
class AlertComponent < ElementalComponents :: Component
attribute :context
attribute :message
end <% # app/components/alert/_alert.html.erb %>
< div class =" alert alert-- <%= alert.context %> " role =" alert " >
<%= alert.message %>
</ div > <%= component "alert", message: "Something went right!", context: "success" %>
<%= component "alert", message: "Something went wrong!", context: "danger" %> Para injetar algum texto ou conteúdo HTML em nosso componente, podemos imprimir o método do componente. Content em nosso modelo e preencher -o passando um bloco de conteúdo para o ajudante do componente:
<% # app/components/alert/_alert.html.erb %>
< div class =" alert alert-- <%= alert.context %> " role =" alert " >
<%= alert.content %>
</ div > <%= component "alert", context: "success" do %>
< em > Something </ em > went right!
<% end %>Outro bom caso de uso para atributos é quando você tem um componente apoiado por um modelo:
# app/components/comment_component.rb %>
class CommentComponent < ElementalComponents :: Component
attribute :comment
delegate :id ,
:author ,
:body , to : :comment
end <% # app/components/comment/_comment.html.erb %>
< div id =" comment- <%= comment.id %> " class =" comment " >
< div class =" comment__author " >
<%= link_to comment.author.name, author_path(comment.author) %>
</ div >
< div class =" comment__body " >
<%= comment.body %>
</ div >
</ div > <% comments.each do |comment| %>
<%= component "comment", comment: comment %>
<% end %> Atributos podem ter valores padrão:
# app/components/alert_component.rb %>
class AlertComponent < ElementalComponents :: Component
attribute :message
attribute :context , default : "primary"
endÉ fácil substituir um atributo com lógica adicional:
# app/components/alert_component.rb %>
class AlertComponent < ElementalComponents :: Component
attribute :message
attribute :context , default : "primary"
def message
@message . upcase if context == "danger"
end
end Para garantir que seus componentes sejam inicializados corretamente, você pode usar ActiveModel::Validations em seus elementos ou componentes:
# app/components/alert_component.rb %>
class AlertComponent < ElementalComponents :: Component
attribute :label
validates :label , presence : true
end Suas validações serão executadas durante a inicialização dos componentes e aumentam um ActiveModel::ValidationError se alguma validação falhar.
Atributos e blocos são ótimos para componentes ou componentes simples apoiados por uma estrutura de dados, como um modelo. Outros componentes são de natureza mais genérica e podem ser usados em vários contextos. Muitas vezes, eles consistem em várias partes ou elementos, que às vezes se repetem e às vezes precisam de seus próprios modificadores.
Pegue um componente de cartão. No React, uma abordagem comum é criar subcomponentes:
< Card flush = { true } >
< CardHeader centered = { true } >
Header
</ CardHeader >
< CardSection size = "large" >
Section 1
</ CardSection >
< CardSection size = "small" >
Section 2
</ CardSection >
< CardFooter >
Footer
</ CardFooter >
</ Card >Existem dois problemas com esta abordagem:
CardHeader pode ser colocado fora de um Card .CardHeader pode ser colocado abaixo, ou dentro de um CardFooter .Usando esta jóia, o mesmo componente pode ser escrito assim:
# app/components/card_component.rb %>
class CardComponent < ElementalComponents :: Component
attribute :flush , default : false
element :header do
attribute :centered , default : false
end
element :section , multiple : true do
attribute :size
end
element :footer
end <% # app/components/card/_card.html.erb %>
< div class =" card <%= "card--flush" if card.flush %> " >
<% if card.header.content? %>
< div class =" card__header <%= "card__header--centered" if card.header.centered %> " >
<%= card.header.content %>
</ div >
<% end %>
<% card.sections.each do |section| %>
< div class =" card__section <%= "card__section--#{section.size}" %> " >
<%= section.content %>
</ div >
<% end %>
<% if card.footer.content? %>
< div class =" card__footer " >
<%= card.footer.content %>
</ div >
<% end %>
</ div > Os elementos podem ser pensados como subcomponentes isolados e são definidos no componente. A passagem multiple: true o torna um elemento repetido, e a passagem de um bloco permite declarar atributos em nossos elementos, da mesma maneira que declaramos atributos nos componentes.
Para preencher os dados, passamos um bloco para o ajudante de componentes, que produz o componente, que nos permite definir atributos e bloqueios de conteúdo no elemento da mesma maneira que fazemos para os componentes:
<%= component "card", flush: true do |c| %>
<% c.header centered: true do %>
Header
<% end %>
<% c.section size: "large" do %>
Section 1
<% end %>
<% c.section size: "large" do %>
Section 2
<% end %>
<% c.footer do %>
Footer
<% end %>
<% end %> Várias chamadas para um elemento de repetição, como section no exemplo acima, anexarão cada seção a uma matriz.
Outro bom caso de uso é um componente de navegação:
# app/components/navigation_component.rb %>
class NavigationComponent < ElementalComponents :: Component
element :items , multiple : true do
attribute :label
attribute :url
attribute :active , default : false
end
end <%= component "navigation" do |c| %>
<% c.item label: "Home", url: root_path, active: true %>
<% c.item label: "Explore" url: explore_path %>
<% end %> Uma alternativa aqui é passar uma estrutura de dados para o componente como um atributo, se nenhum HTML precisar ser injetado ao renderizar o componente:
<%= component "navigation", items: items %> Os elementos também podem ter validações:
class NavigationComponent < ElementalComponents :: Component
element :items , multiple : true do
attribute :label
attribute :url
attribute :active , default : false
validates :label , presence : true
validates :url , presence : true
end
endOs elementos também podem ser aninhados, embora seja recomendável continuar nidificando ao mínimo:
# app/components/card_component.rb %>
class CardComponent < ElementalComponents :: Component
...
element :section , multiple : true do
attribute :size
element :header
element :footer
end
endAlém de declarar atributos e elementos, também é possível declarar métodos auxiliares. Isso é útil se você preferir manter a lógica fora de seus modelos. Vamos extrair a lógica do modificador do modelo de componente do cartão:
# app/components/card_component.rb %>
class CardComponent < ElementalComponents :: Component
...
def css_classes
css_classes = [ "card" ]
css_classes << "card--flush" if flush
css_classes . join ( " " )
end
end <% # app/components/card/_card.html.erb %>
<%= content_tag :div, class: card.css_classes do %>
...
<% end %>É até possível declarar ajudantes em elementos:
# app/components/card_component.rb %>
class CardComponent < ElementalComponents :: Component
...
element :section , multiple : true do
attribute :size
def css_classes
css_classes = [ "card__section" ]
css_classes << "card__section-- #{ size } " if size
css_classes . join ( " " )
end
end
end <% # app/components/card/_card.html.erb %>
<%= content_tag :div, class: card.css_classes do %>
...
<%= content_tag :div, class: section.css_classes do %>
<%= section %>
<% end %>
...
<% end %> Os métodos auxiliares também podem usar a variável de instância @view para chamar os ajudantes de trilhos, como link_to ou content_tag .
Para alguns pequenos componentes, como botões, pode fazer sentido pular completamente o parcial, a fim de acelerar a renderização. Isso pode ser feito substituindo render no componente:
# app/components/button_component.rb %>
class ButtonComponent < ElementalComponents :: Component
attribute :label
attribute :url
attribute :context
def render
@view . link_to label , url , class : css_classes
end
def css_classes
css_classes = "button"
css_classes << "button-- #{ context } " if context
css_classes . join ( " " )
end
end <%= component "button", label: "Sign up", url: sign_up_path, context: "primary" %>
<%= component "button", label: "Sign in", url: sign_in_path %> Os componentes podem ser aninhados sob um espaço para nome. Isso é útil se você deseja praticar coisas como design atômico, Bemit ou qualquer outro esquema de classificação de componentes. Para criar um componente de espaço para nome, coloque -o em uma pasta e envolva a classe em um módulo:
module Objects
class MediaObject < ElementalComponents :: Component ; end
endEm seguida, chame de um modelo como assim:
<%= component "objects/media_object" %> Esta biblioteca, juntamente com elementar_styleguide, foi inspirada nos escritos de Brad Frost no design atômico e nos guias de estilo de vida, e Rizzo, o Lonely Planet Style Guide. Outras inspirações foram:
Para uma lista de guias de estilo do mundo real, consulte http://styleguides.io.