Skip to content

Commit 1649b61

Browse files
committed
feat: trigger search on enter
1 parent 3ebc54d commit 1649b61

File tree

8 files changed

+68
-37
lines changed

8 files changed

+68
-37
lines changed

src/App.tsx

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect, useCallback } from 'react';
2-
import { Search, Unplug, Server, Trash2, Download } from 'lucide-react';
2+
import { Search, Unplug, Server, Trash2, Download, CornerDownLeft } from 'lucide-react';
33

44
import { buildIdeConfigForPkg, buildIdeConfigForRemote } from '~/lib/ide-config';
55
import { Card } from '~/components/ui/card';
@@ -288,33 +288,35 @@ export default function App() {
288288
[registryUrl, resultsPerPage]
289289
);
290290

291-
// Fetch servers when search, apiUrl, filterDate, or resultsPerPage changes
291+
// Fetch servers when apiUrl, filterDate, or resultsPerPage changes (not on every keystroke).
292+
// Use `performSearch` to trigger searches from the UI (Enter or button).
292293
useEffect(() => {
294+
// Reset pagination and fetch for the currently confirmed `search` value.
293295
setCurrentCursor(null);
294296
setPreviousCursors([]);
295297
setNextCursor(null);
296298
setCurrentPage(1);
297-
// Reset page cursor map for the new search / filters / settings.
299+
// Reset page cursor map for the new filters / settings.
298300
setPageCursors({ 1: null });
299301
fetchServers(search, null, filterDate);
300-
}, [search, registryUrl, filterDate, resultsPerPage, fetchServers]);
301-
302-
// // Initialize Orama (client-side) on mount
303-
// useEffect(() => {
304-
// let mounted = true;
305-
// (async () => {
306-
// try {
307-
// if (typeof window === 'undefined') return;
308-
// await initOrama();
309-
// if (!mounted) return;
310-
// } catch (err) {
311-
// // ignore
312-
// }
313-
// })();
314-
// return () => {
315-
// mounted = false;
316-
// };
317-
// }, []);
302+
// NOTE: we intentionally omit `search` from the dependency list so typing into
303+
// the input doesn't trigger a fetch on every keystroke. Searches are triggered
304+
// explicitly via `performSearch` which updates `search` and performs the fetch.
305+
// eslint-disable-next-line react-hooks/exhaustive-deps
306+
}, [registryUrl, filterDate, resultsPerPage, fetchServers]);
307+
308+
// Helper to perform a confirmed search (called on Enter or when clicking the search button)
309+
const performSearch = (newSearch: string) => {
310+
// Update the confirmed `search` state which also keeps URL in sync via the existing effect.
311+
setSearch(newSearch);
312+
// If the query differs from the current confirmed search, reset pagination and fetch
313+
setCurrentCursor(null);
314+
setPreviousCursors([]);
315+
setNextCursor(null);
316+
setCurrentPage(1);
317+
setPageCursors({ 1: null });
318+
fetchServers(newSearch, null, filterDate);
319+
};
318320

