@@ -18,6 +18,22 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess';
1818import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' ;
1919import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism' ;
2020
21+ // Map tag category names to URL parameter names
22+ const SPEC_TAG_PARAM_MAP : Record < string , string > = {
23+ plot_type : 'plot' ,
24+ data_type : 'data' ,
25+ domain : 'dom' ,
26+ features : 'feat' ,
27+ } ;
28+
29+ const IMPL_TAG_PARAM_MAP : Record < string , string > = {
30+ dependencies : 'dep' ,
31+ techniques : 'tech' ,
32+ patterns : 'pat' ,
33+ dataprep : 'prep' ,
34+ styling : 'style' ,
35+ } ;
36+
2137interface SpecTabsProps {
2238 // Code tab
2339 code : string | null ;
@@ -157,6 +173,15 @@ export function SpecTabs({
157173 const [ tabIndex , setTabIndex ] = useState < number | null > ( overviewMode ? 0 : null ) ;
158174 const [ expandedCategories , setExpandedCategories ] = useState < Record < string , boolean > > ( { } ) ;
159175
176+ // Handle tag click - navigate to filtered catalog (full page navigation)
177+ const handleTagClick = useCallback (
178+ ( paramName : string , value : string ) => {
179+ onTrackEvent ?.( 'tag_click' , { param : paramName , value, source : 'spec_detail' } ) ;
180+ window . location . href = `/?${ paramName } =${ encodeURIComponent ( value ) } ` ;
181+ } ,
182+ [ onTrackEvent ]
183+ ) ;
184+
160185 const toggleCategory = ( category : string ) => {
161186 setExpandedCategories ( ( prev ) => ( { ...prev , [ category ] : ! prev [ category ] } ) ) ;
162187 } ;
@@ -369,37 +394,43 @@ export function SpecTabs({
369394 </ >
370395 ) }
371396
372- { /* Tags grouped by category - compact inline */ }
397+ { /* Tags grouped by category - compact inline, clickable */ }
373398 { tags && Object . keys ( tags ) . length > 0 && (
374399 < Box sx = { { mt : 3 , pt : 2 , borderTop : '1px solid #e5e7eb' , display : 'flex' , flexWrap : 'wrap' , gap : 2 } } >
375- { Object . entries ( tags ) . map ( ( [ category , values ] ) => (
376- < Box key = { category } sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
377- < Typography
378- component = "span"
379- sx = { {
380- fontFamily : '"MonoLisa", monospace' ,
381- fontSize : '0.65rem' ,
382- color : '#9ca3af' ,
383- } }
384- >
385- { category . replace ( / _ / g, ' ' ) } :
386- </ Typography >
387- { values . map ( ( value , i ) => (
388- < Chip
389- key = { i }
390- label = { value }
391- size = "small"
400+ { Object . entries ( tags ) . map ( ( [ category , values ] ) => {
401+ const paramName = SPEC_TAG_PARAM_MAP [ category ] ;
402+ return (
403+ < Box key = { category } sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
404+ < Typography
405+ component = "span"
392406 sx = { {
393407 fontFamily : '"MonoLisa", monospace' ,
394408 fontSize : '0.65rem' ,
395- height : 20 ,
396- bgcolor : '#f3f4f6' ,
397- color : '#4b5563' ,
409+ color : '#9ca3af' ,
398410 } }
399- />
400- ) ) }
401- </ Box >
402- ) ) }
411+ >
412+ { category . replace ( / _ / g, ' ' ) } :
413+ </ Typography >
414+ { values . map ( ( value , i ) => (
415+ < Chip
416+ key = { i }
417+ label = { value }
418+ size = "small"
419+ onClick = { paramName ? ( ) => handleTagClick ( paramName , value ) : undefined }
420+ sx = { {
421+ fontFamily : '"MonoLisa", monospace' ,
422+ fontSize : '0.65rem' ,
423+ height : 20 ,
424+ bgcolor : '#f3f4f6' ,
425+ color : '#4b5563' ,
426+ cursor : paramName ? 'pointer' : 'default' ,
427+ '&:hover' : paramName ? { bgcolor : '#e5e7eb' } : { } ,
428+ } }
429+ />
430+ ) ) }
431+ </ Box >
432+ ) ;
433+ } ) }
403434 </ Box >
404435 ) }
405436
@@ -497,39 +528,45 @@ export function SpecTabs({
497528 </ >
498529 ) }
499530
500- { /* Implementation Tags - only show non-empty categories */ }
531+ { /* Implementation Tags - only show non-empty categories, clickable */ }
501532 { implTags && Object . entries ( implTags ) . some ( ( [ , values ] ) => values && values . length > 0 ) && (
502533 < Box sx = { { mt : 3 , pt : 2 , borderTop : '1px solid #e5e7eb' , display : 'flex' , flexWrap : 'wrap' , gap : 2 } } >
503534 { Object . entries ( implTags )
504535 . filter ( ( [ , values ] ) => values && values . length > 0 )
505- . map ( ( [ category , values ] ) => (
506- < Box key = { category } sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
507- < Typography
508- component = "span"
509- sx = { {
510- fontFamily : '"MonoLisa", monospace' ,
511- fontSize : '0.65rem' ,
512- color : '#9ca3af' ,
513- } }
514- >
515- { category } :
516- </ Typography >
517- { values . map ( ( value , i ) => (
518- < Chip
519- key = { i }
520- label = { value }
521- size = "small"
536+ . map ( ( [ category , values ] ) => {
537+ const paramName = IMPL_TAG_PARAM_MAP [ category ] ;
538+ return (
539+ < Box key = { category } sx = { { display : 'flex' , alignItems : 'center' , gap : 0.5 } } >
540+ < Typography
541+ component = "span"
522542 sx = { {
523543 fontFamily : '"MonoLisa", monospace' ,
524544 fontSize : '0.65rem' ,
525- height : 20 ,
526- bgcolor : '#f3f4f6' ,
527- color : '#4b5563' ,
545+ color : '#9ca3af' ,
528546 } }
529- />
530- ) ) }
531- </ Box >
532- ) ) }
547+ >
548+ { category } :
549+ </ Typography >
550+ { values . map ( ( value , i ) => (
551+ < Chip
552+ key = { i }
553+ label = { value }
554+ size = "small"
555+ onClick = { paramName ? ( ) => handleTagClick ( paramName , value ) : undefined }
556+ sx = { {
557+ fontFamily : '"MonoLisa", monospace' ,
558+ fontSize : '0.65rem' ,
559+ height : 20 ,
560+ bgcolor : '#f3f4f6' ,
561+ color : '#4b5563' ,
562+ cursor : paramName ? 'pointer' : 'default' ,
563+ '&:hover' : paramName ? { bgcolor : '#e5e7eb' } : { } ,
564+ } }
565+ />
566+ ) ) }
567+ </ Box >
568+ ) ;
569+ } ) }
533570 </ Box >
534571 ) }
535572
0 commit comments