mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
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.
106 lines
2.7 KiB
TypeScript
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>
|
|
);
|
|
}
|