319321
const handleNext = () => {
320322
if (nextCursor) {
@@ -551,21 +553,38 @@ export default function App() {
551553
<div className="relative flex gap-2 items-center">
552554
<div className="relative flex-1">
553555
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
554-
<input
555-
type="text"
556-
placeholder="Search MCP servers by name"
557-
value={search}
558-
onChange={(e) => setSearch(e.target.value)}
559-
className="w-full rounded-lg border border-input bg-background px-10 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
560-
/>
556+
<div className="flex">
557+
<input
558+
type="text"
559+
placeholder="Search MCP servers by name"
560+
value={search}
561+
onChange={(e) => setSearch(e.target.value)}
562+
onKeyDown={(e) => {
563+
if (e.key === 'Enter') {
564+
e.preventDefault();
565+
performSearch(search);
566+
}
567+
}}
568+
className="w-full rounded-lg border border-input bg-background px-10 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
569+
/>
570+
<Button
571+
variant="outline"
572+
size="sm"
573+
onClick={() => performSearch(search)}
574+
className="-ml-10 mr-2 self-center h-5 w-8 px-2 text-muted-foreground/80"
575+
aria-label="Search"
576+
>
577+
<CornerDownLeft className="h-3.5 w-3.5" />
578+
</Button>
579+
</div>
561580
</div>
562581
{/* Filter Date Button */}
563582
<DatePicker
564583
date={filterDate}
565584
onDateChange={setFilterDate}
566585
placeholder="Filter by date"
567586
variant={filterDate ? 'default' : 'outline'}
568-
className="h-auto py-3 px-4"
587+
className="h-auto py-3 px-4 text-muted-foreground"
569588
/>
570589
{/* Results Per Page Selector */}
571590
<DropdownMenu>

src/components/about.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ export const AboutPopup = () => {
6161
<div className="flex gap-3">
6262
<Server className="h-5 w-5 flex-shrink-0 mt-0.5 text-primary" />
6363
<p>
64-
Build a stack with a list of MCP servers, that can then be exported in VSCode{' '}
65-
<code className="text-xs bg-muted px-1 py-0.5 rounded">mcp.json</code> or Cursor formats.
64+
Build a stack with a list of MCP servers, that can then be exported to VSCode or Cursor{' '}
65+
<code className="text-xs bg-muted px-1 py-0.5 rounded">mcp.json</code>.
6666
</p>
6767
</div>
6868
<div className="flex gap-3">
Lines changed: 12 additions & 0 deletions
Loading

src/components/server-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ServerPkg } from './server-pkg';
1818
import { ServerRemote } from './server-remote';
1919
import { getRemoteIcon, getPkgIcon } from './server-utils';
2020
import { Button } from './ui/button';
21-
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from './ui/dialog';
21+
import { Dialog, DialogContent, DialogTrigger } from './ui/dialog';
2222

2323
/** List and select versions with dropdown */
2424
const VersionList = ({

src/components/server-pkg.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { Input } from './ui/input';
1818
import { getPkgDefaultCmd, getPkgIcon, getPkgUrl } from './server-utils';
1919
import { PasswordInput } from './ui/password-input';
2020
import { ServerActionButtons } from './server-action-buttons';
21-
import { DialogDescription, DialogHeader, DialogTitle } from './ui/dialog';
21+
import { DialogHeader, DialogTitle } from './ui/dialog';
2222

2323
/** Display all details on a MCP server */
2424
export const ServerPkg = ({

src/components/server-utils.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { HardDriveUpload, Rss, Link2, Container, Package } from 'lucide-react';
1+
import { HardDriveUpload, Rss, Link2, Package } from 'lucide-react';
22

33
import type { McpServerPkg, McpServerRemote } from '~/lib/types';
44
import PypiLogo from '~/components/logos/pypi-logo.svg';
55
import NpmLogo from '~/components/logos/npm-logo.svg';
6+
import DockerLogo from '~/components/logos/docker-logo.svg';
67

78
/** Get icon for remote access points */
89
export const getRemoteIcon = (remote: McpServerRemote) => {
@@ -22,7 +23,7 @@ export const getPkgIcon = (pkg: McpServerPkg) => {
2223
} else if (pkg.registryType === 'pypi') {
2324
return <img src={PypiLogo} alt="PyPI" className="h-4 w-4" style={{ filter: 'grayscale(40%)' }} />;
2425
} else if (pkg.registryType === 'oci' || pkg.registryType === 'docker') {
25-
return <Container className="h-4 w-4 text-muted-foreground" />;
26+
return <img src={DockerLogo} alt="Docker" className="h-4 w-4" style={{ filter: 'grayscale(40%)' }} />;
2627
} else {
2728
return <Package className="h-4 w-4 text-muted-foreground" />;
2829
}

src/components/theme/theme-provider.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ export function ThemeProvider({
5757
);
5858
}
5959

60-
// eslint-disable-next-line react-refresh/only-export-components
6160
export const useTheme = () => {
6261
const context = useContext(ThemeProviderContext);
6362
if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');

src/components/ui/button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ function Button({
4747
}) {
4848
const Comp = asChild ? Slot : 'button';
4949

50-
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size, className }))} {...props} />;
50+
return <Comp data-slot="button" className={cn(buttonVariants({ variant, size }), className)} {...props} />;
5151
}
5252

5353
export { Button, buttonVariants };

0 commit comments

Comments
 (0)