From 80ac668335e33ab4e8c9ee35992735a4bf7addb2 Mon Sep 17 00:00:00 2001 From: poyrazavsever Date: Tue, 10 Mar 2026 22:50:02 +0300 Subject: [PATCH] feat: enhance search command functionality and update command palette groups with new data sources --- components/search-command.tsx | 43 +++++++-- lib/command-palette-links.ts | 159 +++++++++++++++++++++++++++++++--- 2 files changed, 183 insertions(+), 19 deletions(-) diff --git a/components/search-command.tsx b/components/search-command.tsx index ff621de..5fbb467 100644 --- a/components/search-command.tsx +++ b/components/search-command.tsx @@ -2,7 +2,7 @@ import { Icon } from "@iconify/react"; import { useRouter } from "next/navigation"; -import { useEffect } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { CommandPalette, CommandPaletteContent, @@ -28,6 +28,13 @@ type SearchCommandProps = { export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { const router = useRouter(); const shortcut = useKeyboardShortcutLabel(); + const [query, setQuery] = useState(""); + const handleOpenChange = useCallback((nextOpen: boolean) => { + if (!nextOpen) { + setQuery(""); + } + onOpenChange(nextOpen); + }, [onOpenChange]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { @@ -35,15 +42,38 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { if (!isShortcut) return; event.preventDefault(); - onOpenChange(!open); + handleOpenChange(!open); }; window.addEventListener("keydown", onKeyDown); return () => window.removeEventListener("keydown", onKeyDown); - }, [open, onOpenChange]); + }, [open, handleOpenChange]); + + const filteredGroups = useMemo(() => { + const value = query.trim().toLowerCase(); + if (!value) return COMMAND_PALETTE_GROUPS; + + const tokens = value.split(/\s+/).filter(Boolean); + + return COMMAND_PALETTE_GROUPS.map((group) => ({ + ...group, + items: group.items.filter((item) => { + const haystack = [ + item.label, + item.href, + item.icon, + ...(item.keywords ?? []), + ] + .join(" ") + .toLowerCase(); + + return tokens.every((token) => haystack.includes(token)); + }), + })).filter((group) => group.items.length > 0); + }, [query]); const handleCommandSelect = (item: PaletteItem) => { - onOpenChange(false); + handleOpenChange(false); if (item.external) { window.open(item.href, "_blank", "noopener,noreferrer"); @@ -54,15 +84,16 @@ export function SearchCommand({ open, onOpenChange }: SearchCommandProps) { }; return ( - + No results found. - {COMMAND_PALETTE_GROUPS.map((group, index) => ( + {filteredGroups.map((group, index) => (
{index > 0 && } diff --git a/lib/command-palette-links.ts b/lib/command-palette-links.ts index df5dece..585121a 100644 --- a/lib/command-palette-links.ts +++ b/lib/command-palette-links.ts @@ -1,3 +1,12 @@ +import { BLOG_ARTICLES, BLOG_CATEGORIES } from "@/data/blog"; +import { BLOG_DETAILS } from "@/data/blog-detail"; +import { certificates } from "@/data/certificates"; +import { EDUCATION } from "@/data/education"; +import { EXPERIENCE } from "@/data/experience"; +import { FIGMA_TEMPLATES, MOBILE_APPS, WEB_APPS } from "@/data/projects"; +import { REFERENCES } from "@/data/references"; +import { VOLUNTEER_COMMUNITY_ITEMS } from "@/data/volunteer-community"; +import { YOUTUBE_VIDEO_LINKS } from "@/data/youtube-videos"; import { NAV_LINKS, SOCIAL_LINKS } from "@/lib/links"; export type CommandPaletteItem = { @@ -6,6 +15,7 @@ export type CommandPaletteItem = { href: string; icon: string; external?: boolean; + keywords?: string[]; }; export type CommandPaletteGroup = { @@ -14,26 +24,149 @@ export type CommandPaletteGroup = { items: CommandPaletteItem[]; }; +const navigationItems: CommandPaletteItem[] = NAV_LINKS.map((item) => ({ + id: item.id, + label: item.label, + href: item.href, + icon: "mdi:compass-outline", + keywords: [item.label, item.href, "page", "navigation"], +})); + +const socialItems: CommandPaletteItem[] = SOCIAL_LINKS.map((item) => ({ + id: item.id, + label: item.label, + href: item.href, + icon: item.icon, + external: true, + keywords: [item.label, "social", "profile"], +})); + +const blogItems: CommandPaletteItem[] = [ + ...BLOG_DETAILS.map((item) => ({ + id: `blog-detail-${item.slug}`, + label: item.title, + href: `/blog/${item.slug}`, + icon: "mdi:post-outline", + keywords: [item.category, item.author, item.excerpt, "blog", "article"], + })), + ...BLOG_ARTICLES.map((item, index) => ({ + id: `blog-article-${item.id}-${index}`, + label: item.title, + href: item.href, + icon: "mdi:file-document-outline", + keywords: [item.category, item.excerpt, item.readTime, "blog", "article"], + })), + ...BLOG_CATEGORIES.map((item) => ({ + id: `blog-category-${item}`, + label: `Category: ${item}`, + href: "/blog", + icon: "mdi:tag-outline", + keywords: [item, "blog", "category"], + })), +]; + +const aboutItems: CommandPaletteItem[] = [ + ...EDUCATION.map((item) => ({ + id: `education-${item.id}`, + label: item.title, + href: "/about", + icon: "mdi:school-outline", + keywords: [item.institution, item.period, item.description, "education", "about"], + })), + ...EXPERIENCE.map((item) => ({ + id: `experience-${item.id}`, + label: item.role, + href: "/about", + icon: "mdi:briefcase-outline", + keywords: [item.company, item.period, "experience", "about"], + })), + ...certificates.map((item, index) => ({ + id: `certificate-${index}`, + label: item.name, + href: "/about", + icon: "mdi:certificate-outline", + keywords: [item.organization, item.date, item.category, item.description, "certificate"], + })), + ...REFERENCES.map((item) => ({ + id: `reference-${item.id}`, + label: `Reference: ${item.author}`, + href: "/about/references", + icon: "mdi:account-voice-outline", + keywords: [item.role, item.quote, "reference", "testimonial"], + })), + ...VOLUNTEER_COMMUNITY_ITEMS.map((item) => ({ + id: `volunteer-${item.id}`, + label: item.title, + href: "/about/volunteer-community", + icon: "mdi:hand-heart-outline", + keywords: [item.timeline, item.focus, item.link ?? "", "volunteer", "community"], + })), +]; + +const projectItems: CommandPaletteItem[] = [ + ...MOBILE_APPS.map((item) => ({ + id: `mobile-project-${item.id}`, + label: item.title, + href: "/projects", + icon: "mdi:cellphone", + keywords: [item.description, item.badge ?? "", "mobile", "project"], + })), + ...WEB_APPS.map((item) => ({ + id: `web-project-${item.id}`, + label: item.title, + href: item.href ?? "/projects", + icon: "mdi:web", + external: Boolean(item.href), + keywords: [item.description, item.badge ?? "", "web", "project"], + })), + ...FIGMA_TEMPLATES.map((item) => ({ + id: `figma-project-${item.id}`, + label: item.title, + href: item.href ?? "/projects", + icon: "mdi:figma", + external: Boolean(item.href), + keywords: [item.description, item.badge ?? "", "figma", "template"], + })), +]; + +const contentItems: CommandPaletteItem[] = YOUTUBE_VIDEO_LINKS.map((item, index) => ({ + id: `youtube-video-${index + 1}`, + label: `YouTube Video ${index + 1}`, + href: item, + icon: "mdi:youtube", + external: true, + keywords: [item, "youtube", "video", "content"], +})); + export const COMMAND_PALETTE_GROUPS: CommandPaletteGroup[] = [ { id: "navigation", heading: "Navigation", - items: NAV_LINKS.map((item) => ({ - id: item.id, - label: item.label, - href: item.href, - icon: "mdi:compass-outline", - })), + items: navigationItems, + }, + { + id: "blog-data", + heading: "Blog Data", + items: blogItems, + }, + { + id: "about-data", + heading: "About Data", + items: aboutItems, + }, + { + id: "projects-data", + heading: "Projects Data", + items: projectItems, + }, + { + id: "content-data", + heading: "Content Data", + items: contentItems, }, { id: "social", heading: "Social", - items: SOCIAL_LINKS.map((item) => ({ - id: item.id, - label: item.label, - href: item.href, - icon: item.icon, - external: true, - })), + items: socialItems, }, ] as const;