backsplash-app
Version:
An AI powered wallpaper app.
107 lines (90 loc) • 3.2 kB
text/typescript
import { useState, useMemo, useEffect } from "react";
import Fuse from "fuse.js";
import { Category } from "@/types/categories";
interface SearchableStyle {
categoryId: string;
categoryTitle: string;
style: {
label: string;
description?: string;
preview_url?: string;
};
}
export interface UseModeSearchProps {
wallpaperCategories: Category[];
setExpandedCategories: (categories: string[] | ((prevCategories: string[]) => string[])) => void;
}
export interface UseModeSearchReturn {
searchQuery: string;
setSearchQuery: (query: string) => void;
filteredCategories: Category[];
handleSearchChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
clearSearch: () => void;
}
export function useModeSearch({ wallpaperCategories, setExpandedCategories }: UseModeSearchProps): UseModeSearchReturn {
const [searchQuery, setSearchQuery] = useState<string>("");
// Create fuse instance for search
const fuse = useMemo(() => {
// Create a flat list of all styles with their category info for searching
const searchableItems: SearchableStyle[] = wallpaperCategories.flatMap((category) =>
category.styles.map((style) => ({
categoryId: category.id,
categoryTitle: category.title,
style,
})),
);
return new Fuse(searchableItems, {
keys: ["categoryTitle", "style.label", "style.description"],
threshold: 0.3,
includeScore: true,
});
}, [wallpaperCategories]);
// Filter categories based on search query
const filteredCategories = useMemo(() => {
if (!searchQuery.trim()) {
return wallpaperCategories;
}
const searchResults = fuse.search(searchQuery);
const matchedCategoryIds = new Set(searchResults.map((result) => result.item.categoryId));
// Return categories that have matching styles, but only include the matching styles
return wallpaperCategories
.map((category) => {
if (!matchedCategoryIds.has(category.id)) {
return null;
}
// Include only styles that match the search
const matchedStyleIds = new Set(
searchResults
.filter((result) => result.item.categoryId === category.id)
.map((result) => result.item.style.label),
);
const filteredStyles = category.styles.filter((style) => matchedStyleIds.has(style.label));
return {
...category,
styles: filteredStyles,
};
})
.filter(Boolean) as Category[];
}, [wallpaperCategories, searchQuery, fuse]);
// Automatically expand categories with search results
useEffect(() => {
if (searchQuery.trim() && filteredCategories.length > 0) {
setExpandedCategories(filteredCategories.map((category) => category.id));
}
}, [searchQuery, filteredCategories, setExpandedCategories]);
// Handle search input change
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value);
};
// Clear search function
const clearSearch = () => {
setSearchQuery("");
};
return {
searchQuery,
setSearchQuery,
filteredCategories,
handleSearchChange,
clearSearch,
};
}