Skip to content

Commit 55f5b4e

Browse files
feat: make tags clickable to navigate to filtered catalog (#2434)
Clicking tags in Spec or Impl tabs now navigates to the filtered catalog page. Examples: - features: basic → /?feat=basic - styling: alpha-blending → /?style=alpha-blending Uses window.location.href for reliable full-page navigation instead of React Router's navigate() which had issues with nested routes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 34ae78b commit 55f5b4e

1 file changed

Lines changed: 87 additions & 50 deletions

File tree

app/src/components/SpecTabs.tsx

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess';
1818
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
1919
import { 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+
2137
interface 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

Comments
 (0)