From 647c5b26677c4b912e3029cb77139e1ff82d8dd5 Mon Sep 17 00:00:00 2001 From: "Somayeh.Ghanavati" Date: Wed, 22 Jul 2026 13:34:24 +0330 Subject: [PATCH] feat(spotlight): implement Fuse.js fuzzy search and action grouping with mock data --- src/App.tsx | 7 +- src/SpotlightTestDemo.tsx | 132 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 src/SpotlightTestDemo.tsx diff --git a/src/App.tsx b/src/App.tsx index 2087680..ff85361 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,12 @@ import { MantineProvider } from "@mantine/core"; import "@mantine/core/styles.css"; import "@mantine/spotlight/styles.css"; +import { SpotlightTestDemo } from "./SpotlightTestDemo"; export default function App() { - return ; + return ( + + + + ); } diff --git a/src/SpotlightTestDemo.tsx b/src/SpotlightTestDemo.tsx new file mode 100644 index 0000000..64598f0 --- /dev/null +++ b/src/SpotlightTestDemo.tsx @@ -0,0 +1,132 @@ +import { useEffect, useMemo, useState } from "react"; +import { Button } from "@mantine/core"; +import { Spotlight, spotlight } from "@mantine/spotlight"; +import type { + SpotlightActionData, + SpotlightActionGroupData, + SpotlightFilterFunction, +} from "@mantine/spotlight"; +import Fuse from "fuse.js"; + +interface Product { + id: number; + title: string; + description: string; + price: number; + category: string; + brand: string; +} + +function generate5kMockProducts(): Product[] { + const categories = [ + "Electronics", + "Fashion", + "Home & Living", + "Beauty & Care", + "Sports & Outdoors", + "Automotive", + ]; + const brands = ["Sony", "Samsung", "Apple", "Nike", "Adidas", "IKEA"]; + + return Array.from({ length: 5000 }, (_, index) => { + const id = index + 1; + const category = categories[index % categories.length]; + const brand = brands[index % brands.length]; + + return { + id, + title: `${brand} High-End Item #${id}`, + description: `Premium ${category.toLowerCase()} product manufactured by ${brand}. High availability in stock.`, + price: Math.floor(Math.random() * 900) + 10, + category, + brand, + }; + }); +} + +function groupActionsByCategory( + actions: SpotlightActionData[], +): SpotlightActionGroupData[] { + const groupsMap = actions.reduce>( + (acc, action) => { + const groupName = (action as any)._category || "OTHER"; + if (!acc[groupName]) { + acc[groupName] = []; + } + acc[groupName].push(action); + return acc; + }, + {}, + ); + + return Object.entries(groupsMap).map(([group, groupActions]) => ({ + group: group.toUpperCase(), + actions: groupActions, + })); +} + +export function SpotlightTestDemo() { + const [query, setQuery] = useState(""); + const [flatActions, setFlatActions] = useState([]); + + useEffect(() => { + setTimeout(() => { + const products = generate5kMockProducts(); + + const actions: SpotlightActionData[] = products.map((product) => ({ + id: `prod-${product.id}`, + label: product.title, + description: `${product.brand} | $${product.price} - ${product.description}`, + _category: product.category, + })); + + setFlatActions(actions); + }, 50); + }, []); + + const fuseIndex = useMemo(() => { + if (flatActions.length === 0) return null; + + return new Fuse(flatActions, { + keys: ["label", "description", "_category"], + threshold: 0.5, + distance: 100, + minMatchCharLength: 1, + ignoreLocation: true, + useExtendedSearch: false, + }); + }, [flatActions]); + + const customFuzzyFilter: SpotlightFilterFunction = (searchQuery) => { + const trimmed = searchQuery.trim(); + + if (!trimmed) { + return groupActionsByCategory(flatActions.slice(0, 20)); + } + + if (!fuseIndex) return []; + + const results = fuseIndex.search(trimmed); + const matchedItems = results.slice(0, 50).map((r) => r.item); + + return groupActionsByCategory(matchedItems); + }; + + return ( + <> + + + + ); +}