236 lines
6.7 KiB
TypeScript
236 lines
6.7 KiB
TypeScript
"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 isApplePlatform =
|
|
typeof navigator !== "undefined" &&
|
|
/Mac|iPhone|iPad|iPod/i.test(navigator.platform);
|
|
const shortcutLabel = isApplePlatform ? "Cmd+K" : "Ctrl+K";
|
|
const shortcutCapsule = isApplePlatform ? "⌘K" : "Ctrl+K";
|
|
|
|
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");
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => setDebouncedQuery(query), 300);
|
|
return () => clearTimeout(timer);
|
|
}, [query]);
|
|
|
|
const { data: searchData, isLoading } = useSearchTeams({
|
|
q: debouncedQuery,
|
|
});
|
|
|
|
const teams: TeamDto[] = searchData?.data ?? [];
|
|
|
|
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);
|
|
}, []);
|
|
|
|
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" }}
|
|
>
|
|
<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={`Takim ara... (${shortcutLabel})`}
|
|
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"
|
|
>
|
|
{shortcutCapsule}
|
|
</Text>
|
|
</Flex>
|
|
|
|
{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">
|
|
Sonuc bulunamadi
|
|
</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>
|
|
);
|
|
}
|