Files
roboco/panel/src/components/knowledge-base/kb-search-bar.tsx
T
Renn F c37516f640 feat(panel): tooltip sweep — projects, products, social, KB, business, settings, notifications
All 34 feature flags get verified one-line tips; secret inputs state
the write-only contract; KB index types get canonical descriptions;
switch tips ride Labels so Radix data-state stays intact. Also fixes
the scorecard SectionLabel swallowing props, which made tooltips on it
silently inert.
2026-07-15 16:33:22 +02:00

106 lines
2.7 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Search, X, Loader2 } from "lucide-react";
import { HelpTip } from "@/components/ui/help-tip";
interface KBSearchBarProps {
value: string;
onChange: (value: string) => void;
onSearch?: () => void;
placeholder?: string;
isLoading?: boolean;
debounceMs?: number;
}
export function KBSearchBar({
value,
onChange,
onSearch,
placeholder = "Search knowledge base...",
isLoading = false,
debounceMs = 300,
}: KBSearchBarProps) {
const [localValue, setLocalValue] = useState(value);
// Debounce the onChange callback
useEffect(() => {
const timer = setTimeout(() => {
if (localValue !== value) {
onChange(localValue);
}
}, debounceMs);
return () => clearTimeout(timer);
}, [localValue, debounceMs, onChange, value]);
// Sync external value changes
useEffect(() => {
setLocalValue(value);
}, [value]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && onSearch) {
onChange(localValue);
onSearch();
}
};
const handleClear = () => {
setLocalValue("");
onChange("");
};
return (
<div className="relative flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onKeyDown={handleKeyDown}
placeholder={placeholder}
className="pl-9 pr-9"
/>
{localValue && (
<HelpTip label="Clear search">
<Button
variant="ghost"
size="icon-sm"
onClick={handleClear}
aria-label="Clear search"
className="absolute right-1 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
>
<X className="h-4 w-4" />
</Button>
</HelpTip>
)}
</div>
{onSearch && (
<HelpTip
label={
isLoading
? "Searching…"
: !localValue || localValue.length < 3
? "Enter at least 3 characters to search"
: null
}
>
<Button
onClick={onSearch}
disabled={!localValue || localValue.length < 3 || isLoading}
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
"Search"
)}
</Button>
</HelpTip>
)}
</div>
);
}