Componentes prontos, API consistente

A ElementComponent traz 17 componentes Bootstrap 5 embutidos, todos sob ElementComponent::Components. Eles seguem a mesma API do Element que vimos nos posts anteriores da série: DSL de blocos, add_content encadeável e atributos HTML customizados via **attributes. Nesta primeira leva, vamos ver Alert, Badge, Button, ButtonGroup e Card.

Alert

alert = ElementComponent::Components::Alert.new(variant: :success) do |e|
  e.add_content("Operation completed!")
end
# => <div class="alert alert-success" role="alert">Operation completed!</div>

Variantes: :primary, :secondary, :success, :danger, :warning, :info, :light, :dark.

Alerts dispensáveis (com botão de fechar), títulos e links usam sub-componentes dedicados:

alert = ElementComponent::Components::Alert.new(variant: :warning, dismissible: true) do |e|
  e.add_content(ElementComponent::Components::AlertHeading.new { |h| h.add_content("Warning") })
  e.add_content("Please review. ")
  e.add_content(ElementComponent::Components::AlertLink.new(href: "/details") { |l| l.add_content("Details") })
end
Sub-componente Tag Classe CSS
AlertHeading <h4> .alert-heading
AlertLink <a> .alert-link
AlertCloseButton <button> (autofechado) .btn-close

Button

btn = ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Click") }
# => <button class="btn btn-primary" type="button">Click</button>

btn = ElementComponent::Components::Button.new(variant: :danger, outline: true) { |b| b.add_content("Delete") }
# => <button class="btn btn-outline-danger" type="button">Delete</button>

btn = ElementComponent::Components::Button.new(variant: :primary, href: "/home") { |b| b.add_content("Home") }
# => <a class="btn btn-primary" href="/home">Home</a>

Um detalhe elegante: quando você passa href, o componente renderiza um <a> em vez de <button> automaticamente — o mesmo objeto Ruby, tag diferente conforme o contexto de uso.

Opções: variant (primary/secondary/success/danger/warning/info/light/dark/link), outline, size (sm/lg), href.

Badge

badge = ElementComponent::Components::Badge.new(variant: :primary) { |b| b.add_content("New") }
# => <span class="badge bg-primary">New</span>

pill = ElementComponent::Components::Badge.new(variant: :danger, pill: true) { |b| b.add_content("99+") }
# => <span class="badge bg-danger rounded-pill">99+</span>

Card

O Card é o primeiro componente composto da lista — combina vários sub-componentes para montar a estrutura clássica de card do Bootstrap:

card = ElementComponent::Components::Card.new do |c|
  c.add_content(ElementComponent::Components::CardImage.new(src: "photo.jpg", top: true))
  c.add_content(ElementComponent::Components::CardBody.new do |body|
    body.add_content(ElementComponent::Components::CardTitle.new { |t| t.add_content("Title") })
    body.add_content(ElementComponent::Components::CardText.new { |t| t.add_content("Some text.") })
  end)
  c.add_content(ElementComponent::Components::CardFooter.new { |f| f.add_content("Footer") })
end
Sub-componente Tag Classe CSS
CardHeader <div> .card-header
CardBody <div> .card-body
CardFooter <div> .card-footer
CardTitle <h5> .card-title
CardText <p> .card-text
CardImage <img> (autofechado) .card-img[-top|-bottom]

ButtonGroup

group = ElementComponent::Components::ButtonGroup.new do |g|
  g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Left") })
  g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Middle") })
  g.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Right") })
end
# => <div class="btn-group" role="group">...</div>

Opções: size (sm/lg), vertical.

O padrão que se repete

Um ponto que vale reforçar: componentes compostos (Card, e mais adiante Modal, Navbar, Dropdown) não têm mágica especial — são apenas Elements Bootstrap-aware compostos via add_content, exatamente como qualquer HTML puro montado com a API core. Isso significa que dá pra misturar componentes prontos com Element.new bruto sempre que precisar de algo fora do catálogo.

Próximo post

Na parte 2 da série, seguimos para os componentes de navegação: Nav, Navbar, Breadcrumb e Pagination, além de Progress e Spinner.