MenuChevron Down
Search Busca - Docs - Artefact

Search Busca

Forms
Auto-interativo

Introdução

Um campo de busca instantânea sobre um índice JSON pré-gerado: as teclas digitadas são aplicadas com debounce, as correspondências aparecem como um menu suspenso de autocompletar, e escolher um resultado navega até seu href. Pode opcionalmente filtrar in loco elementos renderizados no servidor (por exemplo, uma lista de cartões de blog) e sempre degrada para um formulário GET ?q= simples quando JS não está disponível.

Search não realiza uma requisição a cada tecla digitada contra um endpoint ao vivo — ele carrega de forma preguiçosa um único documento JSON (src) na primeira interação, e então filtra inteiramente do lado do cliente. Construa um índice adequado (veja Search Index Format abaixo) para o conteúdo que você quer que seja pesquisável.

Hidratação

Nível 1 — automaticamente interativo. A filtragem com debounce, o menu suspenso de autocompletar e a navegação por teclado exigem JS do cliente, portanto Search se hidrata por padrão. Passe interactive={false} para forçar o retorno estático: um simples <input type="search" name="q">, envolto em um <form> GET quando action é definido.

interactive propResult
omitidoSe hidrata como ilha
trueSe hidrata como ilha
falseEstático — um simples input de formulário GET, sem JS do cliente

Todas as decisões de interatividade na biblioteca passam pelo auxiliar compartilhado shouldHydrate() em app/components/ui/island-utils.ts.

Uso

Search
import { Search } from "../components/ui";

export default function MyPage() {
  return (
    <Search
      src="/api/posts/search.json"
      placeholder="Search articles..."
      itemLabel="articles"
    />
  );
}

Formato do índice de busca

src aponta para um documento JSON estático com a forma de SearchIndexDocument (app/utils/search.ts):

interface SearchIndexEntry {
  /** Stable id (e.g. post slug) — matched against DOM filter attributes */
  key: string;
  /** Navigation target when the entry is picked from autocomplete */
  href: string;
  title: string;
  description?: string;
  tags?: string[];
  /** Precomputed lowercase text blob the query tokens are matched against */
  haystack: string;
}

interface SearchIndexDocument {
  generated: string;
  entries: SearchIndexEntry[];
}

buildHaystack() e tokenize() / filterEntries() no mesmo módulo são compartilhados pelo construtor de índices SSG, pelo retorno do servidor sem JS ?q=, e pela ilha do cliente, de modo que os três concordam sobre o que conta como uma correspondência. /api/posts/search.json e /api/docs/search.json são os dois índices já gerados neste projeto — aponte src para qualquer um deles, ou construa o seu próprio da mesma maneira.

Filtrando resultados in loco

Defina filterAttribute para também mostrar/ocultar elementos correspondentes renderizados no servidor à medida que a consulta muda, em vez de oferecer apenas um menu suspenso de autocompletar. É assim que a página de índice do blog (app/routes/blog/index.tsx) combina a caixa de busca com seus cartões de post já renderizados:

<Search
  src="/api/posts/search.json"
  action="/blog"
  initialQuery={searchQuery}
  placeholder="Search articles..."
  itemLabel="articles"
  total={blogPosts.length}
  filterAttribute="data-post-slug"
  emptyStateId="blog-search-empty"
/>

{blogPosts.map((post) => (
  <article data-post-slug={post.slug}>...</article>
))}

<div id="blog-search-empty" hidden>
  No articles match your search.
</div>

Cada elemento que carrega data-post-slug é ocultado a menos que seu valor corresponda à key de uma entrada nos resultados atuais; o elemento cujo id corresponde a emptyStateId é revelado assim que as correspondências chegam a zero. total inicializa a contagem de resultados exibida antes que o índice tenha terminado de carregar.

Retorno sem JS

Quando action é definido, tanto a variante estática quanto a hidratada renderizam um <form method="get"> ao redor do input, nomeado q. Sem JS do cliente, isso envia ?q= diretamente para action; uma rota que leia esse parâmetro de consulta (por exemplo, via filterEntries() no servidor) responde à mesma requisição que a ilha de outra forma teria tratado — as leituras de /blog?q=... da página do blog funcionam tanto se JS foi executado quanto se não foi.

Interação por teclado

KeyBehavior
ArrowDownAbre o menu suspenso, ou move o destaque para a próxima sugestão (cíclico).
ArrowUpMove o destaque para a sugestão anterior (cíclico).
EnterNavega até o href da sugestão destacada.
EscapeFecha o menu suspenso se estiver aberto; caso contrário, limpa a consulta.

Construtor de páginas do CMS

Este componente está disponível como um bloco search no Construtor de Páginas (content/pages/*.json):

{
  "type": "search",
  "src": "/api/posts/search.json",
  "placeholder": "Search posts...",
  "itemLabel": "posts",
  "maxSuggestions": 5,
  "debounceMs": 150
}

Propriedades

PropTypeDefaultDescription
srcstring/api/posts/search.jsonURL do índice de busca JSON gerado por SSG.
placeholderstring"Search..."Texto de espaço reservado para o input.
initialQuerystring-Consulta inicial, por exemplo, obtida de um parâmetro ?q= da URL na primeira renderização.
debounceMsnumber150Atraso antes de uma tecla digitada ser aplicada como consulta ativa.
maxSuggestionsnumber8Número máximo de entradas exibidas no menu suspenso de autocompletar.
filterAttributestring-Quando definido, elementos que carregam este atributo (por exemplo, data-post-slug) são mostrados/ocultados conforme seu valor corresponda à key de uma entrada.
emptyStateIdstring-id de um elemento a revelar quando a lista filtrada não tiver correspondências.
totalnumber-Contagem de resultados exibida antes que o índice tenha carregado.
itemLabelstring"results"Substantivo usado na contagem de resultados, por exemplo, "articles".
showCountbooleantrueExibe a linha de contagem de resultados "Showing X of N".
actionstring-Retorno sem JS: envia ?q= para este caminho via um formulário GET simples.
syncUrlbooleantrueReflete a consulta ativa na barra de endereços como ?q=.
interactiveboolean-Sobrescreve a decisão de hidratação (ver acima).