@@ -4,8 +4,8 @@ import * as React from "react";
44import type { CodeExample } from "@/lib/code-examples" ;
55import { AppCard } from "./app-card" ;
66import { AppModal } from "./app-modal" ;
7+ import { SearchBar } from "./search-bar" ;
78
8- /* ---------- Animated track ---------- */
99const Track = React . memo ( function Track ( {
1010 apps,
1111 onOpen,
@@ -24,7 +24,6 @@ const Track = React.memo(function Track({
2424 ) ;
2525} ) ;
2626
27- /* ---------- Auto-scrolling row ---------- */
2827const AutoScrollerRow = React . memo ( function AutoScrollerRow ( {
2928 apps,
3029 reverse = false ,
@@ -44,8 +43,7 @@ const AutoScrollerRow = React.memo(function AutoScrollerRow({
4443 reverse ? "animate-marquee-reverse" : "animate-marquee" ,
4544 "[animation-duration:var(--marquee-duration)]" ,
4645 ] . 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 }
4947 >
5048 < Track apps = { apps } onOpen = { onOpen } />
5149 < Track apps = { apps } onOpen = { onOpen } aria-hidden />
@@ -57,6 +55,7 @@ const AutoScrollerRow = React.memo(function AutoScrollerRow({
5755export function AppGrid ( { apps } : { apps : CodeExample [ ] } ) {
5856 const [ open , setOpen ] = React . useState ( false ) ;
5957 const [ active , setActive ] = React . useState < CodeExample | null > ( null ) ;
58+ const [ searchQuery , setSearchQuery ] = React . useState ( "" ) ;
6059
6160 const onOpen = React . useCallback ( ( app : CodeExample ) => {
6261 setActive ( app ) ;
@@ -70,30 +69,72 @@ export function AppGrid({ apps }: { apps: CodeExample[] }) {
7069 } catch { }
7170 } , [ active ] ) ;
7271
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+
7388 const buckets = React . useMemo ( ( ) => {
89+ if ( searchQuery ) return [ ] ;
90+
7491 const ROWS = Math . min ( 8 , Math . max ( 3 , Math . ceil ( apps . length / 8 ) ) ) ;
7592 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 ) ) ;
7794 return rows ;
78- } , [ apps ] ) ;
95+ } , [ apps , searchQuery ] ) ;
7996
8097 return (
8198 < >
99+ < div className = "fixed top-4 right-4 z-50" >
100+ < SearchBar onSearch = { setSearchQuery } />
101+ </ div >
102+
82103 < 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+ ) }
94136 </ div >
95137
96- { /* Modal; background rows keep animating */ }
97138 < AppModal
98139 active = { active }
99140 open = { open }
0 commit comments