From ce47bcd6e1a6c122dc8303228fdd64cc41e8e1e0 Mon Sep 17 00:00:00 2001 From: Yash Vardhan Date: Wed, 19 Aug 2026 21:40:06 +0530 Subject: [PATCH] feat(search): extract Command Palette into reusable component and add typo tolerance Refactored TopicLayout and LandingPage to utilize a unified SearchPalette component. Implemented a dual-threshold Fuse.js search to provide 'Did you mean?' suggestions for improved UX when exact matches fail. --- frontend/app/page.tsx | 313 ++++++++++-------------------------------- 1 file changed, 72 insertions(+), 241 deletions(-) diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index b60775f..baba894 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,12 +1,22 @@ "use client"; -import { useTheme } from "../src/context/ThemeContext"; +/** + * @fileoverview Primary entry point and directory index for the application. + * Composes the category grid and injects the global SearchPalette for + * instant top-level navigation. + * + * @author Yash Vardhan + */ + import { useState, useMemo, useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; +import { useTheme } from "../src/context/ThemeContext"; import { FaGithub, FaLinkedin } from "react-icons/fa"; -import Fuse from "fuse.js"; -import { Command } from "cmdk"; + +// Import the unified search architecture +import SearchPalette, { useSearchOSKey, SearchItem } from "../app/components/SearchPalette"; + import { Binary, Braces, @@ -20,8 +30,6 @@ import { Sparkles, TerminalSquare, TestTube, - X, - Monitor, } from "lucide-react"; export const REFERENCE_DATA = [ @@ -130,53 +138,14 @@ export const REFERENCE_DATA = [ }, ]; -function HighlightText({ - text, - matches, - fieldKey, - highlightStyle, -}: { - text: string; - matches?: readonly any[]; - fieldKey: string; - highlightStyle: string; -}) { - const match = matches?.find((m) => m.key === fieldKey); - if (!match || !match.indices || match.indices.length === 0) return {text}; - - const elements: React.ReactNode[] = []; - let lastIndex = 0; - - match.indices.forEach(([start, end]: [number, number], i: number) => { - if (start > lastIndex) { - elements.push({text.substring(lastIndex, start)}); - } - elements.push( - - {text.substring(start, end + 1)} - - ); - lastIndex = end + 1; - }); - - if (lastIndex < text.length) { - elements.push({text.substring(lastIndex)}); - } - - return <>{elements}; -} - export default function LandingPage() { const { darkMode, toggleTheme } = useTheme(); const router = useRouter(); const [cmdOpen, setCmdOpen] = useState(false); - const [docSearch, setDocSearch] = useState(""); - - const [isMounted, setIsMounted] = useState(false); - const [modifierKey, setModifierKey] = useState("⌘"); + const { modifierKey, isMounted } = useSearchOSKey(); - const theme = { + const themeClasses = { page: darkMode ? "bg-[#050505] text-[#f5f5f5]" : "bg-[#F5F3EE] text-[#111111]", header: darkMode ? "bg-[#050505]/85" : "bg-[#fcfbf9]/85", panel: darkMode ? "bg-[#0d0d0d]" : "bg-white", @@ -190,23 +159,16 @@ export default function LandingPage() { iconBoxHover: darkMode ? "group-hover:border-[#C699FF]/40 group-hover:text-[#C699FF] group-hover:bg-[#C699FF]/10" : "group-hover:border-[#6f45d6]/40 group-hover:text-[#6f45d6] group-hover:bg-[#6f45d6]/10", - highlightText: darkMode ? "bg-[#C699FF]/25 text-[#C699FF]" : "bg-[#6f45d6]/15 text-[#6f45d6]", }; - useEffect(() => { - setIsMounted(true); - if (typeof window !== "undefined") { - const isMac = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform); - setModifierKey(isMac ? "⌘" : "Ctrl "); - } - }, []); - + /** + * Captures global keyboard shortcuts to toggle the Command Palette. + * Separated from the palette itself to ensure the trigger works globally. + * + * - Yash Vardhan + */ useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - if (event.key === "Escape") { - setCmdOpen(false); - return; - } const isModifier = event.metaKey || event.ctrlKey; if (!isModifier || event.key.toLowerCase() !== "k") return; @@ -218,177 +180,49 @@ export default function LandingPage() { return () => window.removeEventListener("keydown", handleKeyDown); }, []); - useEffect(() => { - if (cmdOpen) { - document.body.style.overflow = "hidden"; - } else { - document.body.style.overflow = ""; - } - return () => { - document.body.style.overflow = ""; - }; - }, [cmdOpen]); - - const flattenedItems = useMemo(() => { - return REFERENCE_DATA.flatMap(cat => - cat.items.map(item => ({ - ...item, - categoryName: cat.category, - categoryIcon: cat.icon + /** + * Transforms the static REFERENCE_DATA into the strict SearchItem contract + * required by the global SearchPalette implementation. + * + * - Yash Vardhan + */ + const searchData = useMemo(() => { + return REFERENCE_DATA.flatMap((cat) => + cat.items.map((item) => ({ + id: item.name, + title: item.name, + description: item.desc, + type: "topic", + href: item.href, + icon: item.icon, + category: cat.category, })) ); }, []); - const fuse = useMemo(() => new Fuse(flattenedItems, { - keys: ["name", "desc"], - includeMatches: true, - threshold: 0.3, - ignoreLocation: true, - }), [flattenedItems]); - - const searchResults = useMemo(() => { - if (!docSearch.trim()) return []; - return fuse.search(docSearch.trim()).slice(0, 12); - }, [docSearch, fuse]); - - const searchLower = docSearch.trim().toLowerCase(); - const showHomeAction = searchLower.length > 0 && ("home".includes(searchLower) || "directory".includes(searchLower) || "index".includes(searchLower)); + const handleSearchSelect = (item: SearchItem) => { + if (item.type === "topic" && item.href) { + router.push(item.href); + } + }; return ( -
+
- {/* CMDK GLOBAL COMMAND PALETTE OVERLAY */} - {cmdOpen && ( -
setCmdOpen(false)} - > -
e.stopPropagation()} - className={`w-full max-w-2xl overflow-hidden rounded-xl border shadow-2xl mx-4 ${theme.panel} ${theme.border}`} - > - -
- - { - if (e.key === "Escape") { - e.preventDefault(); - setCmdOpen(false); - } - }} - placeholder="Search algorithms, frameworks, or concepts..." - className="flex h-14 w-full bg-transparent text-sm outline-none placeholder:text-current placeholder:opacity-50 text-current" - /> - -
- - - {docSearch.trim().length > 0 && searchResults.length === 0 && !showHomeAction && ( - - No results found for "{docSearch}". - - )} - - {/* Default Quick Actions when search is empty */} - {!docSearch.trim() && ( - Quick Actions
}> - { - router.push("/"); - setCmdOpen(false); - }} - className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-current/10 aria-selected:bg-current/10 text-current" - > - - Go to Directory (Home) - - { toggleTheme(); setCmdOpen(false); }} - className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-current/10 aria-selected:bg-current/10 text-current" - > - - Switch to {darkMode ? "Light" : "Dark"} Mode - - { - window.open("https://github.com/yash-pluto/refme", "_blank"); - setCmdOpen(false); - }} - className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-current/10 aria-selected:bg-current/10 text-current" - > - - Go to GitHub Repo - - - )} - - {/* Specific match for "Home" or "Directory" */} - {showHomeAction && ( - Navigation
}> - { - router.push("/"); - setDocSearch(""); - setCmdOpen(false); - }} - className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-current/10 aria-selected:bg-current/10 text-current" - > - - Go to Directory (Home) - - - )} - - {/* General Topic Results */} - {docSearch.trim().length > 0 && searchResults.length > 0 && ( - Topics
}> - {searchResults.map(({ item, matches }) => ( - { - router.push(item.href); - setDocSearch(""); - setCmdOpen(false); - }} - className="flex cursor-pointer items-center justify-between gap-3 rounded-lg px-3 py-3 text-sm transition-colors hover:bg-current/10 aria-selected:bg-current/10 text-current" - > -
-
- {item.icon} -
-
-
- -
-
- {item.desc} -
-
-
-
- {item.categoryName} -
-
- ))} - - )} - - -
- - )} + {/* + CENTRALIZED SEARCH INJECTION + Replaces the old inline fuse.js/cmdk logic to ensure feature parity + and typo-tolerance across the entire application. + */} + setCmdOpen(false)} + searchData={searchData} + onSelect={handleSearchSelect} + placeholder="Search algorithms, frameworks, or concepts..." + /> - {/* HEADER */} -
+
@@ -397,28 +231,27 @@ export default function LandingPage() {
- RefMe + RefMe
- {/* HERO SECTION */}

The developer's
quick-reference guide.

-

+

Find the syntax, patterns, and implementation notes you need without digging through scattered docs. Built for speed and focus.

@@ -426,11 +259,11 @@ export default function LandingPage() {
- {/* CATEGORY GRIDS */}
{REFERENCE_DATA.map((category) => (
- + {category.icon}

@@ -462,11 +294,11 @@ export default function LandingPage() {
{item.icon}
@@ -474,7 +306,7 @@ export default function LandingPage() { {item.name}

-

+

{item.desc}

@@ -485,16 +317,15 @@ export default function LandingPage() {
- {/* FOOTER */} -