Ir al contenido

Componentes · Ola 2 · Superposiciones

Popover

Contenido no modal anclado a un botón: filtros, detalles o un formulario corto. Se abre con un clic, se cierra con Escape o con un clic afuera y devuelve el foco al botón que lo abrió.

import { Popover } from '@archeblack/ui';

Ejemplos

Básico

title se ve arriba y nombra al diálogo. El contenido admite texto y links.

Svelte
<script lang="ts">
	import { Button, Popover } from '@archeblack/ui';
	import { IconInfoCircle } from '@archeblack/ui/icons';
</script>

<!-- title nombra al diálogo y se ve arriba del contenido. -->
<Popover title="Uso del plan">
	{#snippet trigger(props)}
		<Button {...props} iconStart={IconInfoCircle}>Detalles</Button>
	{/snippet}
	<p>Llevas 8,4 GB de 10 GB. La cuota se renueva el 1 de octubre.</p>
	<p>
		<Button href="#props" variant="link">Ver el detalle del uso</Button>
	</p>
</Popover>

Filtros

Controles adentro y bind:open para cerrarlo desde «Aplicar». Al abrirse, el foco pasa al primer control; al cerrarse, vuelve al disparador.

Svelte
<script lang="ts">
	import { Button, Checkbox, Popover, Switch } from '@archeblack/ui';
	import { IconFilter } from '@archeblack/ui/icons';

	let open = $state(false);
	let failed = $state(true);
	let production = $state(false);
	let preview = $state(true);

	const active = $derived([failed, production, preview].filter(Boolean).length);
</script>

<!--
	Con bind:open el producto cierra el popover desde adentro («Aplicar»); el foco vuelve al
	disparador. Al abrirse, el foco pasa al primer control.
-->
<Popover title="Filtros" align="start" bind:open>
	{#snippet trigger(props)}
		<Button {...props} iconStart={IconFilter}>Filtros · {active}</Button>
	{/snippet}
	<Switch label="Solo con errores" bind:checked={failed} />
	<div style:display="grid" style:gap="var(--arche-spacing-1)">
		<Checkbox label="Producción" bind:checked={production} />
		<Checkbox label="Vista previa" bind:checked={preview} />
	</div>
	<div style:display="flex" style:justify-content="flex-end" style:gap="var(--arche-spacing-2)">
		<Button
			variant="ghost"
			size="sm"
			onclick={() => {
				failed = production = preview = false;
			}}
		>
			Limpiar
		</Button>
		<Button variant="primary" size="sm" onclick={() => (open = false)}>Aplicar</Button>
	</div>
</Popover>

Con flecha

arrow dibuja una flecha hacia el disparador; side elige el lado. Sin título visible, aria-label nombra al diálogo.

Svelte
<script lang="ts">
	import { Button, Popover } from '@archeblack/ui';
	import { IconHelp } from '@archeblack/ui/icons';
</script>

<!-- Sin título visible: aria-label nombra al diálogo. -->
<Popover side="right" arrow aria-label="Qué es un dominio verificado">
	{#snippet trigger(props)}
		<Button {...props} variant="ghost" icon={IconHelp} aria-label="Qué es un dominio verificado" />
	{/snippet}
	<p>Un dominio verificado apunta a tu proyecto y tiene su certificado al día.</p>
</Popover>

Props

Props de Popover
PropDescripción
trigger Snippet<[TriggerAttributes]> El disparador, obligatorio: el popover se ancla a él. Recibe los atributos del disparador (id, aria-haspopup="dialog", aria-expanded, aria-controls, el clic y la referencia), que se esparcen en un Button: {#snippet trigger(props)}<Button {...props}>Filtros</Button>{/snippet}.
children Snippet El contenido. Admite controles, links y texto.
title? string Título visible, en texto fuerte. Nombra al diálogo (aria-labelledby). Sin title, aria-label o aria-labelledby son obligatorios.
side? 'top' | 'right' | 'bottom' | 'left' Por defecto 'bottom'Lado del disparador en el que aparece. Si no entra, pasa al opuesto.
align? 'start' | 'center' | 'end' Por defecto 'center'Alineación sobre el lado elegido.
arrow? boolean Por defecto falseDibuja una flecha que apunta al disparador.
bind:open? boolean Por defecto falseSi el popover está abierto. Con open = false el producto lo cierra desde adentro y el foco vuelve al disparador.
onOpenChange? (open: boolean) => void Se llama cada vez que se abre o se cierra por sí mismo (disparador, Escape, clic afuera o el foco que sale), con el estado nuevo. No se llama cuando el producto cambia open. Llega después de actualizar open y solo si el cambio se aceptó: si un bind:open de función lo rechaza, no se llama.
class? ClassValue Clases del producto; se suman a arche-popover.
...rest HTMLAttributes<HTMLDivElement> Cualquier otro atributo va al <div role="dialog">, como aria-label o aria-labelledby. El rol lo decide el componente y title es el título visible.

Accesibilidad

  • El contenido es un <div role="dialog"> no modal, con nombre obligatorio por tipos: title visible, aria-label o aria-labelledby.
  • El disparador lleva aria-expanded, aria-controls y aria-haspopup="dialog", y se abre y se cierra con Enter, espacio o un clic.
  • Al abrirse, el foco pasa al primer control del contenido. Si no tiene controles, el foco va al contenido, que muestra el aro de foco cuando se abrió con el teclado. No queda atrapado: Tab puede salir, y el resto de la página sigue disponible.
  • Se cierra con Escape, con un clic afuera o al volver a pulsar el disparador. El foco vuelve al disparador.
  • La superficie es surface-overlay con la elevación de superposición (borde, sombra y brillo tenue). En colores forzados lleva un borde del sistema.
  • Con movimiento reducido, aparece y desaparece sin escala ni fundido.

Qué evitar

  • Un popover para una decisión que bloquea («¿Eliminar el proyecto?»). Un Dialog: es modal, atrapa el foco y pide una respuesta.
  • Solo texto que describe un botón. Un Tooltip, que se abre con el puntero y el foco sin mover el foco.
  • Una lista de acciones dentro de un popover. Un Menu: navegación con flechas, typeahead y el rol correcto.
  • Formularios largos o con varios pasos. Un Sheet o una página propia.
  • Abrir el popover con hover. Abrirlo con un clic: el contenido interactivo tiene que poder alcanzarse con el teclado y en pantallas táctiles.