Avisos
Estado: BetaCampana de notificaciones
Un botón de campana con contador. Cuando llega algo la insignia hace pop con resorte y la campana se balancea; el panel se abre anclado a ella con escala y opacidad desde la esquina. "Marcar como leídas" apaga los puntos en cascada y la insignia se va. Las notificaciones nuevas pueden llegar por Turbo Stream.
- Resorte
- Popover
- Turbo Streams
Notificaciones
2 nuevasSin leer: Se liberó un lugar en Reformer 7:00
Mañana con Iván López. Resérvalo antes de que se llene.
Sin leer: Tu paquete vence en 3 días
Te quedan 2 de tus 10 clases.
Ana García confirmó tu clase privada
Jueves 18:00 · Sala 2
Estás al día: no hay notificaciones.
Sin leer:
Notificaciones
2 nuevasSin leer: Se liberó un lugar en Reformer 7:00
Mañana con Iván López. Resérvalo antes de que se llene.
Sin leer: Tu paquete vence en 3 días
Te quedan 2 de tus 10 clases.
Ana García confirmó tu clase privada
Jueves 18:00 · Sala 2
Estás al día: no hay notificaciones.
Sin leer:
app/views/… (la demo de arriba)
<div class="@container w-full max-w-md">
<div class="flex items-center justify-between gap-2 rounded-2xl border border-border bg-card py-2 pl-4 pr-2">
<div class="flex min-w-0 items-center gap-2">
<span class="relative size-5 shrink-0 overflow-hidden rounded-full bg-foreground" aria-hidden="true"><span class="absolute -inset-px translate-x-[40%] rounded-full bg-card"></span></span>
<span class="hidden truncate text-sm font-semibold tracking-tight @xs:inline">Luna Estudio</span>
</div>
<%= render Ui::NotificationBellComponent.new(id: "notification-bell-demo", on: :card, items: [
{ title: "Se liberó un lugar en Reformer 7:00", detail: "Mañana con Iván López. Resérvalo antes de que se llene.", time: "hace 2 min" },
{ title: "Tu paquete vence en 3 días", detail: "Te quedan 2 de tus 10 clases.", time: "hace 1 h" },
{ title: "Ana García confirmó tu clase privada", detail: "Jueves 18:00 · Sala 2", time: "ayer", unread: false }
]) do %>
<%= render Ui::ButtonComponent.new(variant: :ghost, size: :sm, data: {
action: "notification-bell#add",
notification_bell_title_param: "Nueva clase: Yin al atardecer",
notification_bell_detail_param: "Viernes 19:30 con Sofía Ruiz. Quedan 8 lugares.",
notification_bell_time_param: "Ahora"
}) do %>Simular aviso<% end %>
<% end %>
</div>
</div>Qué hace
- El panel es un popover nativo en la capa superior, anclado con JS; si abajo no cabe abre hacia arriba.
- Turbo Stream: turbo_stream.prepend "eui-notifications-list", Ui::NotificationBellComponent::Notification.new(title: …).
- Acción notification-bell#add con params title, detail y time (la demo la usa).
- Eventos notification-bell:open y notification-bell:read ({ count }).
Props
| Nombre | Tipo | Default | Descripción |
|---|---|---|---|
| items | Array<Hash> | [] | Notificaciones: title, detail, time (String o Time), unread, href. |
| with_notification | slot | — | La misma notificación por slot (title:, detail:, time:, unread:, href:). |
| read_url | String | nil | Marcar como leídas envía un PATCH aquí (button_to, Turbo). |
| id | String | "eui-notifications" | Prefijo de ids; la lista es <id>-list (destino de Turbo Streams). |
| label | String | "Notificaciones" | Nombre del botón y título del panel. |
| align | :end | :start | :end | Borde de la campana al que se alinea el panel. |
| on | :background | :card | :raised | :background | Superficie detrás: color del anillo de la insignia. |
Accesibilidad
- El botón dice cuántas hay sin leer (Notificaciones, 2 sin leer) y lleva aria-haspopup, aria-expanded y aria-controls.
- Enter o Espacio abre y lleva el foco al panel (role="dialog"); Escape cierra y regresa el foco a la campana.
- Clic fuera o tabular fuera del panel lo cierra.
- Lo nuevo y "marcadas como leídas" se anuncian en una región aria-live; cada no leída dice "Sin leer".