@@ -12,6 +12,8 @@ import SearchIcon from '@mui/icons-material/Search';
1212import AddIcon from '@mui/icons-material/Add' ;
1313import ViewAgendaIcon from '@mui/icons-material/ViewAgenda' ;
1414import ViewModuleIcon from '@mui/icons-material/ViewModule' ;
15+ import useMediaQuery from '@mui/material/useMediaQuery' ;
16+ import { useTheme } from '@mui/material/styles' ;
1517
1618import type { FilterCategory , ActiveFilters , FilterCounts } from '../types' ;
1719import { FILTER_LABELS , FILTER_CATEGORIES } from '../types' ;
@@ -51,6 +53,37 @@ export function FilterBar({
5153 onRemoveGroup,
5254 onTrackEvent,
5355} : FilterBarProps ) {
56+ const theme = useTheme ( ) ;
57+ const isMobile = useMediaQuery ( theme . breakpoints . down ( 'md' ) ) ;
58+
59+ // Scroll percentage - estimate based on total plots, not just loaded ones
60+ const [ scrollPercent , setScrollPercent ] = useState ( 0 ) ;
61+
62+ useEffect ( ( ) => {
63+ const calculatePercent = ( ) => {
64+ const scrollY = window . scrollY ;
65+ const docHeight = document . documentElement . scrollHeight ;
66+ const windowHeight = window . innerHeight ;
67+
68+ // Estimate total height based on ratio of loaded vs total plots
69+ const loadRatio = displayedCount > 0 && currentTotal > 0
70+ ? currentTotal / displayedCount
71+ : 1 ;
72+ const estimatedTotalHeight = ( docHeight - windowHeight ) * loadRatio ;
73+
74+ const percent = Math . round ( ( scrollY / estimatedTotalHeight ) * 100 ) ;
75+ setScrollPercent ( Math . min ( 100 , Math . max ( 0 , percent || 0 ) ) ) ;
76+ } ;
77+ calculatePercent ( ) ;
78+ window . addEventListener ( 'scroll' , calculatePercent ) ;
79+ const resizeObserver = new ResizeObserver ( calculatePercent ) ;
80+ resizeObserver . observe ( document . body ) ;
81+ return ( ) => {
82+ window . removeEventListener ( 'scroll' , calculatePercent ) ;
83+ resizeObserver . disconnect ( ) ;
84+ } ;
85+ } , [ displayedCount , currentTotal ] ) ;
86+
5487 // Search/dropdown state
5588 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
5689 const [ dropdownAnchor , setDropdownAnchor ] = useState < HTMLElement | null > ( null ) ;
@@ -260,11 +293,11 @@ export function FilterBar({
260293 gap : 1 ,
261294 justifyContent : 'center' ,
262295 alignItems : 'center' ,
263- position : ' relative',
296+ position : { xs : 'static' , md : ' relative' } ,
264297 } }
265298 >
266- { /* Progress counter - absolute left */ }
267- { currentTotal > 0 && (
299+ { /* Progress counter - absolute left (desktop only) */ }
300+ { ! isMobile && currentTotal > 0 && (
268301 < Typography
269302 sx = { {
270303 position : 'absolute' ,
@@ -275,31 +308,33 @@ export function FilterBar({
275308 whiteSpace : 'nowrap' ,
276309 } }
277310 >
278- { Math . min ( displayedCount , currentTotal ) } / { currentTotal }
311+ { scrollPercent } % · { currentTotal }
279312 </ Typography >
280313 ) }
281- { /* Grid size toggle - absolute right */ }
282- < Box
283- onClick = { ( ) => onImageSizeChange ( imageSize === 'normal' ? 'compact' : 'normal' ) }
284- sx = { {
285- position : 'absolute' ,
286- right : 0 ,
287- display : 'flex' ,
288- alignItems : 'center' ,
289- justifyContent : 'center' ,
290- width : 32 ,
291- height : 32 ,
292- cursor : 'pointer' ,
293- color : '#9ca3af' ,
294- '&:hover' : { color : '#3776AB' } ,
295- } }
296- >
297- { imageSize === 'normal' ? (
298- < ViewAgendaIcon sx = { { fontSize : '1.25rem' } } />
299- ) : (
300- < ViewModuleIcon sx = { { fontSize : '1.25rem' } } />
301- ) }
302- </ Box >
314+ { /* Grid size toggle - absolute right (desktop only) */ }
315+ { ! isMobile && (
316+ < Box
317+ onClick = { ( ) => onImageSizeChange ( imageSize === 'normal' ? 'compact' : 'normal' ) }
318+ sx = { {
319+ position : 'absolute' ,
320+ right : 0 ,
321+ display : 'flex' ,
322+ alignItems : 'center' ,
323+ justifyContent : 'center' ,
324+ width : 32 ,
325+ height : 32 ,
326+ cursor : 'pointer' ,
327+ color : '#9ca3af' ,
328+ '&:hover' : { color : '#3776AB' } ,
329+ } }
330+ >
331+ { imageSize === 'normal' ? (
332+ < ViewAgendaIcon sx = { { fontSize : '1.25rem' } } />
333+ ) : (
334+ < ViewModuleIcon sx = { { fontSize : '1.25rem' } } />
335+ ) }
336+ </ Box >
337+ ) }
303338 { /* Active filter chips */ }
304339 { activeFilters . map ( ( group , index ) => {
305340 const isAnimating = randomAnimation ?. index === index ;
@@ -363,8 +398,8 @@ export function FilterBar({
363398 gap : 0.5 ,
364399 px : isSearchExpanded ? 1.5 : 0 ,
365400 height : 32 ,
366- width : isSearchExpanded ? 'auto' : 32 ,
367- minWidth : isSearchExpanded ? 120 : 32 ,
401+ width : isSearchExpanded ? { xs : 80 , sm : 160 , md : 'auto' } : 32 ,
402+ minWidth : isSearchExpanded ? { xs : 80 , sm : 160 , md : 120 } : 32 ,
368403 border : isSearchExpanded ? '1px dashed #9ca3af' : 'none' ,
369404 borderRadius : '16px' ,
370405 bgcolor : isDropdownOpen ? '#f9fafb' : 'transparent' ,
@@ -441,6 +476,52 @@ export function FilterBar({
441476 ) }
442477 </ Box >
443478
479+ { /* Counter and toggle row (mobile only) */ }
480+ { isMobile && (
481+ < Box
482+ sx = { {
483+ display : 'flex' ,
484+ justifyContent : 'space-between' ,
485+ alignItems : 'center' ,
486+ mt : 1 ,
487+ } }
488+ >
489+ { currentTotal > 0 ? (
490+ < Typography
491+ sx = { {
492+ fontFamily : '"MonoLisa", "MonoLisa Fallback", monospace' ,
493+ fontSize : '0.75rem' ,
494+ color : '#9ca3af' ,
495+ whiteSpace : 'nowrap' ,
496+ } }
497+ >
498+ { scrollPercent } % · { currentTotal }
499+ </ Typography >
500+ ) : (
501+ < Box />
502+ ) }
503+ < Box
504+ onClick = { ( ) => onImageSizeChange ( imageSize === 'normal' ? 'compact' : 'normal' ) }
505+ sx = { {
506+ display : 'flex' ,
507+ alignItems : 'center' ,
508+ justifyContent : 'center' ,
509+ width : 32 ,
510+ height : 32 ,
511+ cursor : 'pointer' ,
512+ color : '#9ca3af' ,
513+ '&:hover' : { color : '#3776AB' } ,
514+ } }
515+ >
516+ { imageSize === 'normal' ? (
517+ < ViewAgendaIcon sx = { { fontSize : '1.25rem' } } />
518+ ) : (
519+ < ViewModuleIcon sx = { { fontSize : '1.25rem' } } />
520+ ) }
521+ </ Box >
522+ </ Box >
523+ ) }
524+
444525 { /* Dropdown menu */ }
445526 < Menu
446527 anchorEl = { dropdownAnchor }
0 commit comments