"use client"
import { useState, useEffect, useRef } from "react"
import { Search, X, Command, Filter, ArrowRight } from "lucide-react"
import { motion, AnimatePresence } from "framer-motion"
import { cn } from "@/lib/utils"
type SearchResult = {
id: string
title: string
category: string
description: string
}
type ActionSearchBarProps = {
placeholder?: string
onSearch?: (query: string) => void
onResultClick?: (result: SearchResult) => void
className?: string
initialResults?: SearchResult[]
}
// Move sampleResults outside the component
const sampleResults: {
id: string
title: string
category: string
description: string
}[] = [
{
id: "1",
title: "Create New Project",
category: "Projects",
description: "Start a new project with custom settings",
},
{
id: "2",
title: "Add Team Member",
category: "Team",
description: "Invite a new member to your team",
},
{
id: "3",
title: "Generate Report",
category: "Analytics",
description: "Create a detailed analytics report",
},
{
id: "4",
title: "Update Settings",
category: "Settings",
description: "Modify your account or project settings",
},
{
id: "5",
title: "Deploy to Production",
category: "Deployment",
description: "Push your changes to the production environment",
},
{
id: "6",
title: "Schedule Meeting",
category: "Calendar",
description: "Set up a new meeting with your team",
},
]
export function ActionSearchBar({
placeholder = "Search actions...",
onSearch,
onResultClick,
className,
initialResults = [],
}: ActionSearchBarProps) {
const [query, setQuery] = useState("")
const [isFocused, setIsFocused] = useState(false)
const [results, setResults] = useState<SearchResult[]>(initialResults)
const [selectedCategory, setSelectedCategory] = useState<string | null>(null)
const inputRef = useRef<HTMLInputElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
// Filter by category
const filteredResults = selectedCategory ? results.filter((result) => result.category === selectedCategory) : results
// Get unique categories
const categories = Array.from(new Set(results.map((result) => result.category)))
// Handle click outside to close
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsFocused(false)
}
}
document.addEventListener("mousedown", handleClickOutside)
return () => {
document.removeEventListener("mousedown", handleClickOutside)
}
}, [])
// Handle keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Cmd/Ctrl + K to focus search
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
e.preventDefault()
inputRef.current?.focus()
setIsFocused(true)
}
// Escape to blur
if (e.key === "Escape") {
inputRef.current?.blur()
setIsFocused(false)
}
}
window.addEventListener("keydown", handleKeyDown)
return () => {
window.removeEventListener("keydown", handleKeyDown)
}
}, [])
// Replace the useEffect for filtering results with this:
useEffect(() => {
if (query.trim() === "") {
setResults(initialResults.length ? initialResults : sampleResults)
} else {
const filtered = sampleResults.filter(
(result) =>
result.title.toLowerCase().includes(query.toLowerCase()) ||
result.description.toLowerCase().includes(query.toLowerCase()),
)
setResults(filtered)
}
if (onSearch) {
onSearch(query)
}
}, [query, initialResults, onSearch])
return (
<div ref={containerRef} className={cn("relative w-full max-w-3xl mx-auto", className)}>
{/* Search Input */}
<div className="relative">
<motion.div
className={cn(
"flex items-center rounded-xl border bg-white shadow-sm transition-all duration-300",
isFocused ? "border-blue-300 shadow-md ring-2 ring-blue-100" : "border-gray-200 hover:border-gray-300",
)}
animate={{
scale: isFocused ? 1.01 : 1,
}}
transition={{ type: "spring", stiffness: 400, damping: 25 }}
>
<div className="flex items-center px-3 py-2">
<Search className="h-5 w-5 text-gray-400" />
</div>
<input
ref={inputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setIsFocused(true)}
placeholder={placeholder}
className="flex-1 border-0 bg-transparent py-3 px-2 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-0"
/>
{query && (
<button
onClick={() => setQuery("")}
className="mr-2 rounded-full p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600"
>
<X className="h-4 w-4" />
</button>
)}
<div className="hidden md:flex items-center border-l border-gray-200 pl-2 pr-3">
<kbd className="hidden md:inline-flex h-5 select-none items-center gap-1 rounded border border-gray-200 bg-gray-50 px-1.5 font-mono text-xs text-gray-500">
<span className="text-xs">โ</span>K
</kbd>
</div>
</motion.div>
</div>
{/* Search Results */}
<AnimatePresence>
{isFocused && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.2 }}
className="absolute mt-2 w-full rounded-xl border border-gray-200 bg-white shadow-lg z-50"
>
{/* Categories */}
<div className="flex items-center gap-2 p-3 overflow-x-auto scrollbar-hide">
<button
onClick={() => setSelectedCategory(null)}
className={cn(
"flex items-center gap-1 rounded-full px-3 py-1 text-sm font-medium transition-colors",
selectedCategory === null
? "bg-blue-100 text-blue-700"
: "bg-gray-100 text-gray-700 hover:bg-gray-200",
)}
>
<Filter className="h-3 w-3" />
All
</button>
{categories.map((category) => (
<button
key={category}
onClick={() => setSelectedCategory(category === selectedCategory ? null : category)}
className={cn(
"rounded-full px-3 py-1 text-sm font-medium transition-colors whitespace-nowrap",
selectedCategory === category
? "bg-blue-100 text-blue-700"
: "bg-gray-100 text-gray-700 hover:bg-gray-200",
)}
>
{category}
</button>
))}
</div>
<div className="max-h-[60vh] overflow-y-auto p-2">
{filteredResults.length > 0 ? (
<div className="grid gap-1">
{filteredResults.map((result) => (
<motion.button
key={result.id}
whileHover={{ scale: 1.01, backgroundColor: "rgba(243, 244, 246, 1)" }}
onClick={() => {
onResultClick?.(result)
setIsFocused(false)
}}
className="flex items-start gap-3 rounded-lg p-3 text-left transition-colors hover:bg-gray-100"
>
<div className="mt-1 flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-blue-50 text-blue-600">
<Command className="h-4 w-4" />
</div>
<div className="flex-1">
<div className="flex items-center justify-between">
<h4 className="font-medium text-gray-900">{result.title}</h4>
<ArrowRight className="h-4 w-4 text-gray-400" />
</div>
<p className="text-sm text-gray-500">{result.description}</p>
<div className="mt-1">
<span className="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600">
{result.category}
</span>
</div>
</div>
</motion.button>
))}
</div>
) : (
<div className="py-10 text-center">
<p className="text-gray-500">No results found</p>
</div>
)}
</div>
<div className="border-t border-gray-100 p-3">
<div className="flex items-center justify-between text-xs text-gray-500">
<span>
Press <kbd className="rounded border border-gray-200 bg-gray-50 px-1.5 font-mono">โ</kbd>{" "}
<kbd className="rounded border border-gray-200 bg-gray-50 px-1.5 font-mono">โ</kbd> to navigate
</span>
<span>
Press <kbd className="rounded border border-gray-200 bg-gray-50 px-1.5 font-mono">Enter</kbd> to
select
</span>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}