feat: add searchable workflow selects
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-17 18:31:39 +08:00
parent 64cb485dfa
commit 5f81f596b8
2 changed files with 112 additions and 20 deletions

View File

@ -6,6 +6,7 @@ import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { SearchableSelect } from "@/components/ui/searchable-select";
import { Textarea } from "@/components/ui/textarea";
import { cn } from "@/lib/utils";
import { defaultAgentWorkflowUiConfig } from "@/lib/agents/workflow-ui-config";
@ -185,32 +186,22 @@ export function AgentWorkflowPanel({
}
placeholder="搭配 SKU"
/>
<Input
<SearchableSelect
value={inputs.brand}
onChange={(event) =>
onInputsChange({ ...inputs, brand: event.target.value })
}
options={brandOptions}
placeholder="品牌"
list="agent-workflow-brand-options"
searchPlaceholder="输入品牌名筛选"
emptyText="暂无品牌可选,请先到系统设置配置"
onValueChange={(value) => onInputsChange({ ...inputs, brand: value })}
/>
<datalist id="agent-workflow-brand-options">
{brandOptions.map((item) => (
<option key={item} value={item} />
))}
</datalist>
<Input
<SearchableSelect
value={inputs.store}
onChange={(event) =>
onInputsChange({ ...inputs, store: event.target.value })
}
options={storeOptions}
placeholder="店铺"
list="agent-workflow-store-options"
searchPlaceholder="输入店铺名筛选"
emptyText="暂无店铺可选,请先到系统设置配置"
onValueChange={(value) => onInputsChange({ ...inputs, store: value })}
/>
<datalist id="agent-workflow-store-options">
{storeOptions.map((item) => (
<option key={item} value={item} />
))}
</datalist>
</div>
{(brandOptions.length > 0 || storeOptions.length > 0) && (
<div className="text-xs text-muted-foreground">

View File

@ -0,0 +1,101 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import { Check, ChevronsUpDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
interface SearchableSelectProps {
value: string;
options: string[];
placeholder: string;
searchPlaceholder?: string;
emptyText?: string;
onValueChange: (value: string) => void;
}
export function SearchableSelect({
value,
options,
placeholder,
searchPlaceholder = "输入关键字搜索",
emptyText = "暂无可选项",
onValueChange,
}: SearchableSelectProps) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
useEffect(() => {
if (!open) {
setQuery("");
}
}, [open]);
const filteredOptions = useMemo(() => {
const normalizedQuery = query.trim().toLowerCase();
if (!normalizedQuery) return options;
return options.filter((item) => item.toLowerCase().includes(normalizedQuery));
}, [options, query]);
return (
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild>
<Button
type="button"
variant="outline"
className="h-10 w-full justify-between px-3 text-left font-normal"
>
<span className={cn("truncate", value ? "text-foreground" : "text-muted-foreground")}>
{value || placeholder}
</span>
<ChevronsUpDown className="h-4 w-4 text-muted-foreground" />
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-[var(--radix-popover-trigger-width)] p-3">
<div className="space-y-3">
<Input
autoFocus
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder={searchPlaceholder}
/>
<ScrollArea className="max-h-64">
<div className="space-y-1">
{filteredOptions.length > 0 ? (
filteredOptions.map((item) => {
const selected = item === value;
return (
<button
key={item}
type="button"
onClick={() => {
onValueChange(item);
setOpen(false);
}}
className={cn(
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-sm transition-colors",
selected
? "bg-primary/10 text-primary"
: "hover:bg-muted"
)}
>
<span className="truncate">{item}</span>
{selected ? <Check className="h-4 w-4" /> : null}
</button>
);
})
) : (
<div className="px-3 py-6 text-center text-sm text-muted-foreground">
{emptyText}
</div>
)}
</div>
</ScrollArea>
</div>
</PopoverContent>
</Popover>
);
}