fix: custom connections sidebar
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
- TUI: migrate key handling to the updated pi-tui Key matcher API.
|
- TUI: migrate key handling to the updated pi-tui Key matcher API.
|
||||||
- macOS: prefer gateway config reads/writes in local mode (fall back to disk if the gateway is unavailable).
|
- macOS: prefer gateway config reads/writes in local mode (fall back to disk if the gateway is unavailable).
|
||||||
- macOS: local gateway now connects via tailnet IP when bind mode is `tailnet`/`auto`.
|
- macOS: local gateway now connects via tailnet IP when bind mode is `tailnet`/`auto`.
|
||||||
- macOS: Connections removes the sidebar toggle from the Settings toolbar to avoid overflow.
|
- macOS: Connections settings now use a custom sidebar to avoid toolbar toggle issues, with rounded styling and full-width row hit targets.
|
||||||
- macOS: drop deprecated `afterMs` from agent wait params to match gateway schema.
|
- macOS: drop deprecated `afterMs` from agent wait params to match gateway schema.
|
||||||
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
|
- Auth: add OpenAI Codex OAuth support and migrate legacy oauth.json into auth.json.
|
||||||
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
- Docs: clarify auth storage, migration, and OpenAI Codex OAuth onboarding.
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import SwiftUI
|
|||||||
|
|
||||||
extension ConnectionsSettings {
|
extension ConnectionsSettings {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationSplitView {
|
HStack(spacing: 0) {
|
||||||
self.sidebar
|
self.sidebar
|
||||||
} detail: {
|
|
||||||
self.detail
|
self.detail
|
||||||
}
|
}
|
||||||
.navigationSplitViewStyle(.balanced)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
.toolbar(removing: .sidebarToggle)
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.store.start()
|
self.store.start()
|
||||||
self.ensureSelection()
|
self.ensureSelection()
|
||||||
@@ -20,27 +18,30 @@ extension ConnectionsSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var sidebar: some View {
|
private var sidebar: some View {
|
||||||
List(selection: self.$selectedProvider) {
|
ScrollView {
|
||||||
if !self.enabledProviders.isEmpty {
|
LazyVStack(alignment: .leading, spacing: 8) {
|
||||||
Section("Configured") {
|
if !self.enabledProviders.isEmpty {
|
||||||
|
self.sidebarSectionHeader("Configured")
|
||||||
ForEach(self.enabledProviders) { provider in
|
ForEach(self.enabledProviders) { provider in
|
||||||
self.sidebarRow(provider)
|
self.sidebarRow(provider)
|
||||||
.tag(provider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !self.availableProviders.isEmpty {
|
if !self.availableProviders.isEmpty {
|
||||||
Section("Available") {
|
self.sidebarSectionHeader("Available")
|
||||||
ForEach(self.availableProviders) { provider in
|
ForEach(self.availableProviders) { provider in
|
||||||
self.sidebarRow(provider)
|
self.sidebarRow(provider)
|
||||||
.tag(provider)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
}
|
}
|
||||||
.listStyle(.sidebar)
|
.frame(minWidth: 220, idealWidth: 240, maxWidth: 280, maxHeight: .infinity, alignment: .topLeading)
|
||||||
.frame(minWidth: 220, idealWidth: 240, maxWidth: 280)
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||||
|
.fill(Color(nsColor: .windowBackgroundColor)))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||||
}
|
}
|
||||||
|
|
||||||
private var detail: some View {
|
private var detail: some View {
|
||||||
@@ -81,18 +82,41 @@ extension ConnectionsSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func sidebarRow(_ provider: ConnectionProvider) -> some View {
|
private func sidebarRow(_ provider: ConnectionProvider) -> some View {
|
||||||
HStack(spacing: 8) {
|
let isSelected = self.selectedProvider == provider
|
||||||
Circle()
|
return Button {
|
||||||
.fill(self.providerTint(provider))
|
self.selectedProvider = provider
|
||||||
.frame(width: 8, height: 8)
|
} label: {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
HStack(spacing: 8) {
|
||||||
Text(provider.title)
|
Circle()
|
||||||
Text(self.providerSummary(provider))
|
.fill(self.providerTint(provider))
|
||||||
.font(.caption)
|
.frame(width: 8, height: 8)
|
||||||
.foregroundStyle(.secondary)
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(provider.title)
|
||||||
|
Text(self.providerSummary(provider))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.padding(.horizontal, 6)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(isSelected ? Color.accentColor.opacity(0.18) : Color.clear)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||||||
|
.background(Color.clear) // ensure full-width hit test area
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.padding(.vertical, 4)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sidebarSectionHeader(_ title: String) -> some View {
|
||||||
|
Text(title)
|
||||||
|
.font(.caption.weight(.semibold))
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.textCase(.uppercase)
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
.padding(.top, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func detailHeader(for provider: ConnectionProvider) -> some View {
|
private func detailHeader(for provider: ConnectionProvider) -> some View {
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ struct SettingsRootView: View {
|
|||||||
}
|
}
|
||||||
.padding(.horizontal, 28)
|
.padding(.horizontal, 28)
|
||||||
.padding(.vertical, 22)
|
.padding(.vertical, 22)
|
||||||
.background(SettingsToolbarCleaner())
|
|
||||||
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
|
.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in
|
.onReceive(NotificationCenter.default.publisher(for: .clawdbotSelectSettingsTab)) { note in
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import AppKit
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct SettingsToolbarCleaner: NSViewRepresentable {
|
|
||||||
func makeNSView(context: Context) -> NSView {
|
|
||||||
NSView()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateNSView(_ nsView: NSView, context: Context) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
guard let toolbar = nsView.window?.toolbar else { return }
|
|
||||||
let items = toolbar.items
|
|
||||||
for (index, item) in items.enumerated().reversed() {
|
|
||||||
let isSidebarToggle =
|
|
||||||
item.itemIdentifier == .toggleSidebar
|
|
||||||
|| item.itemIdentifier.rawValue == "com.apple.NSToolbarShowSidebarItem"
|
|
||||||
if isSidebarToggle {
|
|
||||||
toolbar.removeItem(at: index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user