import { Caption1, CounterBadge, makeStyles, mergeClasses, tokens, shorthands } from '@fluentui/react-components' import { ArrowDownloadFilled, ArrowDownloadRegular, HistoryRegular, LibraryRegular, WindowConsoleRegular, SettingsRegular, WeatherMoonRegular, WeatherSunnyRegular, DesktopRegular, PanelLeftContractRegular, PanelLeftExpandRegular } from '@fluentui/react-icons' import type { ThemeMode } from '@shared/ipc' import { Hint } from './Hint' import { SegmentedControl } from './ui/SegmentedControl' import { IconButton } from './ui/IconButton' import { useFocusStyles } from './ui/focusRing' import { useTextStyles } from './ui/text' import { MOTION, ICON } from './ui/tokens' export type TabValue = 'downloads' | 'library' | 'history' | 'terminal' | 'settings' const useStyles = makeStyles({ root: { width: '212px', flexShrink: 0, display: 'flex', flexDirection: 'column', gap: '4px', padding: '16px 12px', backgroundColor: tokens.colorNeutralBackground1, borderRight: `1px solid ${tokens.colorNeutralStroke2}`, // The app's one motion timing, gated globally on prefers-reduced-motion (UI26). transition: `width ${MOTION.duration} ${MOTION.curve}` }, rootCollapsed: { width: '60px', alignItems: 'center' }, topBar: { display: 'flex', justifyContent: 'flex-end', paddingBottom: '2px' }, topBarCollapsed: { justifyContent: 'center' }, brand: { display: 'flex', alignItems: 'center', gap: '11px', padding: '0 10px 14px' }, brandCollapsed: { padding: '0 0 12px', justifyContent: 'center' }, mark: { width: '36px', height: '36px', flexShrink: 0, borderRadius: tokens.borderRadiusLarge, backgroundColor: tokens.colorBrandBackground, color: tokens.colorNeutralForegroundOnBrand, display: 'flex', alignItems: 'center', justifyContent: 'center', // Brand tile glyph, snapped to the nearest ICON tier (UI11 — no literal px). fontSize: `${ICON.control}px` }, brandText: { display: 'flex', flexDirection: 'column', minWidth: 0 }, brandName: { fontSize: tokens.fontSizeBase400, fontWeight: tokens.fontWeightSemibold, lineHeight: tokens.lineHeightBase400, color: tokens.colorNeutralForeground1 }, nav: { display: 'flex', flexDirection: 'column', gap: '3px', alignSelf: 'stretch' }, navItem: { // relative so the collapsed count badge can pin to the icon's corner. position: 'relative', display: 'flex', alignItems: 'center', gap: '11px', width: '100%', padding: '9px 12px', ...shorthands.borderRadius(tokens.borderRadiusMedium), border: 'none', backgroundColor: 'transparent', color: tokens.colorNeutralForeground2, fontSize: tokens.fontSizeBase300, fontFamily: tokens.fontFamilyBase, textAlign: 'left', cursor: 'pointer', ':hover': { backgroundColor: tokens.colorNeutralBackground1Hover } }, // Expanded: the active-download count sits at the far right of the nav row. navBadge: { marginLeft: 'auto' }, // Collapsed: pin the count to the top-right corner of the centered icon. navBadgeCollapsed: { position: 'absolute', top: '4px', right: '8px' }, navItemCollapsed: { justifyContent: 'center', padding: '9px 0' }, navItemActive: { backgroundColor: tokens.colorBrandBackground2, color: tokens.colorBrandForeground2, fontWeight: tokens.fontWeightSemibold, ':hover': { backgroundColor: tokens.colorBrandBackground2 } }, navIcon: { fontSize: `${ICON.control}px`, flexShrink: 0, display: 'flex' }, spacer: { flexGrow: 1 } }) const NAV: { value: TabValue; label: string; icon: React.JSX.Element }[] = [ { value: 'downloads', label: 'Downloads', icon: }, { value: 'library', label: 'Library', icon: }, { value: 'history', label: 'History', icon: }, { value: 'terminal', label: 'Terminal', icon: }, { value: 'settings', label: 'Settings', icon: } ] const THEMES: { value: ThemeMode; label: string; icon: React.JSX.Element }[] = [ { value: 'light', label: 'Light', icon: }, { value: 'dark', label: 'Dark', icon: }, { value: 'system', label: 'Auto', icon: } ] interface SidebarProps { tab: TabValue onTabChange: (t: TabValue) => void /** the current theme preference: explicit light/dark, or 'system' to follow the OS */ theme: ThemeMode /** whether the resolved theme is currently dark (drives the collapsed toggle icon) */ isDark: boolean onSetTheme: (mode: ThemeMode) => void /** AeroFetch version string, e.g. '0.3.2' ('' until loaded) */ version: string collapsed: boolean onToggleCollapsed: () => void /** Show the Terminal nav item (only when custom commands are enabled) */ showTerminal: boolean /** Active (downloading + queued) count, badged on the Downloads nav item (UX9). */ downloadCount: number } export function Sidebar({ tab, onTabChange, theme, isDark, onSetTheme, version, collapsed, onToggleCollapsed, showTerminal, downloadCount }: SidebarProps): React.JSX.Element { const styles = useStyles() const focus = useFocusStyles() const text = useTextStyles() // Collapsed view shows one button that cycles Light → Dark → Auto. const order: ThemeMode[] = ['light', 'dark', 'system'] function cycleTheme(): void { const next = order[(order.indexOf(theme) + 1) % order.length] if (next) onSetTheme(next) } const themeIcon = theme === 'system' ? ( ) : isDark ? ( ) : ( ) const themeLabel = theme === 'system' ? 'Auto (system)' : isDark ? 'Dark' : 'Light' return ( ) }