@@ -14,6 +14,8 @@ type PatternsSidebarProps = {
1414 onSelectPattern : ( pattern : Pattern ) => void ;
1515} ;
1616
17+ type CategoryFilter = "all" | "spaceship" | "oscillator" | "still" ;
18+
1719const categoryIcons = {
1820 still : Box ,
1921 oscillator : Zap ,
@@ -28,14 +30,24 @@ const categoryLabels = {
2830 custom : "Custom" ,
2931} ;
3032
33+ const categoryFilters : Array < { value : CategoryFilter ; label : string } > = [
34+ { value : "all" , label : "All" } ,
35+ { value : "spaceship" , label : "Spaceships" } ,
36+ { value : "oscillator" , label : "Oscillators" } ,
37+ { value : "still" , label : "Still Lives" } ,
38+ ] ;
39+
3140export const PatternsSidebar = ( { onSelectPattern } : PatternsSidebarProps ) => {
3241 const [ search , setSearch ] = useState ( "" ) ;
42+ const [ selectedCategory , setSelectedCategory ] = useState < CategoryFilter > ( "all" ) ;
3343
34- const filteredPatterns = PATTERNS . filter (
35- ( p ) =>
44+ const filteredPatterns = PATTERNS . filter ( ( p ) => {
45+ const matchesSearch =
3646 p . name . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ||
37- p . description . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ,
38- ) ;
47+ p . description . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ;
48+ const matchesCategory = selectedCategory === "all" || p . category === selectedCategory ;
49+ return matchesSearch && matchesCategory ;
50+ } ) ;
3951
4052 const groupedPatterns = filteredPatterns . reduce (
4153 ( acc , pattern ) => {
@@ -60,33 +72,66 @@ export const PatternsSidebar = ({ onSelectPattern }: PatternsSidebarProps) => {
6072 className = "pl-9 h-9 bg-background/50 border-border/50 text-sm"
6173 />
6274 </ div >
75+ < div className = "mt-3 flex flex-wrap gap-2" >
76+ { categoryFilters . map ( ( category ) => {
77+ const isActive = selectedCategory === category . value ;
78+
79+ return (
80+ < button
81+ key = { category . value }
82+ type = "button"
83+ onClick = { ( ) => setSelectedCategory ( category . value ) }
84+ className = { [
85+ "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors" ,
86+ isActive
87+ ? "border-primary/40 bg-primary/15 text-primary"
88+ : "border-border/60 bg-background/40 text-muted-foreground hover:bg-accent/60" ,
89+ ] . join ( " " ) }
90+ >
91+ { category . label }
92+ </ button >
93+ ) ;
94+ } ) }
95+ </ div >
6396 </ div >
6497
6598 { /* Patterns List */ }
6699 < ScrollArea className = "flex-1" >
67100 < div className = "p-3 space-y-4" >
68- { Object . entries ( groupedPatterns ) . map ( ( [ category , patterns ] ) => {
69- const Icon = categoryIcons [ category as keyof typeof categoryIcons ] ;
70- return (
71- < div key = { category } >
72- < div className = "flex items-center gap-2 px-2 mb-2" >
73- < Icon className = "h-3.5 w-3.5 text-muted-foreground" />
74- < span className = "text-xs font-medium text-muted-foreground uppercase tracking-wide" >
75- { categoryLabels [ category as keyof typeof categoryLabels ] }
76- </ span >
77- </ div >
78- < div className = "space-y-1" >
79- { patterns . map ( ( pattern ) => (
80- < PatternItem
81- key = { pattern . id }
82- pattern = { pattern }
83- onSelect = { ( ) => onSelectPattern ( pattern ) }
84- />
85- ) ) }
101+ { selectedCategory === "all" ? (
102+ Object . entries ( groupedPatterns ) . map ( ( [ category , patterns ] ) => {
103+ const Icon = categoryIcons [ category as keyof typeof categoryIcons ] ;
104+ return (
105+ < div key = { category } >
106+ < div className = "flex items-center gap-2 px-2 mb-2" >
107+ < Icon className = "h-3.5 w-3.5 text-muted-foreground" />
108+ < span className = "text-xs font-medium text-muted-foreground uppercase tracking-wide" >
109+ { categoryLabels [ category as keyof typeof categoryLabels ] }
110+ </ span >
111+ </ div >
112+ < div className = "space-y-1" >
113+ { patterns . map ( ( pattern ) => (
114+ < PatternItem
115+ key = { pattern . id }
116+ pattern = { pattern }
117+ onSelect = { ( ) => onSelectPattern ( pattern ) }
118+ />
119+ ) ) }
120+ </ div >
86121 </ div >
87- </ div >
88- ) ;
89- } ) }
122+ ) ;
123+ } )
124+ ) : (
125+ < div className = "space-y-1" >
126+ { filteredPatterns . map ( ( pattern ) => (
127+ < PatternItem
128+ key = { pattern . id }
129+ pattern = { pattern }
130+ onSelect = { ( ) => onSelectPattern ( pattern ) }
131+ />
132+ ) ) }
133+ </ div >
134+ ) }
90135
91136 { filteredPatterns . length === 0 && (
92137 < div className = "text-center py-8 text-muted-foreground text-sm" > No patterns found</ div >
0 commit comments