SPB Git

spb/zyquo-atlas Public License

The AI-native macOS web browser — every surface, intelligent.

Swift 75.2% JavaScript 22% Shell 2% Makefile 0.9%
3.7 KB · 104 lines swift
Raw Blame History
1//2//  VerticalTabBarView.swift3//  Zyquo Atlas4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The left-vertical tab sidebar (Arc-style), used when the layout's9//  tabPosition is .left. Same TabManager, a stacked column of tabs with a10//  new-tab button at the top. Themed live by ThemeEngine.11//1213import SwiftUI1415struct VerticalTabBarView: View {16    @ObservedObject var tabManager: TabManager17    let onNewTab: () -> Void18    @EnvironmentObject private var themeEngine: ThemeEngine19    private var theme: AtlasTheme { themeEngine.theme }2021    var body: some View {22        VStack(alignment: .leading, spacing: themeEngine.layout.rowSpacing) {23            HStack {24                Text("Zyquo Atlas")25                    .font(themeEngine.layout.font(12, weight: .semibold))26                    .foregroundStyle(theme.textSecondary)27                Spacer()28                Button(action: onNewTab) {29                    Image(systemName: "plus")30                        .font(.system(size: 11, weight: .bold))31                        .frame(width: 22, height: 22)32                        .contentShape(Rectangle())33                }34                .buttonStyle(.plain)35                .foregroundStyle(theme.textSecondary)36            }37            .padding(.bottom, ZyquoSpacing.xxs)3839            ScrollView(showsIndicators: false) {40                VStack(spacing: themeEngine.layout.rowSpacing) {41                    ForEach(tabManager.tabs) { tab in42                        VerticalTabRow(43                            tab: tab,44                            isActive: tab.id == tabManager.activeTabID,45                            onSelect: { tabManager.selectTab(tab.id) },46                            onClose: { tabManager.closeTab(tab.id) }47                        )48                    }49                }50            }51            Spacer()52        }53        .padding(ZyquoSpacing.xs)54        .frame(width: themeEngine.layout.verticalTabWidth)55        .background(theme.surfaceSecondary)56        .overlay(alignment: .trailing) {57            Rectangle().fill(theme.border).frame(width: ZyquoMetrics.hairline)58        }59    }60}6162private struct VerticalTabRow: View {63    @ObservedObject var tab: Tab64    let isActive: Bool65    let onSelect: () -> Void66    let onClose: () -> Void67    @EnvironmentObject private var themeEngine: ThemeEngine68    private var theme: AtlasTheme { themeEngine.theme }69    @State private var hovering = false7071    var body: some View {72        HStack(spacing: ZyquoSpacing.xs) {73            if tab.isLoading {74                ProgressView().controlSize(.mini).scaleEffect(0.6).frame(width: 14)75            } else {76                Image(systemName: "globe").font(.system(size: 10))77                    .foregroundStyle(theme.textTertiary).frame(width: 14)78            }79            Text(tab.title)80                .font(themeEngine.layout.font(12.5))81                .lineLimit(1)82                .foregroundStyle(isActive ? theme.textPrimary : theme.textSecondary)83            Spacer(minLength: 0)84            if hovering || isActive {85                Button(action: onClose) {86                    Image(systemName: "xmark").font(.system(size: 9, weight: .bold))87                        .frame(width: 16, height: 16).contentShape(Rectangle())88                }89                .buttonStyle(.plain)90                .foregroundStyle(theme.textTertiary)91            }92        }93        .padding(.horizontal, ZyquoSpacing.xs)94        .frame(height: 30)95        .background(96            RoundedRectangle(cornerRadius: ZyquoRadius.small)97                .fill(isActive ? theme.accentSubtle : (hovering ? theme.surface : .clear))98        )99        .contentShape(Rectangle())100        .onTapGesture(perform: onSelect)101        .onHover { hovering = $0 }102    }103}104