React UI-Komponente
@processcube-io/qmd-search-ui bietet einen konfigurierbaren Such-Dialog fuer React-Anwendungen.
Live-Demo — Diese Seite nutzt den SearchDialog produktiv. Druecke Cmd+K (macOS) oder Ctrl+K (Windows/Linux) um die Suche zu oeffnen und die Komponente in Aktion zu sehen.
Installation
npm install @processcube-io/qmd-search-uiVoraussetzung: React >= 18
Verwendung
import { SearchDialog } from '@processcube-io/qmd-search-ui';
function App() {
const router = useRouter(); // oder eigene Navigation
return (
<SearchDialog
apiEndpoint="/api/search"
collections={[
{
label: 'Dokumentation',
collections: [
{ key: 'docs', label: 'Docs' },
{ key: 'api', label: 'API Reference' },
],
},
{
label: 'Sonstige',
collections: [
{ key: 'blog', label: 'Blog' },
{ key: 'changelogs', label: 'Changelogs' },
],
},
]}
onNavigate={(path) => router.push(path)}
theme={{
accentColor: '#f7a823',
collectionColors: {
docs: '#3b82f6',
api: '#8b5cf6',
blog: '#10b981',
changelogs: '#a855f7',
},
}}
/>
);
}Props
| Prop | Typ | Default | Beschreibung |
|---|---|---|---|
apiEndpoint | string | '/api/search' | Such-API-Endpoint |
collections | CollectionGroupConfig[] | [] | Collection-Gruppen fuer den Filter |
onNavigate | (path: string) => void | (Pflicht) | Navigation bei Ergebnis-Klick |
theme | SearchTheme | {} | Farben, Schrift, Border-Radius |
labels | SearchLabels | Deutsche Defaults | Texte / Lokalisierung |
debounceMs | number | 350 | Debounce-Zeit |
limit | number | 15 | Max. Ergebnisse pro Suche |
Theme
interface SearchTheme {
accentColor?: string; // Spinner, Ladeleiste (Default: '#f7a823')
borderRadius?: string; // Dialog-Ecken (Default: '12px')
fontFamily?: string; // Schriftart (Default: inherit)
collectionColors?: Record<string, string>; // Badge-Farben pro Collection
}Labels / Lokalisierung
Alle Texte koennen ueberschrieben werden. Defaults sind Deutsch:
<SearchDialog
labels={{
placeholder: 'Search documentation...',
triggerText: 'Search...',
shortcutLabel: '⌘K',
allCollections: 'All',
noResults: 'No results for',
unavailable: 'Search is initializing...',
navigationHint: 'Navigate',
openHint: 'Open',
closeHint: 'Close',
}}
/>Features
- Cmd+K / Ctrl+K — Shortcut zum Oeffnen
- Debounce — Konfigurierbare Verzoegerung
- AbortController — Vorherige Requests werden abgebrochen
- Keyboard-Navigation — Pfeiltasten + Enter
- Skeleton-Platzhalter — Waehrend der ersten Suche
- Collection-Filter — Gruppierter Dropdown
- Animationen — Fade-In, Ladeleiste, Spinner
Auth-Integration
Die UI-Komponente kennt keinen API-Key — der Key darf nie im Browser landen. Zwei Integrationsmuster:
Muster A: Proxy (empfohlen)
Die App leitet Suchanfragen an den qmd-search-server weiter und fuegt den API-Key serverseitig hinzu. Funktioniert mit jedem Framework.
Browser (SearchDialog) → App-Backend (+ API-Key) → qmd-search-serverNext.js Beispiel:
// app/api/search/route.ts — Proxy-Route
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const q = searchParams.get('q');
const collection = searchParams.get('collection') || '';
const limit = searchParams.get('limit') || '20';
const params = new URLSearchParams({ q: q || '', collection, limit });
const response = await fetch(`https://search.example.com/api/search?${params}`, {
headers: { 'X-API-Key': process.env.QMD_API_KEY! },
});
return new Response(response.body, {
status: response.status,
headers: { 'Content-Type': 'application/json' },
});
}// Komponente — spricht die eigene Proxy-Route an
<SearchDialog apiEndpoint="/api/search" onNavigate={(path) => router.push(path)} />Express Beispiel:
import express from 'express';
const app = express();
app.get('/api/search', async (req, res) => {
const params = new URLSearchParams(req.query as Record<string, string>);
const response = await fetch(`https://search.example.com/api/search?${params}`, {
headers: { 'X-API-Key': process.env.QMD_API_KEY! },
});
const data = await response.json();
res.json(data);
});Muster B: Server Component (Next.js)
Die Suche laeuft komplett serverseitig — der Store wird direkt importiert, kein HTTP noetig. Der API-Key ist nicht beteiligt, da der Store lokal auf die SQLite-Datenbank zugreift.
// app/search/page.tsx — Server Component
import { createSearchStore } from '@processcube-io/qmd-search';
const store = await createSearchStore({ dbPath: './data/index.sqlite' });
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ q?: string; collection?: string }>;
}) {
const { q, collection } = await searchParams;
if (!q) return <p>Bitte Suchbegriff eingeben.</p>;
const result = await store.search(q, { collection, limit: 20 });
return (
<ul>
{result.results.map((hit) => (
<li key={hit.path}>
<a href={hit.path}><strong>{hit.title}</strong></a>
<span> [{hit.collection}]</span>
<p>{hit.snippet}</p>
</li>
))}
</ul>
);
}Dieses Muster eignet sich besonders wenn die Datenbank lokal vorliegt (z.B. im gleichen Container).