Files
iddaai-fe/src/components/search/global-search.tsx
T
2026-04-19 13:22:48 +03:00

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>
);
}