haixunMaster/components/inspiration/topic-picker.tsx

120 lines
4.1 KiB
TypeScript

"use client";
import { useMemo, useState } from "react";
import { Check, ChevronDown, Search } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { TOPIC_GOAL_LABELS, isPlacementGoal } from "@/lib/types/topic-goal";
import { cn } from "@/lib/utils";
export interface InspirationTopic {
id: string;
label: string;
topicGoal: string;
}
interface TopicPickerProps {
topics: InspirationTopic[];
selectedTopicId: string | null;
onSelectTopic: (topicId: string | null) => void;
}
export function TopicPicker({ topics, selectedTopicId, onSelectTopic }: TopicPickerProps) {
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const selected = selectedTopicId
? topics.find((t) => t.id === selectedTopicId) ?? null
: null;
const filtered = useMemo(() => {
const q = query.trim().toLowerCase();
if (!q) return topics;
return topics.filter((t) => t.label.toLowerCase().includes(q));
}, [topics, query]);
function pick(topicId: string | null) {
onSelectTopic(topicId);
setOpen(false);
setQuery("");
}
const triggerLabel = selected ? selected.label : `全部主題${topics.length > 0 ? ` · ${topics.length}` : ""}`;
return (
<>
<Button
type="button"
variant="outline"
className="h-9 min-w-[10rem] max-w-[16rem] justify-between gap-2 px-3 text-[13px] font-medium"
onClick={() => setOpen(true)}
>
<span className="truncate">{triggerLabel}</span>
<ChevronDown className="h-4 w-4 shrink-0 text-muted-foreground" />
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="w-[min(100vw-2rem,24rem)] gap-3 p-4">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<div className="relative">
<Search className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜尋主題名稱"
className="pl-9"
autoFocus
/>
</div>
<div className="max-h-64 space-y-0.5 overflow-y-auto">
<button
type="button"
onClick={() => pick(null)}
className={cn(
"flex w-full items-center justify-between rounded-md px-3 py-2 text-left text-[13px] transition-colors hover:bg-secondary",
selectedTopicId === null && "bg-primary/10 text-primary"
)}
>
<span></span>
{selectedTopicId === null && <Check className="h-4 w-4" />}
</button>
{filtered.length === 0 ? (
<p className="px-3 py-6 text-center text-[13px] text-muted-foreground"></p>
) : (
filtered.map((topic) => (
<button
key={topic.id}
type="button"
onClick={() => pick(topic.id)}
className={cn(
"flex w-full items-center justify-between gap-2 rounded-md px-3 py-2 text-left text-[13px] transition-colors hover:bg-secondary",
selectedTopicId === topic.id && "bg-primary/10 text-primary"
)}
>
<span className="flex min-w-0 flex-1 items-center gap-2">
<span className="truncate">{topic.label}</span>
<span className="shrink-0 rounded-full border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
{TOPIC_GOAL_LABELS[isPlacementGoal(topic.topicGoal) ? "placement" : "viral"]}
</span>
</span>
{selectedTopicId === topic.id && <Check className="h-4 w-4 shrink-0" />}
</button>
))
)}
</div>
</DialogContent>
</Dialog>
</>
);
}