← Orion

registry:ui

Search

Command search with live filtering and an empty state.

Rendered example

Install

orion-theme
Open registry JSON

registry/ui/search-list.tsx

"use client"

import * as React from "react"

/**
 * SEARCH
 * Command search with live filtering and an empty state.
 */

const ITEMS = [
  "Draft release notes",
  "Find flaky integration tests",
  "Compare staging vs prod config",
  "Write onboarding checklist",
  "Check CDN cache hit rate",
  "Audit unused feature flags",
  "Rotate API keys",
]

export function SearchList() {
  const [query, setQuery] = React.useState("")
  const results = query ? ITEMS.filter((i) => i.toLowerCase().includes(query.toLowerCase())) : ITEMS.slice(0, 5)
  const empty = query.length > 2 && results.length === 0

  return (
    <div className="flex min-h-[248px] w-full max-w-72 flex-col items-stretch">
      <div className="w-full self-start overflow-hidden rounded-card bg-surface shadow-raised">
        <div className="flex h-8 items-center gap-2 border-b border-line px-3 transition-colors duration-100 hover:bg-hover">
          <svg
            width="14"
            height="14"
            viewBox="0 0 24 24"
            fill="none"
            stroke="var(--ink-3)"
            strokeWidth="2"
            strokeLinecap="round"
            className="shrink-0"
          >
            <circle cx="11" cy="11" r="7" />
            <path d="M21 21l-4.3-4.3" />
          </svg>
          <input
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="Search commands…"
            aria-label="Search commands"
            className="min-w-0 flex-1 bg-transparent text-[13px] text-ink outline-none placeholder:text-ink-3"
          />
          {query && (
            <button
              aria-label="Clear search"
              type="button"
              onClick={() => setQuery("")}
              className="flex size-5.5 items-center justify-center rounded-full text-ink-3 transition-colors duration-100 hover:bg-line/70 hover:text-ink"
              style={{ animation: "fade-in 150ms ease-out both" }}
            >
              <svg
                width="11"
                height="11"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="2.2"
                strokeLinecap="round"
              >
                <path d="M18 6L6 18M6 6l12 12" />
              </svg>
            </button>
          )}
        </div>

        {empty ? (
          <div
            className="flex flex-col items-center justify-center gap-1 px-4 py-8"
            style={{ animation: "fade-in 250ms ease-out both" }}
          >
            <span className="mb-1.5 flex size-8 items-center justify-center rounded-control bg-inset text-ink-3 shadow-hairline">
              <svg
                width="15"
                height="15"
                viewBox="0 0 24 24"
                fill="none"
                stroke="currentColor"
                strokeWidth="1.8"
                strokeLinecap="round"
              >
                <circle cx="11" cy="11" r="7" />
                <path d="M21 21l-4.3-4.3" />
              </svg>
            </span>
            <span className="text-[13px] font-medium text-ink">No results found</span>
            <span className="text-[12px] text-ink-3">Adjust your search to try again</span>
          </div>
        ) : (
          <div className="p-1">
            {results.map((item) => (
              <button
                key={item}
                type="button"
                onClick={() => setQuery(item)}
                className="flex h-7 w-full items-center rounded-control px-2 text-left text-[13px] text-ink transition-colors duration-100 hover:bg-hover active:scale-[0.96]"
                style={{ animation: "fade-in 200ms ease-out both" }}
              >
                {item}
              </button>
            ))}
          </div>
        )}
      </div>
    </div>
  )
}