



















@@ -8,7 +8,7 @@ struct SettingsRootView: View {
88@State private var monitoringPermissions = false
99@State private var selectedTab: SettingsTab = .general
1010@State private var cachedTabs: Set<SettingsTab>
11-@State private var columnVisibility: NavigationSplitViewVisibility = .all
11+@State private var sidebarVisible = true
1212@State private var snapshotPaths: (configPath: String?, stateDir: String?) = (nil, nil)
1313let updater: UpdaterProviding?
1414private let isPreview = ProcessInfo.processInfo.isPreview
@@ -23,38 +23,37 @@ struct SettingsRootView: View {
2323}
24242525var body: some View {
26-NavigationSplitView(columnVisibility: self.$columnVisibility) {
27-List(selection: self.$selectedTab) {
28-ForEach(self.visibleGroups) { group in
29-Section(group.title) {
30-ForEach(group.tabs) { tab in
31-NavigationLink(value: tab) {
32-Label(tab.title, systemImage: tab.systemImage)
33-}
34-}
35-}
36-}
26+HStack(spacing: 0) {
27+if self.sidebarVisible {
28+SettingsSidebar(
29+ groups: self.visibleGroups,
30+ selectedTab: self.$selectedTab)
31+.frame(width: SettingsLayout.sidebarWidth)
32+.transition(.move(edge: .leading).combined(with: .opacity))
3733}
38-.navigationSplitViewColumnWidth(min: 190, ideal: 210, max: 240)
39-} detail: {
40-VStack(alignment: .leading, spacing: 14) {
41-if self.isNixMode {
42-self.nixManagedBanner
43-}
44-self.cachedDetailViews
45-}
46-.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
47-.padding(.horizontal, 22)
48-.padding(.vertical, 18)
34+35+self.detailContainer
4936}
50-.navigationSplitViewStyle(.balanced)
5137.frame(width: SettingsTab.windowWidth, height: SettingsTab.windowHeight, alignment: .topLeading)
5238.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
5339.background(SettingsWindowChromeConfigurator())
40+.toolbar {
41+ToolbarItem(placement: .navigation) {
42+Button {
43+withAnimation(.spring(response: 0.28, dampingFraction: 0.86)) {
44+self.sidebarVisible.toggle()
45+}
46+} label: {
47+Image(systemName: "sidebar.leading")
48+}
49+.help(self.sidebarVisible ? "Hide Sidebar" : "Show Sidebar")
50+}
51+}
5452.onReceive(NotificationCenter.default.publisher(for: .openclawSelectSettingsTab)) { note in
5553if let tab = note.object as? SettingsTab {
5654withAnimation(.spring(response: 0.32, dampingFraction: 0.85)) {
5755self.selectedTab = self.validTab(for: tab)
56+self.sidebarVisible = true
5857}
5958}
6059}
@@ -93,6 +92,18 @@ struct SettingsRootView: View {
9392SettingsTabGroup.defaultGroups(showDebug: self.state.debugPaneEnabled)
9493}
959495+private var detailContainer: some View {
96+VStack(alignment: .leading, spacing: 14) {
97+if self.isNixMode {
98+self.nixManagedBanner
99+}
100+self.cachedDetailViews
101+}
102+.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
103+.padding(.horizontal, SettingsLayout.detailHorizontalPadding)
104+.padding(.vertical, 18)
105+}
106+96107private var cachedDetailTabs: [SettingsTab] {
97108let cached = self.cachedTabs.union([self.selectedTab])
98109return self.visibleGroups.flatMap(\.tabs).filter { cached.contains($0) }
@@ -207,6 +218,77 @@ struct SettingsRootView: View {
207218}
208219}
209220221+private struct SettingsSidebar: View {
222+let groups: [SettingsTabGroup]
223+@Binding var selectedTab: SettingsTab
224+225+var body: some View {
226+ZStack(alignment: .topLeading) {
227+VisualEffectView(material: .sidebar)
228+.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
229+.overlay {
230+RoundedRectangle(cornerRadius: 16, style: .continuous)
231+.strokeBorder(.white.opacity(0.09), lineWidth: 1)
232+}
233+.shadow(color: .black.opacity(0.16), radius: 18, x: 0, y: 12)
234+235+ScrollView(.vertical) {
236+VStack(alignment: .leading, spacing: 18) {
237+ForEach(self.groups) { group in
238+VStack(alignment: .leading, spacing: 6) {
239+Text(group.title)
240+.font(.caption.weight(.semibold))
241+.foregroundStyle(.secondary)
242+.padding(.horizontal, 8)
243+244+ForEach(group.tabs) { tab in
245+SettingsSidebarRow(
246+ tab: tab,
247+ selected: self.selectedTab == tab)
248+{
249+self.selectedTab = tab
250+}
251+}
252+}
253+.frame(maxWidth: .infinity, alignment: .leading)
254+}
255+}
256+.frame(maxWidth: .infinity, alignment: .leading)
257+.padding(.horizontal, 10)
258+.padding(.vertical, 10)
259+}
260+}
261+.padding(.leading, 12)
262+.padding(.vertical, 10)
263+}
264+}
265+266+private struct SettingsSidebarRow: View {
267+let tab: SettingsTab
268+let selected: Bool
269+let select: () -> Void
270+271+var body: some View {
272+Label(self.tab.title, systemImage: self.tab.systemImage)
273+.font(.body.weight(.medium))
274+.labelStyle(.titleAndIcon)
275+.frame(maxWidth: .infinity, alignment: .leading)
276+.padding(.horizontal, 10)
277+.padding(.vertical, 8)
278+.frame(maxWidth: .infinity, alignment: .leading)
279+.background {
280+RoundedRectangle(cornerRadius: 10, style: .continuous)
281+.fill(self.selected ? Color.white.opacity(0.13) : Color.clear)
282+}
283+.contentShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
284+.onTapGesture(perform: self.select)
285+.accessibilityElement(children: .combine)
286+.accessibilityLabel(self.tab.title)
287+.accessibilityAddTraits(self.selected ? [.isButton, .isSelected] : .isButton)
288+.accessibilityAction { self.select() }
289+}
290+}
291+210292private struct SettingsTabGroup: Identifiable {
211293let title: String
212294let tabs: [SettingsTab]
@@ -235,7 +317,7 @@ private struct SettingsTabGroup: Identifiable {
235317enum SettingsTab: CaseIterable, Identifiable, Hashable {
236318case general, connection, permissions, voiceWake, channels, skills, cron
237319case execApprovals, sessions, instances, config, debug, about
238-static let windowWidth: CGFloat = 960
320+static let windowWidth: CGFloat = 1120
239321static let windowHeight: CGFloat = 790
240322241323var id: Self {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。