1- import { useState } from "react" ;
1+ import { useState , useMemo } from "react" ;
22import * as Blockly from "blockly" ;
33import {
44 ChevronRight ,
@@ -35,6 +35,7 @@ interface CategorySectionProps {
3535 previews : Map < string , BlockPreview > ;
3636 disabledTypes : Set < string > ;
3737 defaultOpen ?: boolean ;
38+ searchQuery ?: string ;
3839}
3940
4041export default function CategorySection ( {
@@ -43,28 +44,49 @@ export default function CategorySection({
4344 previews,
4445 disabledTypes,
4546 defaultOpen,
47+ searchQuery = "" ,
4648} : CategorySectionProps ) {
4749 const [ isOpen , setIsOpen ] = useState ( defaultOpen ?? false ) ;
4850 const Icon = iconMap [ category . icon ] ;
4951
52+ const isSearching = searchQuery . trim ( ) . length > 0 ;
53+
54+ const filteredBlocks = useMemo ( ( ) => {
55+ if ( ! isSearching ) return category . blocks ;
56+ const lowerQuery = searchQuery . toLowerCase ( ) ;
57+ return category . blocks . filter ( ( b ) => b . label . toLowerCase ( ) . includes ( lowerQuery ) ) ;
58+ } , [ isSearching , searchQuery , category . blocks ] ) ;
59+
60+ const effectiveOpen = isSearching ? filteredBlocks . length > 0 : isOpen ;
61+
62+ if ( isSearching && filteredBlocks . length === 0 ) return null ;
63+
5064 return (
5165 < div className = "border-b border-gray-200" >
5266 < button
53- onClick = { ( ) => setIsOpen ( ! isOpen ) }
54- className = "w-full flex items-center gap-2 px-3 py-2 hover:bg-gray-50 transition-colors"
67+ type = "button"
68+ onClick = { ( ) => {
69+ if ( ! isSearching ) setIsOpen ( ( prev ) => ! prev ) ;
70+ } }
71+ aria-expanded = { effectiveOpen }
72+ className = { `w-full flex items-center gap-2 px-3 py-2 hover:bg-gray-50 transition-colors ${ isSearching ? "cursor-default" : "" } ` }
5573 >
56- { isOpen ? (
57- < ChevronDown size = { 14 } className = "text-gray-400" />
74+ { effectiveOpen ? (
75+ < ChevronDown size = { 14 } className = { isSearching ? "text-gray-200" : "text-gray- 400"} />
5876 ) : (
59- < ChevronRight size = { 14 } className = "text-gray-400" />
77+ < ChevronRight size = { 14 } className = { isSearching ? "text-gray-200" : "text-gray- 400"} />
6078 ) }
6179 { Icon && < Icon size = { 16 } color = { category . colour } /> }
6280 < span className = "text-sm font-medium text-gray-700" > { category . name } </ span >
63- < span className = "ml-auto text-xs text-gray-400 font-normal" > { category . blocks . length } </ span >
81+ < span className = "ml-auto text-xs text-gray-400 font-normal" >
82+ { isSearching
83+ ? `${ filteredBlocks . length } /${ category . blocks . length } `
84+ : category . blocks . length }
85+ </ span >
6486 </ button >
65- { isOpen && (
87+ { effectiveOpen && (
6688 < div className = "pb-1 pl-2" >
67- { category . blocks . map ( ( block ) => (
89+ { filteredBlocks . map ( ( block ) => (
6890 < BlockItem
6991 key = { block . type }
7092 type = { block . type }
0 commit comments