project setup
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user