Encabezado de página

El encabezado de cada pantalla del admin: migas, título con conteo, una línea que diga qué hay aquí, la acción principal a la derecha y, debajo, las pestañas o filtros de la vista. Con sticky se queda arriba y, solo cuando el contenido ya pasa por debajo, toma fondo de vidrio y una línea.

Usuarios 47

Alumnos, maestras y admins del estudio.

  • Ana García ana.garcia@correo.mx
  • Iván López ivan@lunaestudio.mx
  • Mariana Peña mariana.pena@correo.mx
  • Sofía Ruiz sofia@lunaestudio.mx
  • Diego Hernández diego.hdz@correo.mx
  • Lucía Torres lucia.torres@correo.mx
  • Regina Castro regina@lunaestudio.mx
  • Tomás Navarro tomas.navarro@correo.mx

app/views/… (la demo de arriba)

<% people = [ [ "Ana García", "ana.garcia@correo.mx" ], [ "Iván López", "ivan@lunaestudio.mx" ], [ "Mariana Peña", "mariana.pena@correo.mx" ],
              [ "Sofía Ruiz", "sofia@lunaestudio.mx" ], [ "Diego Hernández", "diego.hdz@correo.mx" ], [ "Lucía Torres", "lucia.torres@correo.mx" ],
              [ "Regina Castro", "regina@lunaestudio.mx" ], [ "Tomás Navarro", "tomas.navarro@correo.mx" ] ] %>
<%# Un panel con scroll hace las veces de <main> del admin: desplázalo y el encabezado se vuelve vidrio. %>
<div class="w-full max-w-3xl">
  <div class="h-80 overflow-y-auto overscroll-contain rounded-2xl border border-border bg-background px-4 pb-4 eui-focus sm:px-5" tabindex="0" role="region" aria-label="Vista previa del admin">
    <%= render Ui::PageHeaderComponent.new(title: "Usuarios", count: 47, heading: :h2, sticky: true, class: "-mx-4 px-4 sm:-mx-5 sm:px-5",
                                           description: "Alumnos, maestras y admins del estudio.",
                                           breadcrumbs: [ [ "Panel", "#page-header-panel" ], [ "Usuarios" ] ]) do |header| %>
      <% header.with_actions do %>
        <%= render Ui::ButtonComponent.new(variant: :secondary, size: :sm, href: "#page-header-exportar") do %>Exportar<% end %>
        <%= render Ui::ButtonComponent.new(size: :sm, href: "#page-header-nuevo") do %>
          <%= render Ui::IconComponent.new(:plus, size: :sm) %>
          Nuevo usuario
        <% end %>
      <% end %>
      <% header.with_tabs do %>
        <%= render Ui::SegmentedControlComponent.new(label: "Filtrar por rol", selected: "all") do |seg| %>
          <% seg.with_option(label: "Todos", value: "all", count: 47, href: "#page-header-todos", data: { turbo: false }) %>
          <% seg.with_option(label: "Alumnos", value: "student", count: 38, href: "#page-header-alumnos", data: { turbo: false }) %>
          <% seg.with_option(label: "Maestros", value: "teacher", count: 6, href: "#page-header-maestros", data: { turbo: false }) %>
          <% seg.with_option(label: "Admins", value: "admin", count: 3, href: "#page-header-admins", data: { turbo: false }) %>
        <% end %>
      <% end %>
    <% end %>

    <ul class="divide-y divide-border-subtle overflow-hidden rounded-xl border border-border bg-card">
      <% people.each do |name, email| %>
        <li class="flex items-center gap-3 px-4 py-3">
          <span class="grid size-8 shrink-0 place-items-center rounded-full border border-border-subtle bg-raised text-2xs font-medium text-muted-foreground" aria-hidden="true"><%= name.split.map(&:first).join %></span>
          <span class="min-w-0 leading-tight">
            <span class="block truncate text-sm font-medium text-foreground"><%= name %></span>
            <span class="mt-0.5 block truncate text-xs text-faint"><%= email %></span>
          </span>
        </li>
      <% end %>
    </ul>
  </div>
</div>

Qué hace

  • El vidrio aparece solo cuando el encabezado ya está pegado y el contenido pasa por debajo.
  • Detecta el contenedor que se desplaza (en el layout del admin es <main>), no solo la ventana.
  • Las acciones bajan bajo el título en pantallas angostas sin romper el renglón.

Props

Nombre Tipo Default Descripción
title / description String — / nil El nombre de la pantalla y una línea de contexto.
count Integer nil Conteo junto al título (p. ej. @pagy.count).
eyebrow String nil Etiqueta técnica sobre el título (eui-label).
breadcrumbs Array<[label, href]> [] Migas; la última es la página actual (sin enlace, aria-current).
sticky / offset Boolean / String (CSS) false / "0px" Se pega al contenedor que se desplaza; offset si hay algo más fijo encima.
heading :h1…:h6 (o 1–6) :h1 El nivel del título según la página (heading_level: es su nombre anterior).
with_actions / with_tabs slots — La acción principal (y secundarias) a la derecha; pestañas o filtros debajo.

Accesibilidad