Skip to content

Commit 4d24af5

Browse files
committed
refactor: search.jsx for better search capabilities
1 parent 2959a43 commit 4d24af5

File tree

3 files changed

+122
-91
lines changed

3 files changed

+122
-91
lines changed

src/components/Navbar.jsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,11 @@ const Navbar = ({
9191

9292
<button
9393
onClick={toggleSearch}
94-
className={`group flex items-center gap-2 px-3 py-2 rounded-sm transition-all ${
95-
isSearchVisible
96-
? 'bg-emerald-500 text-black'
97-
: 'text-gray-400 hover:text-white hover:bg-white/5'
98-
}`}
94+
className="group flex items-center gap-2 px-3 py-2 text-gray-400 hover:text-white hover:bg-white/5 rounded-sm transition-all"
9995
aria-label="Toggle Search"
10096
>
101-
<MagnifyingGlassIcon size={20} weight="bold" />
102-
<span className="hidden md:inline text-[10px] font-mono font-bold uppercase tracking-widest">
97+
<MagnifyingGlassIcon size={20} weight="bold" className="group-hover:text-emerald-500 transition-colors" />
98+
<span className="hidden md:inline text-[10px] font-mono font-bold uppercase tracking-widest group-hover:text-emerald-500 transition-colors">
10399
Search
104100
</span>
105101
</button>

src/components/Search.jsx

Lines changed: 92 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
import React, { useState, useEffect, useRef } from 'react';
22
import { Link } from 'react-router-dom';
3-
import { MagnifyingGlassIcon } from '@phosphor-icons/react';
3+
import { MagnifyingGlassIcon, XCircleIcon, HashIcon } from '@phosphor-icons/react';
44
import useSearchableData from '../hooks/useSearchableData';
55
import { filterItems } from '../utils/search';
66

7-
const Search = ({ isVisible }) => {
7+
const categoryColorMap = {
8+
page: 'text-red-400 border-red-400/20 bg-red-400/5',
9+
command: 'text-amber-400 border-amber-400/20 bg-amber-400/5',
10+
post: 'text-blue-400 border-blue-400/20 bg-blue-400/5',
11+
project: 'text-orange-400 border-orange-400/20 bg-orange-400/5',
12+
log: 'text-rose-400 border-rose-400/20 bg-rose-400/5',
13+
app: 'text-teal-400 border-teal-400/20 bg-teal-400/5',
14+
story: 'text-violet-400 border-violet-400/20 bg-violet-400/5',
15+
notebook: 'text-lime-400 border-lime-400/20 bg-lime-400/5',
16+
};
17+
18+
const getCategoryStyle = (type) => {
19+
return categoryColorMap[type] || 'text-gray-400 border-white/10 bg-white/5';
20+
};
21+
22+
const Search = ({ isVisible, toggleSearch }) => {
823
const [searchTerm, setSearchTerm] = useState('');
924
const [searchResults, setSearchResults] = useState([]);
1025
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@@ -20,7 +35,7 @@ const Search = ({ isVisible }) => {
2035

2136
useEffect(() => {
2237
if (searchTerm) {
23-
const results = filterItems(items, searchTerm);
38+
const results = filterItems(items, searchTerm).slice(0, 8); // Limit results for clean look
2439
setSearchResults(results);
2540
setIsDropdownOpen(true);
2641
} else {
@@ -53,44 +68,88 @@ const Search = ({ isVisible }) => {
5368
return (
5469
<div
5570
ref={searchRef}
56-
className="w-full bg-gray-900 py-3 px-4 border-b border-gray-700"
71+
className="w-full bg-[#050505]/95 backdrop-blur-md py-6 px-6 border-b border-white/10 relative z-50"
5772
>
5873
<form
5974
onSubmit={(e) => e.preventDefault()}
60-
className="relative w-full max-w-md mx-auto"
75+
className="relative w-full max-w-2xl mx-auto"
6176
>
62-
<input
63-
ref={inputRef}
64-
type="text"
65-
placeholder={isLoading ? 'Loading...' : 'Search...'}
66-
value={searchTerm}
67-
onChange={(e) => setSearchTerm(e.target.value)}
68-
onFocus={() => setIsDropdownOpen(true)}
69-
className="bg-gray-800 text-white w-full py-2 px-4 pl-10 focus:outline-none focus:ring-2 focus:ring-primary-400 rounded-md"
70-
disabled={isLoading}
71-
/>
72-
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" />
77+
<div className="relative group">
78+
<MagnifyingGlassIcon
79+
size={20}
80+
className={`absolute left-0 top-1/2 transform -translate-y-1/2 transition-colors ${searchTerm ? 'text-emerald-500' : 'text-gray-600 group-focus-within:text-emerald-500'}`}
81+
/>
82+
<input
83+
ref={inputRef}
84+
type="text"
85+
placeholder={isLoading ? 'SYNCHRONIZING REGISTRY...' : 'SEARCH...'}
86+
value={searchTerm}
87+
onChange={(e) => setSearchTerm(e.target.value)}
88+
onFocus={() => setIsDropdownOpen(true)}
89+
className="w-full bg-transparent text-white border-b border-white/10 py-3 pl-10 pr-10 focus:outline-none focus:border-emerald-500 transition-colors font-mono uppercase tracking-[0.2em] text-sm placeholder-gray-800"
90+
disabled={isLoading}
91+
/>
92+
{searchTerm && (
93+
<button
94+
type="button"
95+
onClick={() => setSearchTerm('')}
96+
className="absolute right-0 top-1/2 transform -translate-y-1/2 text-gray-600 hover:text-red-500 transition-colors"
97+
>
98+
<XCircleIcon size={20} weight="fill" />
99+
</button>
100+
)}
101+
</div>
102+
73103
{isDropdownOpen && searchResults.length > 0 && (
74-
<div className="absolute mt-2 w-full max-w-md max-h-96 overflow-y-auto bg-gray-800 border border-gray-700 rounded-md shadow-lg z-50 left-1/2 -translate-x-1/2">
75-
<ul>
104+
<div className="absolute mt-4 w-full bg-[#0a0a0a] border border-white/10 shadow-2xl z-[100] left-0 overflow-hidden">
105+
<div className="flex flex-col">
76106
{searchResults.map((result, index) => (
77-
<li key={index}>
78-
<Link
79-
to={getResultLink(result)}
80-
onClick={() => {
81-
setSearchTerm('');
82-
setIsDropdownOpen(false);
83-
}}
84-
className="block px-4 py-2 text-white hover:bg-gray-700"
85-
>
86-
<span className="font-bold">{result.title}</span>
87-
<span className="text-sm text-gray-400 ml-2">
88-
({result.type})
107+
<Link
108+
key={`${result.slug || result.commandId}-${index}`}
109+
to={getResultLink(result)}
110+
onClick={() => {
111+
setSearchTerm('');
112+
setIsDropdownOpen(false);
113+
if (toggleSearch) toggleSearch();
114+
}}
115+
className="flex items-center justify-between px-6 py-4 border-b border-white/5 hover:bg-emerald-500/10 hover:border-l-4 hover:border-l-emerald-500 transition-all group"
116+
>
117+
<div className="flex flex-col gap-1">
118+
<span className="font-bold text-gray-300 group-hover:text-white uppercase font-mono tracking-tight text-sm truncate">
119+
{result.title}
89120
</span>
90-
</Link>
91-
</li>
121+
{result.description && (
122+
<span className="text-[9px] font-mono uppercase tracking-widest text-gray-600 group-hover:text-gray-400 line-clamp-1">
123+
{result.description}
124+
</span>
125+
)}
126+
</div>
127+
128+
<div className="flex items-center gap-3">
129+
<span className={`px-2 py-0.5 text-[8px] font-mono font-black uppercase tracking-widest border rounded-sm ${getCategoryStyle(result.type)}`}>
130+
{result.type}
131+
</span>
132+
<HashIcon size={14} className="text-gray-800 group-hover:text-emerald-500 transition-colors" />
133+
</div>
134+
</Link>
92135
))}
93-
</ul>
136+
</div>
137+
<div className="bg-white/[0.02] px-6 py-2 border-t border-white/5 flex justify-between items-center">
138+
<span className="text-[8px] font-mono text-gray-700 uppercase tracking-widest">
139+
Showing top {searchResults.length} matches
140+
</span>
141+
<span className="text-[8px] font-mono text-gray-700 uppercase tracking-widest">
142+
Fezcodex_Search_v2
143+
</span>
144+
</div>
145+
</div>
146+
)}
147+
148+
{isDropdownOpen && searchTerm && searchResults.length === 0 && (
149+
<div className="absolute mt-4 w-full bg-[#0a0a0a] border border-white/10 p-8 text-center shadow-2xl z-[100] left-0">
150+
<p className="font-mono text-xs text-gray-600 uppercase tracking-[0.3em]">
151+
No_Results_Found_For: <span className="text-red-500">{searchTerm}</span>
152+
</p>
94153
</div>
95154
)}
96155
</form>

src/hooks/useSearchableData.js

Lines changed: 27 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useMemo } from 'react';
1+
import {useEffect, useMemo, useState} from 'react';
22
import piml from 'piml';
33

44
const useSearchableData = () => {
@@ -102,12 +102,12 @@ const useSearchableData = () => {
102102
const allPosts = postsData.flatMap((item) =>
103103
item.series
104104
? item.series.posts.map((p) => ({
105-
...p,
106-
type: 'post',
107-
title: `${item.title}: ${p.title}`,
108-
path: `/blog/series/${item.slug}/${p.slug}`,
109-
}))
110-
: { ...item, type: 'post', path: `/blog/${item.slug}` },
105+
...p,
106+
type: 'post',
107+
title: `${item.title}: ${p.title}`,
108+
path: `/blog/series/${item.slug}/${p.slug}`,
109+
}))
110+
: {...item, type: 'post', path: `/blog/${item.slug}`},
111111
);
112112

113113
// Process Projects
@@ -126,42 +126,18 @@ const useSearchableData = () => {
126126

127127
// Define static routes and custom commands
128128
const staticRoutes = [
129-
{ title: 'Home', slug: '/', type: 'page', path: '/' },
130-
{ title: 'Blog', slug: '/blog', type: 'page', path: '/blog' },
131-
{
132-
title: 'Projects',
133-
slug: '/projects',
134-
type: 'page',
135-
path: '/projects',
136-
},
137-
{ title: 'About Me', slug: '/about', type: 'page', path: '/about' },
138-
{ title: 'Logs', slug: '/logs', type: 'page', path: '/logs' },
139-
{
140-
title: 'Fezzilla Roadmap',
141-
slug: '/roadmap',
142-
type: 'page',
143-
path: '/roadmap',
144-
},
145-
{
146-
title: 'Timeline',
147-
slug: '/timeline',
148-
type: 'page',
149-
path: '/timeline',
150-
},
151-
{
152-
title: 'Settings',
153-
slug: '/settings',
154-
type: 'page',
155-
path: '/settings',
156-
},
157-
{
158-
title: 'Stories',
159-
slug: '/stories',
160-
type: 'page',
161-
path: '/stories',
162-
},
163-
{ title: 'Apps', slug: '/apps', type: 'page', path: '/apps' },
164-
{ title: 'Random', slug: '/random', type: 'page', path: '/random' },
129+
{title: 'Home', slug: '/', type: 'page', path: '/'},
130+
{title: 'Blog', slug: '/blog', type: 'page', path: '/blog'},
131+
{title: 'Projects', slug: '/projects', type: 'page', path: '/projects'},
132+
{title: 'About Me', slug: '/about', type: 'page', path: '/about'},
133+
{title: 'Logs', slug: '/logs', type: 'page', path: '/logs'},
134+
{title: 'Fezzilla Roadmap', slug: '/roadmap', type: 'page', path: '/roadmap'},
135+
{title: 'Timeline', slug: '/timeline', type: 'page', path: '/timeline'},
136+
{title: 'Settings', slug: '/settings', type: 'page', path: '/settings'},
137+
{title: 'Stories', slug: '/stories', type: 'page', path: '/stories'},
138+
{title: 'Glossary', slug: '/vocab', type: 'page', path: '/vocab'},
139+
{title: 'Apps', slug: '/apps', type: 'page', path: '/apps'},
140+
{title: 'Random', slug: '/random', type: 'page', path: '/random'},
165141
];
166142

167143
const customCommands = [
@@ -205,7 +181,7 @@ const useSearchableData = () => {
205181
type: 'command',
206182
commandId: 'openLinkedIn',
207183
},
208-
{ title: 'Scroll to Top', type: 'command', commandId: 'scrollToTop' },
184+
{title: 'Scroll to Top', type: 'command', commandId: 'scrollToTop'},
209185
{
210186
title: 'Scroll to Bottom',
211187
type: 'command',
@@ -216,7 +192,7 @@ const useSearchableData = () => {
216192
type: 'command',
217193
commandId: 'showSiteStats',
218194
},
219-
{ title: 'Show Version', type: 'command', commandId: 'showVersion' },
195+
{title: 'Show Version', type: 'command', commandId: 'showVersion'},
220196
{
221197
title: 'Go to Latest Post',
222198
type: 'command',
@@ -237,7 +213,7 @@ const useSearchableData = () => {
237213
type: 'command',
238214
commandId: 'digitalRain',
239215
},
240-
{ title: 'Generate Art', type: 'command', commandId: 'generateArt' },
216+
{title: 'Generate Art', type: 'command', commandId: 'generateArt'},
241217
{
242218
title: 'Leet Speak Transformer',
243219
type: 'command',
@@ -263,7 +239,7 @@ const useSearchableData = () => {
263239
type: 'command',
264240
commandId: 'clearLocalStorage',
265241
},
266-
{ title: 'Reload Page', type: 'command', commandId: 'reloadPage' },
242+
{title: 'Reload Page', type: 'command', commandId: 'reloadPage'},
267243
{
268244
title: 'Go to Random App',
269245
type: 'command',
@@ -279,7 +255,7 @@ const useSearchableData = () => {
279255
type: 'command',
280256
commandId: 'openGitHubIssue',
281257
},
282-
{ title: 'Her Daim', type: 'command', commandId: 'herDaim' },
258+
{title: 'Her Daim', type: 'command', commandId: 'herDaim'},
283259
{
284260
title: 'Do a Barrel Roll',
285261
type: 'command',
@@ -290,7 +266,7 @@ const useSearchableData = () => {
290266
type: 'command',
291267
commandId: 'toggleInvertColors',
292268
},
293-
{ title: 'Party Mode', type: 'command', commandId: 'partyMode' },
269+
{title: 'Party Mode', type: 'command', commandId: 'partyMode'},
294270
{
295271
title: 'Toggle Retro Mode',
296272
type: 'command',
@@ -386,7 +362,7 @@ const useSearchableData = () => {
386362
type: 'command',
387363
commandId: 'previousPage',
388364
},
389-
{ title: 'Next Page', type: 'command', commandId: 'nextPage' },
365+
{title: 'Next Page', type: 'command', commandId: 'nextPage'},
390366
];
391367

392368
setItems([
@@ -407,7 +383,7 @@ const useSearchableData = () => {
407383
fetchData();
408384
}, [categories]);
409385

410-
return { items, isLoading };
386+
return {items, isLoading};
411387
};
412388

413389
export default useSearchableData;

0 commit comments

Comments
 (0)