1+ import { useState , useEffect } from 'react' ;
12import type { ScenarioClientPayload } from '../../hooks/useScenario' ;
23
34type Tab = 'sim' | 'viz' | 'settings' | 'reports' | 'chat' | 'log' | 'about' ;
@@ -8,6 +9,27 @@ interface TabBarProps {
89 scenario : ScenarioClientPayload ;
910}
1011
12+ function TabIcon ( { id, size = 16 } : { id : Tab ; size ?: number } ) {
13+ const s = size ;
14+ const props = { width : s , height : s , viewBox : '0 0 24 24' , fill : 'none' , stroke : 'currentColor' , strokeWidth : 2 , strokeLinecap : 'round' as const , strokeLinejoin : 'round' as const } ;
15+ switch ( id ) {
16+ case 'sim' :
17+ return < svg { ...props } > < path d = "M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" /> </ svg > ;
18+ case 'viz' :
19+ return < svg { ...props } > < circle cx = "8" cy = "8" r = "3" /> < circle cx = "16" cy = "16" r = "3" /> < circle cx = "18" cy = "8" r = "2" /> < circle cx = "6" cy = "16" r = "2" /> < line x1 = "10.5" y1 = "9.5" x2 = "14" y2 = "14" /> </ svg > ;
20+ case 'settings' :
21+ return < svg { ...props } > < circle cx = "12" cy = "12" r = "3" /> < path d = "M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" /> </ svg > ;
22+ case 'reports' :
23+ return < svg { ...props } > < path d = "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> < polyline points = "14 2 14 8 20 8" /> < line x1 = "16" y1 = "13" x2 = "8" y2 = "13" /> < line x1 = "16" y1 = "17" x2 = "8" y2 = "17" /> </ svg > ;
24+ case 'chat' :
25+ return < svg { ...props } > < path d = "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /> </ svg > ;
26+ case 'log' :
27+ return < svg { ...props } > < polyline points = "4 17 10 11 4 5" /> < line x1 = "12" y1 = "19" x2 = "20" y2 = "19" /> </ svg > ;
28+ case 'about' :
29+ return < svg { ...props } > < circle cx = "12" cy = "12" r = "10" /> < line x1 = "12" y1 = "16" x2 = "12" y2 = "12" /> < line x1 = "12" y1 = "8" x2 = "12.01" y2 = "8" /> </ svg > ;
30+ }
31+ }
32+
1133const TABS : Array < { id : Tab ; label : string } > = [
1234 { id : 'sim' , label : 'SIM' } ,
1335 { id : 'viz' , label : 'VIZ' } ,
@@ -18,8 +40,17 @@ const TABS: Array<{ id: Tab; label: string }> = [
1840 { id : 'about' , label : 'ABOUT' } ,
1941] ;
2042
43+ const MOBILE_BREAKPOINT = 640 ;
44+
2145export function TabBar ( { active, onTabChange, scenario } : TabBarProps ) {
2246 const tabs = TABS . filter ( t => t . id !== 'chat' || scenario . policies . characterChat ) ;
47+ const [ compact , setCompact ] = useState ( window . innerWidth < MOBILE_BREAKPOINT ) ;
48+
49+ useEffect ( ( ) => {
50+ const onResize = ( ) => setCompact ( window . innerWidth < MOBILE_BREAKPOINT ) ;
51+ window . addEventListener ( 'resize' , onResize ) ;
52+ return ( ) => window . removeEventListener ( 'resize' , onResize ) ;
53+ } , [ ] ) ;
2354
2455 return (
2556 < nav
@@ -35,24 +66,30 @@ export function TabBar({ active, onTabChange, scenario }: TabBarProps) {
3566 role = "tab"
3667 aria-selected = { active === tab . id }
3768 aria-controls = { `panel-${ tab . id } ` }
69+ aria-label = { tab . label }
3870 id = { `tab-${ tab . id } ` }
3971 className = "cursor-pointer transition-colors"
4072 style = { {
41- padding : '8px 0' ,
73+ padding : compact ? '8px 0' : '8px 0' ,
4274 flex : 1 ,
4375 fontFamily : 'var(--sans)' ,
44- fontSize : '12px' ,
76+ fontSize : compact ? '10px' : '12px' ,
4577 fontWeight : 700 ,
46- letterSpacing : '0.5px' ,
78+ letterSpacing : compact ? '0' : '0.5px' ,
4779 textTransform : 'uppercase' as const ,
4880 color : active === tab . id ? 'var(--amber)' : 'var(--text-3)' ,
4981 background : active === tab . id ? 'var(--bg-card)' : 'transparent' ,
5082 border : 'none' ,
5183 borderBottom : active === tab . id ? '2px solid var(--amber)' : '2px solid transparent' ,
5284 marginBottom : '-1px' ,
85+ display : 'flex' ,
86+ flexDirection : 'column' ,
87+ alignItems : 'center' ,
88+ gap : compact ? '2px' : '0' ,
5389 } }
5490 >
55- { tab . label }
91+ { compact && < TabIcon id = { tab . id } size = { 16 } /> }
92+ { ! compact && tab . label }
5693 </ button >
5794 ) ) }
5895 </ nav >
0 commit comments