Skip to content

Commit bc28727

Browse files
committed
feat(frontend): add SearchBox component with glass styling
- Glass morphism styling using v2 design tokens - ⌘K / Ctrl+K keyboard shortcut to focus - Animated typewriter placeholder effect - Loading state with spinner - Integrated into SearchPanel Closes #111
1 parent 2821145 commit bc28727

3 files changed

Lines changed: 247 additions & 91 deletions

File tree

Lines changed: 74 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,82 @@
1-
import { useState } from 'react'
2-
import { toast } from 'sonner'
3-
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4-
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
5-
import type { SearchResult } from '../types'
1+
import { useState } from 'react';
2+
import { toast } from 'sonner';
3+
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
4+
import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism';
5+
import { SearchBox } from './search';
6+
import type { SearchResult } from '../types';
67

78
interface SearchPanelProps {
8-
repoId: string
9-
apiUrl: string
10-
apiKey: string
9+
repoId: string;
10+
apiUrl: string;
11+
apiKey: string;
1112
}
1213

1314
export function SearchPanel({ repoId, apiUrl, apiKey }: SearchPanelProps) {
14-
const [query, setQuery] = useState('')
15-
const [results, setResults] = useState<SearchResult[]>([])
16-
const [loading, setLoading] = useState(false)
17-
const [searchTime, setSearchTime] = useState<number | null>(null)
18-
const [cached, setCached] = useState(false)
15+
const [query, setQuery] = useState('');
16+
const [results, setResults] = useState<SearchResult[]>([]);
17+
const [loading, setLoading] = useState(false);
18+
const [searchTime, setSearchTime] = useState<number | null>(null);
19+
const [cached, setCached] = useState(false);
1920

20-
const handleSearch = async (e: React.FormEvent) => {
21-
e.preventDefault()
22-
if (!query.trim()) return
21+
const handleSearch = async () => {
22+
if (!query.trim()) return;
2323

24-
setLoading(true)
25-
const startTime = Date.now()
24+
setLoading(true);
25+
const startTime = Date.now();
2626

2727
try {
2828
const response = await fetch(`${apiUrl}/search`, {
2929
method: 'POST',
3030
headers: {
31-
'Authorization': `Bearer ${apiKey}`,
32-
'Content-Type': 'application/json'
31+
Authorization: `Bearer ${apiKey}`,
32+
'Content-Type': 'application/json',
3333
},
3434
body: JSON.stringify({
3535
query,
3636
repo_id: repoId,
37-
max_results: 10
38-
})
39-
})
37+
max_results: 10,
38+
}),
39+
});
4040

41-
const data = await response.json()
42-
setResults(data.results || [])
43-
setSearchTime(Date.now() - startTime)
44-
setCached(data.cached || false)
41+
const data = await response.json();
42+
setResults(data.results || []);
43+
setSearchTime(Date.now() - startTime);
44+
setCached(data.cached || false);
4545
} catch (error) {
46-
console.error('Search error:', error)
46+
console.error('Search error:', error);
4747
toast.error('Search failed', {
48-
description: 'Please check your query and try again'
49-
})
48+
description: 'Please check your query and try again',
49+
});
5050
} finally {
51-
setLoading(false)
51+
setLoading(false);
5252
}
53-
}
53+
};
5454

