Command-style capability picker with search and arrow keys — blog-12 content-highlight adapted for features.
@nusaiba
registry/blocks/features/8/features-index-section.tsx
"use client";
import { useCallback, useEffect, useState } from "react";
import { CommandIcon, SearchIcon } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { defaultSpring, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";
const capabilities = [
{
id: "motion-docs",
tag: "docs",
title: "Motion pattern docs",
excerpt: "40 live patterns with spring tokens, triggers, and block links.",
},
{
id: "cli-install",
tag: "cli",
title: "Shadcn CLI install",
excerpt: "npx shadcn add @nusaiba/block-name copies source into your repo.",
},
{
id: "pro-blocks",
tag: "pro",
title: "Pro block catalog",
excerpt: "Token-gated depth sections with commercial license.",
},
{
id: "copy-prompt",
tag: "ai",
title: "Copy prompt per block",
excerpt: "AI-ready install handoff on every block page.",
},
{
id: "preview-live",
tag: "preview",
title: "Live block preview",
excerpt: "Inspect motion before anything lands in your codebase.",
},
{
id: "token-auth",
tag: "auth",
title: "Registry token",
excerpt: "Private installs for Pro blocks — no subscription on solo use.",
},
];
export function FeaturesWorkflowIndexSection() {
const reduceMotion = useReducedMotion();
const [activeIndex, setActiveIndex] = useState(0);
const [query, setQuery] = useState("");
const filtered = capabilities.filter((item) => {
const haystack = `${item.title} ${item.tag} ${item.excerpt}`.toLowerCase();
return haystack.includes(query.toLowerCase());
});
const activeItem = filtered[activeIndex];
const move = useCallback(
(delta: number) => {
if (filtered.length === 0) return;
setActiveIndex((current) => (current + delta + filtered.length) % filtered.length);
},
[filtered.length],
);
useEffect(() => {
setActiveIndex(0);
}, [query]);
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (event.key === "ArrowDown") {
event.preventDefault();
move(1);
}
if (event.key === "ArrowUp") {
event.preventDefault();
move(-1);
}
}
window.addEventListener("keydown", onKeyDown);
return () => window.removeEventListener("keydown", onKeyDown);
}, [move]);
return (
<SectionShell spacingMode="section" className="bg-background">
<ContentRail maxWidth="max-w-2xl" className="space-y-8">
<div className="max-w-xl space-y-2">
<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">
Index
</p>
<h2 className="text-balance font-medium text-2xl tracking-tight md:text-4xl">
Capability lookup
</h2>
<p className="text-pretty text-muted-foreground text-sm md:text-base">
Command-style picker for developer audiences — pairs with blog-12 and
pricing-3 on the same landing.
</p>
</div>
<motion.div
className="overflow-hidden rounded-[var(--radius-md)] border border-border bg-background shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:shadow-[0_1px_2px_rgba(0,0,0,0.2)]"
initial={reduceMotion ? false : { opacity: 0, y: 12, filter: "blur(4px)" }}
transition={defaultSpring}
viewport={motionViewport}
whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
>
<div className="flex items-center gap-3 border-b border-border px-4 py-3">
<SearchIcon className="size-4 text-muted-foreground" />
<input
className="min-w-0 flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground"
onChange={(event) => setQuery(event.target.value)}
placeholder="Filter workflows…"
type="search"
value={query}
/>
<kbd className="hidden items-center gap-0.5 rounded-[var(--radius-sm)] border border-border bg-muted/40 px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground sm:inline-flex">
<CommandIcon className="size-2.5" />K
</kbd>
</div>
<ul className="max-h-[20rem] divide-y divide-border overflow-y-auto p-1">
{filtered.map((item, index) => {
const isActive = index === activeIndex;
return (
<li key={item.id}>
<button
className={cn(
"flex w-full items-start gap-3 rounded-[var(--radius-sm)] px-3 py-3 text-left transition-[background-color,transform] active:scale-[0.98]",
"after:absolute after:top-1/2 after:left-1/2 after:size-10 after:-translate-1/2",
isActive
? "bg-muted/50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.06)] dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08)]"
: "hover:bg-muted/25",
)}
onClick={() => setActiveIndex(index)}
onMouseEnter={() => setActiveIndex(index)}
type="button"
>
<span className="mt-0.5 font-mono text-muted-foreground text-xs tabular-nums">
{String(capabilities.indexOf(item) + 1).padStart(2, "0")}
</span>
<span className="min-w-0 flex-1">
<span className="flex flex-wrap items-center gap-2">
<span className="font-medium text-sm">{item.title}</span>
<span className="rounded-[var(--radius-sm)] bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground uppercase">
{item.tag}
</span>
</span>
<span className="mt-1 block text-pretty text-muted-foreground text-xs leading-relaxed">
{item.excerpt}
</span>
</span>
</button>
</li>
);
})}
</ul>
{activeItem ? (
<motion.div
animate={{ opacity: 1, y: 0 }}
className="border-t border-border bg-muted/20 px-4 py-3"
initial={{ opacity: 0, y: 6 }}
key={activeItem.id}
transition={{ type: "spring", duration: 0.3, bounce: 0 }}
>
<p className="text-pretty text-muted-foreground text-xs">
<span className="font-medium text-foreground">{activeItem.title}</span>
{" — "}
{activeItem.excerpt}
</p>
</motion.div>
) : null}
</motion.div>
<p className="font-mono text-[10px] text-muted-foreground uppercase tracking-[0.14em]">
↑ ↓ navigate rows
</p>
</ContentRail>
</SectionShell>
);
}




























