@@ -4,8 +4,8 @@ import * as React from "react";
4
4
import type { CodeExample } from "@/lib/code-examples" ;
5
5
import { AppCard } from "./app-card" ;
6
6
import { AppModal } from "./app-modal" ;
7
+ import { SearchBar } from "./search-bar" ;
7
8
8
- /* ---------- Animated track ---------- */
9
9
const Track = React . memo ( function Track ( {
10
10
apps,
11
11
onOpen,
@@ -24,7 +24,6 @@ const Track = React.memo(function Track({
24
24
) ;
25
25
} ) ;
26
26
27
- /* ---------- Auto-scrolling row ---------- */
28
27
const AutoScrollerRow = React . memo ( function AutoScrollerRow ( {
29
28
apps,
30
29
reverse = false ,
@@ -44,8 +43,7 @@ const AutoScrollerRow = React.memo(function AutoScrollerRow({
44
43
reverse ? "animate-marquee-reverse" : "animate-marquee" ,
45
44
"[animation-duration:var(--marquee-duration)]" ,
46
45
] . join ( " " ) }
47
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
- style = { { [ "--marquee-duration" as any ] : `${ duration } s` } }
46
+ style = { { "--marquee-duration" : `${ duration } s` } as React . CSSProperties }
49
47
>
50
48
< Track apps = { apps } onOpen = { onOpen } />
51
49
< Track apps = { apps } onOpen = { onOpen } aria-hidden />
@@ -57,6 +55,7 @@ const AutoScrollerRow = React.memo(function AutoScrollerRow({
57
55
export function AppGrid ( { apps } : { apps : CodeExample [ ] } ) {
58
56
const [ open , setOpen ] = React . useState ( false ) ;
59
57
const [ active , setActive ] = React . useState < CodeExample | null > ( null ) ;
58
+ const [ searchQuery , setSearchQuery ] = React . useState ( "" ) ;
60
59
61
60
const onOpen = React . useCallback ( ( app : CodeExample ) => {
62
61
setActive ( app ) ;
@@ -70,30 +69,72 @@ export function AppGrid({ apps }: { apps: CodeExample[] }) {
70
69
} catch { }
71
70
} , [ active ] ) ;
72
71
72
+ const filteredApps = React . useMemo ( ( ) => {
73
+ if ( ! searchQuery . trim ( ) ) return apps ;
74
+
75
+ const query = searchQuery . toLowerCase ( ) . trim ( ) ;
76
+ return apps . filter ( app => {
77
+ const searchableText = [
78
+ app . title ,
79
+ app . prompt ,
80
+ app . id ,
81
+ ...( app . tags || [ ] )
82
+ ] . join ( ' ' ) . toLowerCase ( ) ;
83
+
84
+ return searchableText . includes ( query ) ;
85
+ } ) ;
86
+ } , [ apps , searchQuery ] ) ;
87
+
73
88
const buckets = React . useMemo ( ( ) => {
89
+ if ( searchQuery ) return [ ] ;
90
+
74
91
const ROWS = Math . min ( 8 , Math . max ( 3 , Math . ceil ( apps . length / 8 ) ) ) ;
75
92
const rows : CodeExample [ ] [ ] = Array . from ( { length : ROWS } , ( ) => [ ] ) ;
76
- apps . forEach ( ( app , i ) => rows [ i % ROWS ] . push ( app ) ) ; // deterministic
93
+ apps . forEach ( ( app , i ) => rows [ i % ROWS ] . push ( app ) ) ;
77
94
return rows ;
78
- } , [ apps ] ) ;
95
+ } , [ apps , searchQuery ] ) ;
79
96
80
97
return (
81
98
< >
99
+ < div className = "fixed top-4 right-4 z-50" >
100
+ < SearchBar onSearch = { setSearchQuery } />
101
+ </ div >
102
+
82
103
< div className = "min-h-screen overflow-y-auto pt-4" >
83
- < div className = "full-bleed flex flex-col gap-y-4" >
84
- { buckets . map ( ( row , i ) => (
85
- < AutoScrollerRow
86
- key = { i }
87
- apps = { row . length ? row : apps }
88
- reverse = { i % 2 === 1 }
89
- onOpen = { onOpen }
90
- duration = { 18 + ( ( i * 2 ) % 8 ) }
91
- />
92
- ) ) }
93
- </ div >
104
+ { searchQuery && (
105
+ < div className = "text-center mb-4 px-4" >
106
+ < p className = "text-gray-600" >
107
+ { filteredApps . length > 0
108
+ ? `Found ${ filteredApps . length } example${ filteredApps . length !== 1 ? 's' : '' } matching "${ searchQuery } "`
109
+ : `No examples found for "${ searchQuery } "`
110
+ }
111
+ </ p >
112
+ </ div >
113
+ ) }
114
+
115
+ { searchQuery ? (
116
+ filteredApps . length > 0 && (
117
+ < div className = "px-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-4 max-w-7xl mx-auto" >
118
+ { filteredApps . map ( ( app ) => (
119
+ < AppCard key = { app . id } app = { app } onOpen = { onOpen } />
120
+ ) ) }
121
+ </ div >
122
+ )
123
+ ) : (
124
+ < div className = "full-bleed flex flex-col gap-y-4" >
125
+ { buckets . map ( ( row , i ) => (
126
+ < AutoScrollerRow
127
+ key = { i }
128
+ apps = { row }
129
+ reverse = { i % 2 === 1 }
130
+ onOpen = { onOpen }
131
+ duration = { 18 + ( ( i * 2 ) % 8 ) }
132
+ />
133
+ ) ) }
134
+ </ div >
135
+ ) }
94
136
</ div >
95
137
96
- { /* Modal; background rows keep animating */ }
97
138
< AppModal
98
139
active = { active }
99
140
open = { open }
0 commit comments