@@ -21,12 +21,13 @@ import { useTheme } from '@mui/material/styles';
2121import type { FilterCategory , ActiveFilters , FilterCounts } from '../types' ;
2222import { FILTER_LABELS , FILTER_TOOLTIPS , FILTER_CATEGORIES } from '../types' ;
2323import type { ImageSize } from '../constants' ;
24- import { getAvailableValues , getAvailableValuesForGroup , getSearchResults } from '../utils' ;
24+ import { getAvailableValues , getAvailableValuesForGroup , getSearchResults , type SearchResult } from '../utils' ;
2525
2626interface FilterBarProps {
2727 activeFilters : ActiveFilters ;
2828 filterCounts : FilterCounts | null ; // Contextual counts (for AND additions)
2929 orCounts : Record < string , number > [ ] ; // Per-group counts for OR additions
30+ specTitles : Record < string , string > ; // Mapping spec_id -> title for search/tooltips
3031 currentTotal : number ; // Total number of filtered images
3132 displayedCount : number ; // Currently displayed images
3233 randomAnimation : { index : number ; phase : 'out' | 'in' ; oldLabel ?: string } | null ;
@@ -44,6 +45,7 @@ export function FilterBar({
4445 activeFilters,
4546 filterCounts,
4647 orCounts,
48+ specTitles,
4749 currentTotal,
4850 displayedCount,
4951 randomAnimation,
@@ -208,8 +210,8 @@ export function FilterBar({
208210
209211 // Memoize search results to avoid recalculating on every render
210212 const searchResults = useMemo (
211- ( ) => getSearchResults ( filterCounts , activeFilters , searchQuery , selectedCategory ) ,
212- [ filterCounts , activeFilters , searchQuery , selectedCategory ]
213+ ( ) => getSearchResults ( filterCounts , activeFilters , searchQuery , selectedCategory , specTitles ) ,
214+ [ filterCounts , activeFilters , searchQuery , selectedCategory , specTitles ]
213215 ) ;
214216
215217 // Track searches with no results (debounced, to discover missing specs)
@@ -699,38 +701,83 @@ export function FilterBar({
699701 ]
700702 : [ ] ) ,
701703 ...( searchResults . length > 0
702- ? searchResults . map ( ( { category, value, count } , idx ) => (
703- < MenuItem
704- key = { `${ category } -${ value } ` }
705- onClick = { ( ) => handleValueSelect ( category , value ) }
706- selected = { idx === highlightedIndex }
707- sx = { { fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' } }
708- >
709- < ListItemText
710- primary = { value }
711- secondary = { ! selectedCategory ? FILTER_LABELS [ category ] : undefined }
712- primaryTypographyProps = { {
713- fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
714- fontSize : '0.85rem' ,
715- } }
716- secondaryTypographyProps = { {
717- fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
718- fontSize : '0.7rem' ,
719- color : '#9ca3af' ,
720- } }
721- />
722- < Typography
723- sx = { {
724- fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
725- fontSize : '0.75rem' ,
726- color : '#9ca3af' ,
727- ml : 2 ,
728- } }
729- >
730- ({ count } )
731- </ Typography >
732- </ MenuItem >
733- ) )
704+ ? ( ( ) => {
705+ // Split results into exact and fuzzy matches
706+ const exactResults = searchResults . filter ( ( r ) => r . matchType === 'exact' ) ;
707+ const fuzzyResults = searchResults . filter ( ( r ) => r . matchType === 'fuzzy' ) ;
708+
709+ const renderMenuItem = ( result : SearchResult , idx : number ) => {
710+ const { category, value, count } = result ;
711+ const specTitle = category === 'spec' ? specTitles [ value ] : undefined ;
712+ const menuItem = (
713+ < MenuItem
714+ key = { `${ category } -${ value } ` }
715+ onClick = { ( ) => handleValueSelect ( category , value ) }
716+ selected = { idx === highlightedIndex }
717+ sx = { { fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' } }
718+ >
719+ < ListItemText
720+ primary = { value }
721+ secondary = { ! selectedCategory ? FILTER_LABELS [ category ] : undefined }
722+ primaryTypographyProps = { {
723+ fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
724+ fontSize : '0.85rem' ,
725+ } }
726+ secondaryTypographyProps = { {
727+ fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
728+ fontSize : '0.7rem' ,
729+ color : '#9ca3af' ,
730+ } }
731+ />
732+ < Typography
733+ sx = { {
734+ fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
735+ fontSize : '0.75rem' ,
736+ color : '#9ca3af' ,
737+ ml : 2 ,
738+ } }
739+ >
740+ ({ count } )
741+ </ Typography >
742+ </ MenuItem >
743+ ) ;
744+ return specTitle ? (
745+ < Tooltip key = { `${ category } -${ value } ` } title = { specTitle } placement = "right" arrow >
746+ < span > { menuItem } </ span >
747+ </ Tooltip >
748+ ) : (
749+ menuItem
750+ ) ;
751+ } ;
752+
753+ const items : React . ReactNode [ ] = [ ] ;
754+ // Add exact matches
755+ exactResults . forEach ( ( result , i ) => {
756+ items . push ( renderMenuItem ( result , i ) ) ;
757+ } ) ;
758+ // Add fuzzy label/divider if there are fuzzy results
759+ if ( fuzzyResults . length > 0 ) {
760+ items . push (
761+ < Divider key = "exact-fuzzy-divider" sx = { { my : 0.5 } } >
762+ < Typography
763+ sx = { {
764+ fontSize : '0.65rem' ,
765+ color : '#9ca3af' ,
766+ fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
767+ px : 1 ,
768+ } }
769+ >
770+ fuzzy
771+ </ Typography >
772+ </ Divider >
773+ ) ;
774+ }
775+ // Add fuzzy matches
776+ fuzzyResults . forEach ( ( result , i ) => {
777+ items . push ( renderMenuItem ( result , exactResults . length + i ) ) ;
778+ } ) ;
779+ return items ;
780+ } ) ( )
734781 : [
735782 < MenuItem key = "no-results" disabled >
736783 < Typography
0 commit comments