This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Input,
|
||||
Text,
|
||||
VStack,
|
||||
HStack,
|
||||
Image,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import { useColorModeValue } from "@/components/ui/color-mode";
|
||||
import { useSearchTeams } from "@/lib/api/leagues/use-hooks";
|
||||
import { useRouter } from "@/i18n/navigation";
|
||||
import { LuSearch, LuX } from "react-icons/lu";
|
||||
import type { TeamDto } from "@/lib/api/leagues/types";
|
||||
|
||||
export default function GlobalSearch() {
|
||||
const [query, setQuery] = useState("");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [debouncedQuery, setDebouncedQuery] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const bg = useColorModeValue("white", "gray.900");
|
||||
const borderColor = useColorModeValue("gray.200", "gray.700");
|
||||
const hoverBg = useColorModeValue("gray.50", "gray.800");
|
||||
const inputBg = useColorModeValue("gray.50", "gray.800");
|
||||
|
||||
// Debounce search input
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setDebouncedQuery(query), 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [query]);
|
||||
|
||||
const { data: searchData, isLoading } = useSearchTeams({
|
||||
q: debouncedQuery,
|
||||
});
|
||||
|
||||
const teams: TeamDto[] = searchData?.data ?? [];
|
||||
|
||||
// Close dropdown on outside click
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(e.target as Node)
|
||||
) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Keyboard shortcut: Ctrl+K to focus
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
setIsOpen(true);
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
setIsOpen(false);
|
||||
inputRef.current?.blur();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => document.removeEventListener("keydown", handleKeyDown);
|
||||
}, []);
|
||||
|
||||
const handleTeamClick = useCallback(
|
||||
(team: TeamDto) => {
|
||||
setIsOpen(false);
|
||||
setQuery("");
|
||||
router.push(`/teams/${team.id}`);
|
||||
},
|
||||
[router],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box ref={containerRef} position="relative" w={{ base: "full", lg: "280px" }}>
|
||||
{/* Search Input */}
|
||||
<Flex
|
||||
align="center"
|
||||
bg={inputBg}
|
||||
borderRadius="full"
|
||||
border="1px solid"
|
||||
borderColor={isOpen ? "primary.400" : borderColor}
|
||||
px={3}
|
||||
py={1}
|
||||
transition="all 0.2s"
|
||||
_focusWithin={{
|
||||
borderColor: "primary.400",
|
||||
shadow: "0 0 0 1px var(--chakra-colors-primary-400)",
|
||||
}}
|
||||
>
|
||||
<LuSearch
|
||||
style={{ flexShrink: 0, opacity: 0.5, width: 16, height: 16 }}
|
||||
/>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value);
|
||||
setIsOpen(true);
|
||||
}}
|
||||
onFocus={() => query.length >= 2 && setIsOpen(true)}
|
||||
placeholder="Takım ara... (Ctrl+K)"
|
||||
variant="flushed"
|
||||
size="sm"
|
||||
px={2}
|
||||
fontSize="sm"
|
||||
/>
|
||||
{query && (
|
||||
<Box
|
||||
as="button"
|
||||
onClick={() => {
|
||||
setQuery("");
|
||||
setIsOpen(false);
|
||||
}}
|
||||
cursor="pointer"
|
||||
opacity={0.5}
|
||||
_hover={{ opacity: 1 }}
|
||||
flexShrink={0}
|
||||
>
|
||||
<LuX style={{ width: 14, height: 14 }} />
|
||||
</Box>
|
||||
)}
|
||||
<Text
|
||||
display={{ base: "none", lg: "block" }}
|
||||
fontSize="xs"
|
||||
color="fg.muted"
|
||||
flexShrink={0}
|
||||
bg={useColorModeValue("gray.100", "gray.700")}
|
||||
px={1.5}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
fontFamily="mono"
|
||||
>
|
||||
⌘K
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{/* Dropdown Results */}
|
||||
{isOpen && debouncedQuery.length >= 2 && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="calc(100% + 8px)"
|
||||
left={0}
|
||||
right={0}
|
||||
bg={bg}
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="xl"
|
||||
shadow="lg"
|
||||
zIndex={100}
|
||||
maxH="360px"
|
||||
overflowY="auto"
|
||||
py={2}
|
||||
>
|
||||
{isLoading ? (
|
||||
<Flex justify="center" py={6}>
|
||||
<Spinner size="sm" color="primary.500" />
|
||||
</Flex>
|
||||
) : teams.length === 0 ? (
|
||||
<Flex justify="center" py={6}>
|
||||
<Text fontSize="sm" color="fg.muted">
|
||||
Sonuç bulunamadı
|
||||
</Text>
|
||||
</Flex>
|
||||
) : (
|
||||
<VStack gap={0} align="stretch">
|
||||
{teams.map((team: TeamDto) => (
|
||||
<HStack
|
||||
key={team.id}
|
||||
px={3}
|
||||
py={2.5}
|
||||
cursor="pointer"
|
||||
_hover={{ bg: hoverBg }}
|
||||
transition="background 0.15s"
|
||||
onClick={() => handleTeamClick(team)}
|
||||
gap={3}
|
||||
>
|
||||
{team.logo ? (
|
||||
<Image
|
||||
src={team.logo}
|
||||
alt={team.name}
|
||||
boxSize="32px"
|
||||
objectFit="contain"
|
||||
borderRadius="md"
|
||||
flexShrink={0}
|
||||
/>
|
||||
) : (
|
||||
<Flex
|
||||
boxSize="32px"
|
||||
bg="primary.subtle"
|
||||
borderRadius="md"
|
||||
align="center"
|
||||
justify="center"
|
||||
flexShrink={0}
|
||||
>
|
||||
<Text fontSize="sm" fontWeight="bold" color="primary.fg">
|
||||
{team.name?.charAt(0) || "T"}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
<Box flex={1} minW={0}>
|
||||
<Text fontSize="sm" fontWeight="600" truncate>
|
||||
{team.name}
|
||||
</Text>
|
||||
{team.country && (
|
||||
<Text fontSize="xs" color="fg.muted" truncate>
|
||||
{team.country}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
</HStack>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user