nyuchimzizi
Mzizi — an open-architecture project of the Bundu Foundation, operated and developed by Nyuchi. Built on the Five African Minerals palette.
Built by Nyuchi Africav4.0.39
Universal branded search results view composing search-bar, filter-bar, and search-results primitives with mineral-accented category filters. Shows unified cross-app results: people, places, events, products, articles, groups. Each result type renders using its corresponding brand card (nyuchi-listing-card, nyuchi-place-card, user-card, nyuchi-group-card). The standard search experience across the entire ecosystem.
View the full component source code below.
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
import { useNyuchiHarness } from "@/lib/harness"
/* ═══════════════════════════════════════════════════════════════
NYUCHI SEARCH VIEW — Universal Brand Component (Pre-Wired)
Cross-app search results. Unified results from all domains:
people, places, events, products, articles, groups.
Each result type renders with its own brand card.
Dynamic mineral accent per result category.
✅ HARNESS ✅ TOKENS ✅ STRICT MINERAL RULES ✅ TOUCH 48px+
═══════════════════════════════════════════════════════════════ */
type ResultCategory = "all" | "people" | "places" | "events" | "products" | "articles" | "groups" | "jobs"
interface SearchResult {
id: string
type: ResultCategory
title: string
subtitle?: string
imageUrl?: string
verified?: boolean
mineral?: "cobalt" | "tanzanite" | "malachite" | "gold" | "terracotta"
}
interface NyuchiSearchViewProps {
/** Current search query */
query?: string
/** Search handler */
onSearch?: (query: string) => void
/** Active category filter */
activeCategory?: ResultCategory
/** Category change handler */
onCategoryChange?: (cat: ResultCategory) => void
/** Search results */
results?: SearchResult[]
/** Loading state */
loading?: boolean
/** Result click handler */
onResultClick?: (result: SearchResult) => void
/** Recent searches */
recentSearches?: string[]
/** Trending topics */
trending?: string[]
className?: string
}
const categoryMinerals: Record<ResultCategory, string> = {
all: "var(--brand-accent,var(--color-malachite,#64FFDA))",
people: "var(--color-tanzanite,#B388FF)",
places: "var(--color-gold,#FFD740)",
events: "var(--color-malachite,#64FFDA)",
products: "var(--color-gold,#FFD740)",
articles: "var(--color-cobalt,#00B0FF)",
groups: "var(--color-terracotta,#D4A574)",
jobs: "var(--color-gold,#FFD740)",
}
const categories: { key: ResultCategory; label: string }[] = [
{ key: "all", label: "All" },
{ key: "people", label: "People" },
{ key: "places", label: "Places" },
{ key: "events", label: "Events" },
{ key: "products", label: "Products" },
{ key: "articles", label: "Articles" },
{ key: "groups", label: "Groups" },
{ key: "jobs", label: "Jobs" },
]
export function NyuchiSearchView({
query = "",
onSearch,
activeCategory = "all",
onCategoryChange,
results = [],
loading = false,
onResultClick,
recentSearches = [],
trending = [],
className,
}: NyuchiSearchViewProps) {
const { log, motion, LiveRegion } = useNyuchiHarness("search-view")
const animStyle = React.useMemo(() => motion.prefersReduced ? {} : { animation: `nyuchi-fade-slide-up ${motion.enterDuration}ms ${motion.enterEasing} both` }, [motion])
const [localQuery, setLocalQuery] = React.useState(query)
return (
<div data-slot="nyuchi-search-view" data-portal="https://design.nyuchi.com/components/nyuchi-search-view" className={cn("space-y-4", className)}>
{/* Search input */}
<div className="relative">
<span className="absolute left-4 top-1/2 -translate-y-1/2 text-muted-foreground">🔍</span>
<input
type="text"
value={localQuery}
onChange={e => { setLocalQuery(e.target.value); onSearch?.(e.target.value) }}
aria-label="Search the ecosystem" placeholder="Search everything\u2026"
className="h-12 w-full rounded-full bg-muted pl-11 pr-4 text-sm text-foreground placeholder:text-muted-foreground outline-none border border-border focus:border-[var(--brand-accent,var(--color-malachite,#64FFDA))] transition-colors"
/>
</div>
{/* Category filters */}
<div className="flex gap-1.5 overflow-x-auto pb-1 scrollbar-none">
{categories.map(cat => (
<button
key={cat.key}
onClick={() => onCategoryChange?.(cat.key)}
className={cn(
"shrink-0 rounded-full px-3.5 py-1.5 text-xs font-medium transition-colors",
activeCategory === cat.key
? "text-[#0A0A0A]"
: "bg-muted text-muted-foreground hover:text-foreground"
)}
style={activeCategory === cat.key ? { backgroundColor: categoryMinerals[cat.key] } : undefined}
>
{cat.label}
</button>
))}
</div>
{/* Results or empty/recent */}
{loading ? (
<div className="space-y-2">{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="h-16 rounded-[var(--radius-lg,14px)] bg-muted animate-pulse" />
))}</div>
) : results.length > 0 ? (
<div className="space-y-1.5">{results.map(r => (
<button key={r.id} onClick={() => onResultClick?.(r)} className="flex w-full items-center gap-3 rounded-[var(--radius-lg,14px)] p-3 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-primary,#00B0FF)] transition-colors hover:bg-muted min-h-[56px]">
<div className="size-10 shrink-0 rounded-[var(--radius-sm,7px)] bg-muted flex items-center justify-center overflow-hidden">
{r.imageUrl ? <img src={r.imageUrl} alt="" className="size-full object-cover" /> : <span className="text-sm">🔹</span>}
</div>
<div className="flex-1 min-w-0">
<p className="truncate text-sm font-medium text-foreground">{r.title}{r.verified && <span className="ml-1 text-[var(--color-gold,#FFD740)]">✓</span>}</p>
{r.subtitle && <p className="truncate text-[11px] text-muted-foreground">{r.subtitle}</p>}
</div>
<span className="shrink-0 rounded-full px-2 py-0.5 text-[9px] font-medium capitalize" style={{ backgroundColor: `color-mix(in srgb, ${categoryMinerals[r.type]} 15%, transparent)`, color: categoryMinerals[r.type] }}>{r.type}</span>
</button>
))}</div>
) : localQuery ? (
<div className="py-12 text-center">
<p className="text-sm text-muted-foreground">No results for "{localQuery}"</p>
</div>
) : (
<div className="space-y-4">
{recentSearches.length > 0 && (
<div><p className="text-[11px] font-semibold text-muted-foreground mb-2">Recent</p>
<div className="space-y-1">{recentSearches.map(s => (
<button key={s} onClick={() => { setLocalQuery(s); onSearch?.(s) }} className="flex w-full items-center gap-2 rounded-[var(--radius-sm,7px)] px-3 py-2 text-sm text-muted-foreground hover:bg-muted hover:text-foreground transition-colors">🕐 {s}</button>
))}</div>
</div>
)}
{trending.length > 0 && (
<div><p className="text-[11px] font-semibold text-muted-foreground mb-2">Trending</p>
<div className="flex flex-wrap gap-1.5">{trending.map(t => (
<button key={t} onClick={() => { setLocalQuery(t); onSearch?.(t) }} className="rounded-full bg-muted px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground transition-colors">{t}</button>
))}</div>
</div>
)}
</div>
)}
</div>
)
}
npx shadcn@latest add https://mzizi.dev/api/v1/ui/nyuchi-search-viewFetch this component's metadata and source code from the registry API.
/api/v1/ui/nyuchi-search-view