返回 presentation-ai
PanelSearchFilter.tsx
根目录 / src / components / presentation / edit-panel / sections / PanelSearchFilter.tsx
1 "use client";
2
3 import { Search, X } from "lucide-react";
4
5 import { Button } from "@/components/ui/button";
6 import { Input } from "@/components/ui/input";
7 import { cn } from "@/lib/utils";
8
9 function normalizePanelSearchText(value: string): string {
10 return value
11 .toLowerCase()
12 .replace(/[-_/&]+/g, " ")
13 .replace(/\s+/g, " ")
14 .trim();
15 }
16
17 export function matchesPanelSearch(
18 query: string,
19 values: Array<string | null | undefined>,
20 ): boolean {
21 const normalizedQuery = normalizePanelSearchText(query);
22 if (!normalizedQuery) return true;
23
24 return values.some((value) =>
25 normalizePanelSearchText(value ?? "").includes(normalizedQuery),
26 );
27 }
28
29 export function PanelSearchFilter({
30 className,
31 onQueryChange,
32 placeholder,
33 query,
34 }: {
35 className?: string;
36 onQueryChange: (value: string) => void;
37 placeholder: string;
38 query: string;
39 }) {
40 return (
41 <div className={cn("shrink-0 border-b bg-background px-4 py-3", className)}>
42 <div className="relative">
43 <Search className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
44 <Input
45 value={query}
46 onChange={(event) => onQueryChange(event.target.value)}
47 placeholder={placeholder}
48 className="h-9 rounded-md pr-9 pl-9 text-sm"
49 />
50 {query && (
51 <Button
52 type="button"
53 variant="ghost"
54 size="icon"
55 aria-label="Clear search"
56 onClick={() => onQueryChange("")}
57 className="absolute top-1/2 right-1 size-7 -translate-y-1/2 rounded-full"
58 >
59 <X className="size-3.5" />
60 </Button>
61 )}
62 </div>
63 </div>
64 );
65 }
66
66 lines Plain Text