Files
AthenaFE/components/features/navigation/HeaderControls.tsx
T
2025-11-02 10:08:56 +01:00

113 lines
3.3 KiB
TypeScript

import { View, Animated } from "react-native";
import { useState, useMemo, useEffect, useRef } from "react";
import { debounce } from "lodash";
import { router, useLocalSearchParams, usePathname } from "expo-router";
import { SearchBar } from "@/components/ui/SearchBar";
import { SearchDropdown } from "@/components/ui/SearchDropdown";
import { SizeEnum } from "@/constants/sizeEnum";
import { UserButton } from "@/components/auth/UserButton";
import { SearchResult } from "@/store/api/searchApi";
export const HeaderControls = () => {
const [searchQuery, setSearchQuery] = useState("");
const params = useLocalSearchParams<{ q?: string }>();
const pathname = usePathname();
const searchWidth = useRef(new Animated.Value(250)).current;
const isOnSearchPage =
typeof pathname === "string" && pathname.includes("/search");
const debouncedSearch = useMemo(
() =>
debounce((query: string) => {
if (!query.trim()) return;
}, 300),
[]
);
useEffect(() => {
debouncedSearch(searchQuery);
// Animate width based on whether user is typing
// Use spring animation for smoother feel
Animated.spring(searchWidth, {
toValue: searchQuery.trim() ? 500 : 250,
useNativeDriver: false,
tension: 100,
friction: 10,
}).start();
return debouncedSearch.cancel;
}, [searchQuery, debouncedSearch, searchWidth]);
useEffect(() => {
if (!isOnSearchPage) return;
const q = (params.q ?? "").toString();
setSearchQuery(q);
}, [isOnSearchPage, params.q]);
useEffect(() => {
if (!isOnSearchPage) return;
const current = (params.q ?? "").toString();
if (searchQuery.trim() !== "" && searchQuery !== current) {
router.setParams({ q: searchQuery });
}
}, [isOnSearchPage, searchQuery, params.q]);
const handleResultSelect = (result: SearchResult) => {
const q = result.title || "";
const encodedQ = encodeURIComponent(q);
// Immediately clear search and collapse animation to prevent lag
setSearchQuery("");
Animated.timing(searchWidth, {
toValue: 250,
duration: 200,
useNativeDriver: false,
}).start();
// Navigate after starting animation
switch (result.type) {
case "admin":
router.push(`/(tabs)/accounts/admins?q=${encodedQ}`);
break;
case "user":
router.push(`/(tabs)/accounts/users?q=${encodedQ}`);
break;
case "todo":
router.push(`/(tabs)/accounts/todos?q=${encodedQ}`);
break;
default: {
router.push(`/(tabs)/search?q=${encodedQ}`);
break;
}
}
};
const handleViewAllResults = (query: string) => {
router.push(`/(tabs)/search?q=${encodeURIComponent(query)}`);
};
return (
<View className="flex-row items-center justify-end px-4 absolute right-0 top-[6px] h-8">
<Animated.View
className="relative"
style={{ zIndex: 1000, width: searchWidth }}
id="search-container"
>
<SearchBar
searchQuery={searchQuery}
onChange={setSearchQuery}
placeholderText="Search"
size={SizeEnum.Small}
/>
<SearchDropdown
searchQuery={searchQuery}
onResultSelect={handleResultSelect}
onViewAllResults={handleViewAllResults}
/>
</Animated.View>
<UserButton />
</View>
);
};