5555
return (
5656
<div className="p-6 space-y-6">
57-
{/* Search */}
58-
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5">
59-
<form onSubmit={handleSearch}>
60-
<div className="flex gap-3">
61-
<input
62-
type="text"
63-
value={query}
64-
onChange={(e) => setQuery(e.target.value)}
65-
placeholder="e.g., authentication middleware, React hooks, database queries..."
66-
className="flex-1 px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder:text-gray-500 focus:outline-none focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 transition-all"
67-
disabled={loading}
68-
autoFocus
69-
/>
70-
<button
71-
type="submit"
72-
className="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white font-medium rounded-xl transition-all disabled:opacity-50"
73-
disabled={loading}
74-
>
75-
{loading ? 'Searching...' : 'Search'}
76-
</button>
77-
</div>
78-
<p className="mt-3 text-xs text-gray-500">
79-
Powered by semantic embeddings - finds code by meaning, not just keywords
80-
</p>
81-
</form>
57+
{/* Search Box */}
58+
<div className="card p-5">
59+
<SearchBox
60+
value={query}
61+
onChange={setQuery}
62+
onSubmit={handleSearch}
63+
loading={loading}
64+
autoFocus
65+
/>
8266

8367
{searchTime !== null && (
84-
<div className="mt-4 pt-4 border-t border-white/5 flex items-center gap-4 text-sm text-gray-400">
68+
<div className="mt-4 pt-4 border-t border-border flex items-center gap-4 text-sm text-text-secondary">
8569
<span>
86-
<span className="font-semibold text-white">{results.length}</span> results
70+
<span className="font-semibold text-text-primary">{results.length}</span> results
8771
</span>
88-
<span className="text-gray-600"></span>
72+
<span className="text-text-muted"></span>
8973
<span>
90-
<span className="font-mono font-semibold text-white">{searchTime}ms</span>
74+
<span className="font-mono font-semibold text-text-primary">{searchTime}ms</span>
9175
</span>
9276
{cached && (
9377
<>
94-
<span className="text-gray-600"></span>
95-
<span className="text-xs bg-green-500/10 text-green-400 border border-green-500/20 px-2 py-0.5 rounded-md">
96-
⚡ Cached
97-
</span>
78+
<span className="text-text-muted"></span>
79+
<span className="badge-success">⚡ Cached</span>
9880
</>
9981
)}
10082
</div>
@@ -104,45 +86,48 @@ export function SearchPanel({ repoId, apiUrl, apiKey }: SearchPanelProps) {
10486
{/* Results */}
10587
<div className="space-y-4">
10688
{results.map((result, idx) => (
107-
<div key={idx} className="bg-[#0a0a0c] border border-white/5 rounded-xl p-5 hover:border-white/10 transition-all group">
89+
<div
90+
key={idx}
91+
className="card p-5 hover:border-border-accent transition-all duration-normal group"
92+
>
10893
{/* Header */}
10994
<div className="flex items-start justify-between mb-4">
11095
<div className="flex-1">
11196
<div className="flex items-center gap-2 mb-1">
112-
<h3 className="font-mono font-semibold text-sm text-white">
97+
<h3 className="font-mono font-semibold text-sm text-text-primary">
11398
{result.name}
11499
</h3>
115-
<span className="px-2 py-0.5 text-[10px] uppercase tracking-wide bg-white/5 text-gray-400 border border-white/10 rounded">
100+
<span className="badge-neutral text-[10px] uppercase tracking-wide">
116101
{result.type.replace('_', ' ')}
117102
</span>
118103
</div>
119-
<p className="text-xs text-gray-500 font-mono">
104+
<p className="text-xs text-text-muted font-mono">
120105
{result.file_path.split('/').slice(-3).join('/')}
121106
</p>
122107
</div>
123-
108+
124109
<div className="flex items-center gap-3">
125110
<div className="text-right">
126-
<div className="text-xs font-mono text-gray-500">Match</div>
127-
<div className="text-sm font-mono font-semibold text-blue-400">
111+
<div className="text-xs font-mono text-text-muted">Match</div>
112+
<div className="text-sm font-mono font-semibold text-accent">
128113
{(result.score * 100).toFixed(0)}%
129114
</div>
130115
</div>
131116
<button
132117
onClick={(e) => {
133-
e.stopPropagation()
134-
navigator.clipboard.writeText(result.code)
135-
toast.success('Code copied!')
118+
e.stopPropagation();
119+
navigator.clipboard.writeText(result.code);
120+
toast.success('Code copied!');
136121
}}
137-
className="px-3 py-1.5 text-sm text-gray-400 hover:text-white bg-white/5 hover:bg-white/10 rounded-lg opacity-0 group-hover:opacity-100 transition-all"
122+
className="btn-ghost px-3 py-1.5 text-sm opacity-0 group-hover:opacity-100"
138123
title="Copy code"
139124
>
140125
Copy
141126
</button>
142127
</div>
143128
</div>
144129

145-
{/* Code with Syntax Highlighting */}
130+
{/* Code */}
146131
<div className="relative rounded-lg overflow-hidden">
147132
<SyntaxHighlighter
148133
language={result.language}
@@ -152,47 +137,45 @@ export function SearchPanel({ repoId, apiUrl, apiKey }: SearchPanelProps) {
152137
borderRadius: '0.5rem',
153138
fontSize: '0.75rem',
154139
lineHeight: '1.5',
155-
background: '#0d0d0f',
140+
background: 'var(--color-bg-secondary)',
156141
}}
157142
showLineNumbers
158143
startingLineNumber={result.line_start}
159144
>
160145
{result.code}
161146
</SyntaxHighlighter>
162-
147+
163148
<div className="absolute top-3 right-3">
164-
<span className="px-2 py-0.5 text-[10px] font-mono uppercase bg-black/50 text-gray-400 backdrop-blur rounded">
149+
<span className="px-2 py-0.5 text-[10px] font-mono uppercase glass text-text-muted rounded">
165150
{result.language}
166151
</span>
167152
</div>
168153
</div>
169154

170-
{/* Metadata */}
171-
<div className="mt-3 flex items-center gap-3 text-xs text-gray-500">
155+
{/* Footer */}
156+
<div className="mt-3 flex items-center gap-3 text-xs text-text-muted">
172157
<span className="font-mono">
173158
Lines {result.line_start}{result.line_end}
174159
</span>
175-
<span className="text-gray-600"></span>
176-
<span className="text-gray-500 truncate">
177-
{result.file_path}
178-
</span>
160+
<span></span>
161+
<span className="truncate">{result.file_path}</span>
179162
</div>
180163
</div>
181164
))}
182165
</div>
183166

184167
{/* Empty State */}
185168
{results.length === 0 && query && !loading && (
186-
<div className="bg-[#0a0a0c] border border-white/5 rounded-xl p-16 text-center">
187-
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-white/5 flex items-center justify-center">
169+
<div className="card p-16 text-center">
170+
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl glass flex items-center justify-center">
188171
<span className="text-4xl">🔍</span>
189172
</div>
190-
<h3 className="text-base font-semibold mb-2 text-white">No results found</h3>
191-
<p className="text-sm text-gray-400">
173+
<h3 className="text-base font-semibold mb-2 text-text-primary">No results found</h3>
174+
<p className="text-sm text-text-secondary">
192175
Try a different query or check if the repository is fully indexed
193176
</p>
194177
</div>
195178
)}
196179
</div>
197-
)
180+
);
198181
}

0 commit comments

Comments
 (0)