Files

19 lines
5.3 KiB
JSON

{
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "search",
"type": "registry:component",
"title": "Search",
"description": "Command search with live filtering and an empty state.",
"registryDependencies": [
"https://www.beautifului.dev/r/foundation.json",
"https://www.beautifului.dev/r/glide-menu.json"
],
"files": [
{
"path": "components/primitives/SearchList.tsx",
"content": "\"use client\";\n\nimport { useState } from \"react\";\nimport GlideMenu from \"@/components/primitives/GlideMenu\";\n\n/* ─────────────────────────────────────────────────────────\n * SEARCH — command search with live filtering.\n * The field, clear action, and results are directly usable.\n * ───────────────────────────────────────────────────────── */\n\nexport type SearchItem = string;\n\nexport type SearchListLabels = {\n placeholder: string;\n ariaLabel: string;\n emptyTitle: string;\n emptyHint: string;\n};\n\nconst ITEMS: SearchItem[] = [\n \"Forecast summer demand\",\n \"Find waffle cone suppliers\",\n \"Compare seasonal flavors\",\n \"Draft flavor launch plan\",\n \"Check cold-chain status\",\n \"Audit sugar costs\",\n \"Retire low sellers\",\n];\n\nconst LABELS: SearchListLabels = {\n placeholder: \"Search flavors…\",\n ariaLabel: \"Search flavors\",\n emptyTitle: \"No results found\",\n emptyHint: \"Adjust your search to try again\",\n};\n\nexport default function SearchList({\n items = ITEMS,\n labels = LABELS,\n}: {\n items?: SearchItem[];\n labels?: SearchListLabels;\n variant?: string;\n} = {}) {\n const [query, setQuery] = useState(\"\");\n const results = query\n ? items.filter((i) => i.toLowerCase().includes(query.toLowerCase()))\n : items.slice(0, 5);\n const empty = query.length > 2 && results.length === 0;\n\n return (\n <div className=\"flex min-h-[248px] w-full max-w-72 flex-col items-stretch\">\n <div className=\"w-full self-start overflow-hidden rounded-card bg-surface shadow-raised\">\n {/* input row */}\n <div className=\"flex h-10 items-center gap-2 border-b border-line px-3 transition-colors duration-100 hover:bg-hover\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"var(--ink-3)\" strokeWidth=\"2\" strokeLinecap=\"round\" className=\"shrink-0\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"M21 21l-4.3-4.3\" />\n </svg>\n <input\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={labels.placeholder}\n aria-label={labels.ariaLabel}\n className=\"min-w-0 flex-1 bg-transparent text-[13px] text-ink outline-none placeholder:text-ink-3\"\n />\n {query && (\n <button\n aria-label=\"Clear search\"\n type=\"button\"\n onClick={() => setQuery(\"\")}\n className=\"flex size-6 items-center justify-center rounded-full text-ink-3\n transition-colors duration-100 hover:bg-line/70 hover:text-ink\"\n style={{ animation: \"fade-in 150ms ease-out both\" }}\n >\n <svg width=\"11\" height=\"11\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\">\n <path d=\"M18 6L6 18M6 6l12 12\" />\n </svg>\n </button>\n )}\n </div>\n\n {/* results / empty state */}\n {empty ? (\n <div className=\"flex flex-col items-center justify-center gap-1 px-4 py-8\" style={{ animation: \"fade-in 250ms ease-out both\" }}>\n <span className=\"mb-1.5 flex size-8 items-center justify-center rounded-control bg-inset text-ink-3 shadow-hairline\">\n <svg width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"M21 21l-4.3-4.3\" />\n </svg>\n </span>\n <span className=\"text-[13px] font-medium text-ink\">{labels.emptyTitle}</span>\n <span className=\"text-[12px] text-ink-3\">{labels.emptyHint}</span>\n </div>\n ) : (\n <div className=\"p-1\">\n <GlideMenu className=\"flex flex-col gap-px\" highlightClassName=\"inset-x-0 rounded-[6px] bg-hover\">\n {results.map((item) => (\n <button\n key={item}\n data-menu-row\n type=\"button\"\n onClick={() => setQuery(item)}\n className=\"relative z-10 flex h-8 w-full items-center rounded-[6px] px-2 text-left text-[13px] text-ink\"\n style={{ animation: \"fade-in 200ms ease-out both\" }}\n >\n {item}\n </button>\n ))}\n </GlideMenu>\n </div>\n )}\n </div>\n </div>\n );\n}\n",
"type": "registry:component",
"target": "components/primitives/SearchList.tsx"
}
]
}