61 lines
1.8 KiB
TypeScript
61 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
import { motion, AnimatePresence } from "framer-motion";
|
|
import ActionSearchBar from "@/components/action-search-bar";
|
|
|
|
export default function FloatingActionSearchBar() {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const searchBarRef = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleKeyDown = (event: KeyboardEvent) => {
|
|
if ((event.ctrlKey || event.metaKey) && event.key === "k") {
|
|
event.preventDefault();
|
|
setIsOpen((prev) => !prev);
|
|
} else if (event.key === "Escape") {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (isOpen && searchBarRef.current) {
|
|
searchBarRef.current.focus();
|
|
}
|
|
}, [isOpen]);
|
|
|
|
return (
|
|
<AnimatePresence>
|
|
{isOpen && (
|
|
<motion.div
|
|
initial={{ opacity: 0 }}
|
|
animate={{ opacity: 1 }}
|
|
exit={{ opacity: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="fixed inset-0 z-50 flex items-start justify-center bg-background/80 backdrop-blur-sm p-4 sm:p-6 md:p-8 lg:p-40"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
<motion.div
|
|
initial={{ scale: 0.95, y: -20 }}
|
|
animate={{ scale: 1, y: 0 }}
|
|
exit={{ scale: 0.95, y: -20 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="w-full max-w-lg sm:max-w-xl md:max-w-2xl"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<ActionSearchBar
|
|
ref={searchBarRef}
|
|
autoFocus={true}
|
|
isFloating={true}
|
|
/>
|
|
</motion.div>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|