113 lines
3.3 KiB
TypeScript
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>
|
|
);
|
|
};
|