diff --git a/frontend/functions/ui/index.ts b/frontend/functions/ui/index.ts index e31dcd5c..9f674c01 100644 --- a/frontend/functions/ui/index.ts +++ b/frontend/functions/ui/index.ts @@ -114,6 +114,10 @@ export type { TextareaProps } from './textarea' export { Toast, ToastProvider, ToastViewport, toastVariants, useToast } from './toast' export type { ToastEntry, ToastProps, ToastViewportProps } from './toast' +// Search +export { SearchBar } from './search_bar' +export type { SearchBarProps } from './search_bar' + // Hooks — Canvas export { useAnimatedCanvas } from './use_animated_canvas' diff --git a/frontend/functions/ui/search_bar.md b/frontend/functions/ui/search_bar.md new file mode 100644 index 00000000..8f14571a --- /dev/null +++ b/frontend/functions/ui/search_bar.md @@ -0,0 +1,64 @@ +--- +name: search_bar +kind: component +lang: ts +domain: ui +version: "1.0.0" +purity: impure +signature: "SearchBar(props: SearchBarProps): JSX.Element" +description: "Search input with debounce, search icon, and clear button" +tags: [component, ui, search, input, debounce] +uses_functions: [cn_ts_core] +uses_types: [] +returns: [] +returns_optional: false +error_type: "" +imports: ["lucide-react"] +tested: false +tests: [] +test_file_path: "" +file_path: "frontend/functions/ui/search_bar.tsx" +props: + - name: onSearch + type: "(query: string) => void" + required: true + description: "Called with the debounced search query" + - name: placeholder + type: "string" + required: false + description: "Placeholder text (default: Search...)" + - name: debounceMs + type: "number" + required: false + description: "Debounce delay in ms (default: 300)" + - name: className + type: "string" + required: false + description: "Additional CSS classes" +emits: [] +has_state: true +framework: react +variant: [] +--- + +## Ejemplo + +```tsx +import { SearchBar } from '@fn_library' + +function MyPage() { + return ( + console.log('search:', query)} + placeholder="Search entities..." + debounceMs={300} + /> + ) +} +``` + +## Notas + +- Debounce usa ref para evitar re-renders innecesarios del callback +- El icono de clear solo aparece cuando hay texto +- Usa CSS variables del tema para colores (border, input, foreground, muted-foreground) diff --git a/frontend/functions/ui/search_bar.tsx b/frontend/functions/ui/search_bar.tsx new file mode 100644 index 00000000..450096f5 --- /dev/null +++ b/frontend/functions/ui/search_bar.tsx @@ -0,0 +1,65 @@ +import * as React from "react" +import { cn } from "../core/cn" +import { Search, X } from "lucide-react" + +interface SearchBarProps { + /** Called with the debounced search query */ + onSearch: (query: string) => void + /** Placeholder text */ + placeholder?: string + /** Debounce delay in ms (default 300) */ + debounceMs?: number + /** Additional CSS classes for the outer wrapper */ + className?: string +} + +function SearchBar({ + onSearch, + placeholder = "Search...", + debounceMs = 300, + className, +}: SearchBarProps) { + const [query, setQuery] = React.useState("") + const timerRef = React.useRef | null>(null) + const onSearchRef = React.useRef(onSearch) + onSearchRef.current = onSearch + + React.useEffect(() => { + if (timerRef.current) clearTimeout(timerRef.current) + timerRef.current = setTimeout(() => { + onSearchRef.current(query) + }, debounceMs) + return () => { + if (timerRef.current) clearTimeout(timerRef.current) + } + }, [query, debounceMs]) + + return ( +
+ + setQuery(e.target.value)} + placeholder={placeholder} + className="flex-1 bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground" + /> + {query && ( + + )} +
+ ) +} + +export { SearchBar } +export type { SearchBarProps }