Seguindo a série sobre a ElementComponent, esta parte cobre os componentes ligados a navegação e apresentação de dados: Nav, Navbar, Breadcrumb, ListGroup, Pagination, Progress, Spinner e Table.

nav = ElementComponent::Components::Nav.new(type: :tabs) do |n|
  n.add_content(ElementComponent::Components::NavItem.new do |item|
    item.add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { |l| l.add_content("Home") })
  end)
  n.add_content(ElementComponent::Components::NavItem.new do |item|
    item.add_content(ElementComponent::Components::NavLink.new(href: "/profile") { |l| l.add_content("Profile") })
  end)
end
# => <ul class="nav nav-tabs">...</ul>

Opções: type (tabs/pills/underline), fill, justified, vertical.

O Navbar é o componente de navegação mais completo, combinando marca, toggler responsivo e um menu colapsável:

navbar = ElementComponent::Components::Navbar.new(theme: :dark, background: :dark) do |n|
  n.add_content(ElementComponent::Components::NavbarBrand.new(href: "/") { |b| b.add_content("Brand") })
  n.add_content(ElementComponent::Components::NavbarToggler.new(target: "nav"))
  n.add_content(ElementComponent::Components::NavbarCollapse.new(id: "nav") do |collapse|
    collapse.add_content(ElementComponent::Components::NavbarNav.new do |nav|
      nav.add_content(ElementComponent::Components::NavItem.new do |item|
        item.add_content(ElementComponent::Components::NavLink.new(href: "/", active: true) { |l| l.add_content("Home") })
      end)
      nav.add_content(ElementComponent::Components::NavItem.new do |item|
        item.add_content(ElementComponent::Components::NavLink.new(href: "/about") { |l| l.add_content("About") })
      end)
    end)
  end)
end

Opções: expand (sm/md/lg/xl/xxl), theme (light/dark), background, fixed (top/bottom), sticky (top/bottom), container.

Sub-componente Tag Classe CSS
NavbarBrand <a> .navbar-brand
NavbarToggler <button> (autofechado) .navbar-toggler
NavbarCollapse <div> .collapse .navbar-collapse
NavbarNav <ul> .navbar-nav

Repare que Navbar reutiliza NavItem e NavLink do componente Nav — a gem evita duplicar conceitos que já existem, compondo componentes maiores a partir dos menores.

crumb = ElementComponent::Components::Breadcrumb.new do |b|
  b.add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/") { |i| i.add_content("Home") })
  b.add_content(ElementComponent::Components::BreadcrumbItem.new(href: "/section") { |i| i.add_content("Section") })
  b.add_content(ElementComponent::Components::BreadcrumbItem.new(active: true) { |i| i.add_content("Current") })
end
# => <nav aria-label="breadcrumb"><ol class="breadcrumb">...</ol></nav>

ListGroup

list = ElementComponent::Components::ListGroup.new(flush: true) do |l|
  l.add_content(ElementComponent::Components::ListGroupItem.new { |i| i.add_content("Item 1") })
  l.add_content(ElementComponent::Components::ListGroupItem.new(active: true) { |i| i.add_content("Item 2") })
  l.add_content(ElementComponent::Components::ListGroupItem.new(href: "/link") { |i| i.add_content("Link") })
end
# => <ul class="list-group list-group-flush">...</ul>

Opções: flush, numbered; ListGroupItem aceita variant, active, disabled, href.

Pagination

nav = ElementComponent::Components::Pagination.new(size: :lg) do |p|
  p.add_content(ElementComponent::Components::PageItem.new(active: true) { |i| i.add_content("1") })
  p.add_content(ElementComponent::Components::PageItem.new { |i| i.add_content("2") })
end
# => <nav aria-label="Pagination"><ul class="pagination pagination-lg">...</ul></nav>

Progress

progress = ElementComponent::Components::Progress.new do |p|
  p.add_content(ElementComponent::Components::ProgressBar.new(value: 75, variant: :success, striped: true) do |bar|
    bar.add_content("75%")
  end)
end
# => <div class="progress" role="progressbar"><div class="progress-bar bg-success progress-bar-striped" ...>75%</div></div>

Spinner

border = ElementComponent::Components::Spinner.new(type: :border, variant: :primary)
# => <div class="spinner-border text-primary" role="status"></div>

grow = ElementComponent::Components::Spinner.new(type: :grow, variant: :success)
# => <div class="spinner-grow text-success" role="status"></div>

Table

table = ElementComponent::Components::Table.new(striped: true, bordered: true, hover: true) do |t|
  t.add_content("<thead><tr><th>Name</th><th>Age</th></tr></thead>")
  t.add_content("<tbody><tr><td>John</td><td>30</td></tr></tbody>")
end
# => <table class="table table-striped table-bordered table-hover">...</table>

Opções: striped, bordered, hover, small, variant.

Vale notar que, no exemplo de Table, o conteúdo passado é uma string HTML crua — e por conta do escaping automático coberto na parte 2 da série, strings com <thead>/<tbody> só funcionam como esperado porque o próprio HTML é o conteúdo pretendido. Em casos assim, ou você monta <thead>/<tbody> com Element.new (a forma mais idiomática), ou usa ElementComponent.html_safe() para strings HTML já prontas.

Próximo post

Na parte final da série, fechamos com os componentes interativos mais complexos — Modal, Carousel, Dropdown e CloseButton — e um resumo do fluxo de desenvolvimento e contribuição do projeto.