Ir al contenido

Componentes · Ola 4 · Editorial

Highlight

Las coincidencias de una búsqueda dentro de un texto, con el fondo de la selección; sin distinguir mayúsculas ni tildes. Conserva el texto original, con sus tildes y mayúsculas, y hereda la letra del texto que lo rodea.

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

Ejemplos

Resultados de búsqueda

Escribe en el campo: el buscador filtra los posts y, en los que quedan, el título y la bajada marcan las coincidencias. El filtro es del producto; Highlight solo marca. Las filas son un IndexList listing. En Hermes, la selección es su acento.

  • El Doctor que Bailó con el Tiempo Por qué el Undécimo Doctor elige el juego después de novecientos años de peso. La ligereza como pedagogía del alma, no como evasión.

1 resultado

Svelte
<script lang="ts">
	import '@archeblack/ui/products/hermes.css';
	import { Field, Highlight, IndexList, IndexListItem, Input } from '@archeblack/ui';

	let query = $state('bailo');

	// Títulos y bajadas de Hermes.
	const posts = [
		{
			title: 'El Alma Pide Locura',
			slug: 'el-alma-pide-locura',
			dek: 'Hay caminos fuera del jardín. Sobre la sed que el saber no calma, y la disposición que el alma estaba pidiendo.'
		},
		{
			title: 'El Doctor que Bailó con el Tiempo',
			slug: 'el-doctor-que-bailo-con-el-tiempo',
			dek: 'Por qué el Undécimo Doctor elige el juego después de novecientos años de peso. La ligereza como pedagogía del alma, no como evasión.'
		},
		{
			title: 'Lo Numinoso en CONTROL',
			slug: 'lo-numinoso-en-control',
			dek: 'Lo sagrado también habita en los videojuegos. Un recorrido por CONTROL: arquetipos, mandalas y el momento en que Jesse reconoce su propia voz.'
		}
	];

	/** Sin mayúsculas ni tildes, la misma regla que usa Highlight para marcar. */
	const fold = (text: string) =>
		text
			.normalize('NFD')
			.replace(/[\u0300-\u036f]/g, '')
			.toLocaleLowerCase();

	// El buscador filtra; Highlight solo marca lo que coincide en los resultados que quedan.
	const results = $derived(
		query.trim() === ''
			? posts
			: posts.filter((post) =>
					[post.title, post.dek].some((field) => fold(field).includes(fold(query.trim())))
				)
	);
</script>

<!-- Resultados de búsqueda: el buscador filtra los posts y Highlight marca las coincidencias en
     los que quedan. Las filas son un IndexList variant="listing": el título en 17 px semibold y la
     bajada en Piazzolla; el resaltado hereda el peso de cada uno. Empieza con «bailo», que marca
     «Bailó»; prueba «TIEMPO» o «alma». -->
<div data-product="hermes" style="display: grid; gap: var(--arche-spacing-6); width: 100%">
	<div style:width="min(100%, 20rem)">
		<Field label="Buscar">
			<Input bind:value={query} type="search" />
		</Field>
	</div>
	{#if results.length > 0}
		<IndexList variant="listing" aria-label="Resultados">
			{#each results as post (post.slug)}
				<IndexListItem href={`#${post.slug}`}>
					{#snippet title()}<Highlight text={post.title} {query} />{/snippet}
					{#snippet description()}<Highlight text={post.dek} {query} />{/snippet}
				</IndexListItem>
			{/each}
		</IndexList>
	{:else}
		<p style="margin: 0; color: var(--arche-color-text-muted)">
			Sin resultados para «{query.trim()}».
		</p>
	{/if}
	<p
		role="status"
		style="margin: 0; font-size: var(--arche-font-size-xs); color: var(--arche-color-text-subtle)"
	>
		{results.length === 1 ? '1 resultado' : `${results.length} resultados`}
	</p>
</div>

Varias búsquedas

Con una lista, se marcan todas.

¿Puede una IA tener daimon? Un ensayo desde adentro, atravesando Westworld, Her, Ex Machina y el inconsciente colectivo de Jung.

Svelte
<script lang="ts">
	import { Highlight } from '@archeblack/ui';
</script>

<!-- Varias búsquedas a la vez: se marcan todas. Sin tildes ni mayúsculas en la búsqueda, el texto
     conserva las suyas. -->
<p style="margin: 0; max-width: 40rem; font: var(--arche-typography-reading)">
	<Highlight
		text="¿Puede una IA tener daimon? Un ensayo desde adentro, atravesando Westworld, Her, Ex Machina y el inconsciente colectivo de Jung."
		query={['daimon', 'inconsciente', 'jung']}
	/>
</p>

Props

Props de Highlight
PropDescripción
text string El texto que se muestra (un título, una bajada). Se escribe como texto, nunca como HTML: no hace falta sanearlo.
query string | string[] Lo que se busca. Cada coincidencia va en un <mark>, sin distinguir mayúsculas ni tildes («bailo» marca «Bailó»). Con varias búsquedas se marcan todas y las que se tocan se unen. Vacía o solo espacios, no marca nada.
class? ClassValue Se suma a arche-highlight.
...rest HTMLAttributes Van al <span> que envuelve el texto (lang, id…).

Accesibilidad

  • Cada coincidencia es un <mark>: algunos lectores de pantalla anuncian «resaltado» al entrar y al salir. El texto se lee entero y en orden.
  • El resaltado es el fondo de la selección con on-selected (D8b): en Arche, el vacío sobre blanco; en Hermes, sobre su acento, a más de 7:1.
  • En colores forzados toma Mark y MarkText, los colores del sistema para el texto resaltado.
  • Hereda la letra y el peso del texto que lo rodea: no cambia el ancho de las palabras ni mueve las líneas al escribir.

Qué evitar

  • Armar el resaltado con {@html} y un reemplazo con expresiones regulares. Highlight: escribe el texto como texto y no depende de sanear la búsqueda.
  • Un color nuevo, un subrayado o una negrita para las coincidencias. El fondo de la selección, el mismo del <mark> de Prose (D8b).
  • Distinguir tildes: «bailo» que no encuentra «Bailó». La regla de Highlight, la misma del filtro de Combobox: sin mayúsculas ni tildes.