Fechando a série
Este é o último post da série sobre a ElementComponent. Depois de cobrir a API core, hooks/cache/Rails e os componentes de conteúdo e navegação, chegamos aos componentes interativos mais elaborados — Modal, Carousel e Dropdown — e ao fluxo de desenvolvimento do projeto.
CloseButton
O mais simples da lista, usado internamente por Alert, Modal e outros:
btn = ElementComponent::Components::CloseButton.new
# => <button class="btn-close" type="button" aria-label="Close">
disabled = ElementComponent::Components::CloseButton.new(disabled: true)
# => <button class="btn-close" type="button" aria-label="Close" disabled>
Modal
O Modal é o componente mais profundamente aninhado da gem, com seis sub-componentes trabalhando juntos:
modal = ElementComponent::Components::Modal.new(id: "exampleModal") do |m|
m.add_content(ElementComponent::Components::ModalContent.new do |content|
content.add_content(ElementComponent::Components::ModalHeader.new do |header|
header.add_content(ElementComponent::Components::ModalTitle.new { |t| t.add_content("Modal title") })
end)
content.add_content(ElementComponent::Components::ModalBody.new { |body| body.add_content("Modal body text.") })
content.add_content(ElementComponent::Components::ModalFooter.new do |footer|
footer.add_content(ElementComponent::Components::Button.new(variant: :secondary) { |b| b.add_content("Close") })
footer.add_content(ElementComponent::Components::Button.new(variant: :primary) { |b| b.add_content("Save") })
end)
end)
end
Opções: fade, static, scrollable, centered, size (sm/lg/xl), fullscreen.
| Sub-componente | Tag | Classe CSS |
|---|---|---|
ModalDialog |
<div> |
.modal-dialog |
ModalContent |
<div> |
.modal-content |
ModalHeader |
<div> |
.modal-header |
ModalTitle |
<h5> |
.modal-title |
ModalBody |
<div> |
.modal-body |
ModalFooter |
<div> |
.modal-footer |
Note que os botões do rodapé são o próprio componente Button da parte 1 da série — reforçando o padrão de composição que atravessa toda a gem.
Carousel
carousel = ElementComponent::Components::Carousel.new(id: "slides") do |c|
c.add_content(ElementComponent::Components::CarouselItem.new(active: true) do |item|
item.add_content(%(<img src="slide1.jpg" class="d-block w-100" alt="...">))
end)
c.add_content(ElementComponent::Components::CarouselItem.new do |item|
item.add_content(%(<img src="slide2.jpg" class="d-block w-100" alt="...">))
end)
end
Opções: fade (crossfade), indicators, controls — indicadores e controles de navegação são gerados automaticamente a partir dos itens adicionados, sem precisar montá-los manualmente.
| Sub-componente | Tag | Classe CSS |
|---|---|---|
CarouselItem |
<div> |
.carousel-item |
CarouselCaption |
<div> |
.carousel-caption |
Vale mencionar que o Carousel teve um changeset dedicado só para correções (fix-carousel-component, registrado no histórico de specs do OpenSpec do projeto) — é o componente mais sensível a estado (item ativo, índices de indicadores) da gem.
Dropdown
dropdown = ElementComponent::Components::Dropdown.new do |d|
d.add_content(
ElementComponent::Element.new("button",
class: "btn btn-secondary dropdown-toggle",
type: "button",
"data-bs-toggle": "dropdown",
"aria-expanded": "false") { |b| b.add_content("Dropdown") }
)
d.add_content(
ElementComponent::Components::DropdownMenu.new do |menu|
menu.add_content(ElementComponent::Components::DropdownItem.new { |i| i.add_content("Action") })
menu.add_content(ElementComponent::Components::DropdownItem.new(active: true) { |i| i.add_content("Active") })
menu.add_content(ElementComponent::Components::DropdownDivider.new)
menu.add_content(ElementComponent::Components::DropdownItem.new(disabled: true) { |i| i.add_content("Disabled") })
end
)
end
Opções: direction (dropup/dropend/dropstart).
| Sub-componente | Tag | Classe CSS |
|---|---|---|
DropdownMenu |
<ul> |
.dropdown-menu |
DropdownItem |
<li> → <a>/<button> |
.dropdown-item |
DropdownDivider |
<li> → <hr> |
.dropdown-divider |
DropdownHeader |
<li> → <h6> |
.dropdown-header |
Este exemplo é o único do catálogo onde o botão de acionamento é montado com Element.new puro em vez de um sub-componente dedicado — mostra bem como a API core e os componentes prontos convivem no mesmo código sem fricção.
Desenvolvimento e contribuição
O projeto segue um fluxo padrão de gem Ruby, com RSpec para testes e RuboCop para lint:
bin/setup # instala dependências
bundle exec rspec # roda os testes
bundle exec rubocop # lint
bundle exec rake # specs + rubocop
bin/console # console interativo
ruby examples/alert_example.rb # roda exemplos isolados por componente
Cada componente tem um arquivo de exemplo correspondente em examples/ e um spec dedicado em spec/lib/components/, o que facilita tanto aprender a API quanto garantir cobertura ao alterar um componente. Para rodar com relatório de cobertura:
COVERAGE=true bundle exec rspec
O processo de release é simples: atualizar lib/element_component/version.rb e rodar bundle exec rake release, ou empurrar uma tag de versão (vX.Y.Z) para disparar o workflow de release automatizado no GitHub Actions.
Curiosamente, o repositório também usa OpenSpec para documentar mudanças arquiteturais maiores (como a correção do Carousel e a adição de exemplos) como propostas versionadas antes de virarem código — um processo de design mais formal do que o típico fluxo de issues do GitHub.
Roadmap
Segundo o README, dois itens seguem em aberto:
- Adicionar um identificador automático ao adicionar componentes via
add_content. - Corrigir o componente
Button, adicionando a opção de atributotype(:button/:submit) noinitialize.
Encerrando a série
Com isso fechamos o tour completo pela ElementComponent: da API core de construção de HTML, passando por segurança, cache e integração com Rails, até os 17 componentes Bootstrap 5 e o fluxo de contribuição do projeto. É uma gem enxuta que resolve um problema comum — montar HTML complexo em Ruby sem recorrer a template engines — com uma API consistente do início ao fim.