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%
4.6 KB · 134 lines swift
Raw Blame History
1//2//  ToolbarView.swift3//  Zyquo Atlas4//5//  Author: Simon-Pierre Boucher6//  Mail: contact@spboucher.ai7//8//  The unified browser toolbar: back / forward / reload-or-stop, the omnibox,9//  and a new-tab button. A determinate progress bar hairline sits under the10//  toolbar while a page loads. The AI panel toggle and profile switcher attach11//  here in later phases.12//1314import SwiftUI1516struct ToolbarView: View {17    @ObservedObject var tab: Tab18    let isAISidebarOpen: Bool19    let onSubmit: (String) -> Void20    let onNewTab: () -> Void21    let onToggleAI: () -> Void22    let onCustomize: () -> Void23    var downloadCount: Int = 024    var onToggleDownloads: () -> Void = {}25    @EnvironmentObject private var themeEngine: ThemeEngine26    @EnvironmentObject private var windowState: WindowState27    private var theme: AtlasTheme { themeEngine.theme }2829    var body: some View {30        VStack(spacing: 0) {31            HStack(spacing: ZyquoSpacing.xs) {32                navButton("chevron.backward", enabled: tab.canGoBack) { tab.goBack() }33                navButton("chevron.forward", enabled: tab.canGoForward) { tab.goForward() }34                if tab.isLoading {35                    navButton("xmark", enabled: true) { tab.stop() }36                } else {37                    navButton("arrow.clockwise", enabled: tab.url != nil) { tab.reload() }38                }39                readerButton4041                OmniboxView(tab: tab, onSubmit: onSubmit,42                            onAsk: { windowState.askOmnibox($0) })43                    .frame(maxWidth: .infinity)4445                navButton("plus", enabled: true, action: onNewTab)46                downloadsButton47                navButton("paintbrush", enabled: true, action: onCustomize)48                aiToggle49            }50            .padding(.horizontal, ZyquoSpacing.sm)51            .frame(height: themeEngine.layout.toolbarHeight)5253            progressBar54        }55        .background(theme.surface)56        .overlay(alignment: .bottom) {57            Rectangle()58                .fill(theme.border)59                .frame(height: ZyquoMetrics.hairline)60        }61    }6263    // MARK: - Progress6465    @ViewBuilder66    private var progressBar: some View {67        GeometryReader { geo in68            if tab.showsProgress {69                Rectangle()70                    .fill(theme.accent)71                    .frame(width: geo.size.width * max(0.02, tab.estimatedProgress))72                    .animation(.easeOut(duration: 0.2), value: tab.estimatedProgress)73            }74        }75        .frame(height: 2)76    }7778    private var readerButton: some View {79        Button { windowState.toggleReader() } label: {80            Image(systemName: windowState.showReader ? "doc.plaintext.fill" : "doc.plaintext")81                .font(.system(size: 13, weight: .medium))82                .frame(width: 28, height: 28)83                .contentShape(Rectangle())84        }85        .buttonStyle(.plain)86        .foregroundStyle(windowState.showReader ? theme.accent : theme.textSecondary)87        .disabled(tab.url == nil)88        .help("Reader mode")89    }9091    private var downloadsButton: some View {92        Button(action: onToggleDownloads) {93            Image(systemName: "arrow.down.circle")94                .font(.system(size: 13, weight: .medium))95                .frame(width: 28, height: 28)96                .contentShape(Rectangle())97                .overlay(alignment: .topTrailing) {98                    if downloadCount > 0 {99                        Circle().fill(theme.accent).frame(width: 8, height: 8).offset(x: -4, y: 4)100                    }101                }102        }103        .buttonStyle(.plain)104        .foregroundStyle(theme.textSecondary)105        .help("Downloads")106    }107108    private var aiToggle: some View {109        Button(action: onToggleAI) {110            Image(systemName: "sparkles")111                .font(.system(size: 13, weight: .medium))112                .frame(width: 28, height: 28)113                .contentShape(Rectangle())114        }115        .buttonStyle(.plain)116        .foregroundStyle(isAISidebarOpen ? theme.accentIndigo : theme.textSecondary)117        .help("Toggle Atlas AI sidebar")118    }119120    // MARK: - Buttons121122    private func navButton(_ symbol: String, enabled: Bool, action: @escaping () -> Void) -> some View {123        Button(action: action) {124            Image(systemName: symbol)125                .font(.system(size: 13, weight: .medium))126                .frame(width: 28, height: 28)127                .contentShape(Rectangle())128        }129        .buttonStyle(.plain)130        .foregroundStyle(enabled ? theme.textSecondary : theme.textTertiary.opacity(0.5))131        .disabled(!enabled)132    }133}134