// // TabBarView.swift // Zyquo Atlas // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Top horizontal tab strip. Each tab shows its title (and a spinner while // loading), the active tab is tinted with the accent, and hovering reveals a // close button. Phase 4 adds the vertical/left layout option, drag-reorder, // pinning, and hover previews; this is the top-bar baseline. // import SwiftUI struct TabBarView: View { @ObservedObject var tabManager: TabManager @EnvironmentObject private var themeEngine: ThemeEngine private var theme: AtlasTheme { themeEngine.theme } var body: some View { ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: ZyquoSpacing.xxs) { ForEach(tabManager.tabs) { tab in TabChip( tab: tab, isActive: tab.id == tabManager.activeTabID, onSelect: { tabManager.selectTab(tab.id) }, onClose: { tabManager.closeTab(tab.id) }, onTogglePin: { tabManager.togglePin(tab.id) } ) } } .padding(.horizontal, ZyquoSpacing.xs) } .frame(height: ZyquoMetrics.tabBarHeight) .background(theme.backgroundColor) } } private struct TabChip: View { @ObservedObject var tab: Tab let isActive: Bool let onSelect: () -> Void let onClose: () -> Void var onTogglePin: () -> Void = {} @EnvironmentObject private var themeEngine: ThemeEngine private var theme: AtlasTheme { themeEngine.theme } @State private var hovering = false /// Pinned tabs are compact (icon only); suspended tabs dim. private var isCompact: Bool { tab.isPinned } var body: some View { HStack(spacing: ZyquoSpacing.xs) { leading .frame(width: 14, height: 14) if !isCompact { Text(tab.title) .font(ZyquoFont.control) .lineLimit(1) .foregroundStyle(isActive ? theme.textPrimary : theme.textSecondary) Spacer(minLength: 0) if hovering || isActive { Button(action: onClose) { Image(systemName: "xmark") .font(.system(size: 9, weight: .bold)) .frame(width: 16, height: 16) .contentShape(Rectangle()) } .buttonStyle(.plain) .foregroundStyle(theme.textTertiary) } } } .padding(.horizontal, ZyquoSpacing.sm) .frame(width: isCompact ? 44 : ZyquoMetrics.tabMinWidth, height: ZyquoMetrics.tabBarHeight - 6) .opacity(tab.isSuspended && !isActive ? 0.6 : 1) .background( RoundedRectangle(cornerRadius: ZyquoRadius.small) .fill(isActive ? theme.accentSubtle : (hovering ? theme.surfaceSecondary : .clear)) ) .overlay(alignment: .bottom) { if isActive { Rectangle().fill(theme.accent).frame(height: 2) .padding(.horizontal, ZyquoSpacing.xs) } } .contentShape(Rectangle()) .onTapGesture(perform: onSelect) .onHover { hovering = $0 } .help(tab.title) .contextMenu { Button(tab.isPinned ? "Unpin Tab" : "Pin Tab", action: onTogglePin) Button("Close Tab", action: onClose) } } @ViewBuilder private var leading: some View { if tab.isLoading { ProgressView() .controlSize(.mini) .scaleEffect(0.7) } else { Image(systemName: "globe") .font(.system(size: 10)) .foregroundStyle(theme.textTertiary) } } }