Liquid Glass Phase 1: segmented-selection tab bars and filter chips

First Liquid Glass adoption in Confab (deployment target is already
macOS 26.2, so no #available gating needed anywhere). Converts the two
confirmed-safe, non-scrolling segmented selectors:

- Settings' top-level 12-tab bar (tabButton) and yesterday's new MCP-tab
  sidebar (mcpSidebarRow): selected-state Color.blue.opacity(0.1)
  backgrounds become tinted .glassEffect(), wrapped in
  GlassEffectContainer. Shipped without morphing (@Namespace/
  glassEffectID) for now, since at most one item is visible at a time in
  steady state — morphing is a stretch goal only if wanted after seeing
  this.
- ModelSelectorView's filter/category/favorites/sort chip row: flat
  Color.opacity() pill backgrounds become .glassEffect(), all wrapped in
  one GlassEffectContainer for a real multi-element merge demo (several
  chips can be active simultaneously here, unlike the single-selection
  tab bars).

Deliberately NOT touched in this phase: anything inside a ScrollView/
List (formSection cards, chat bubbles, sidebar rows) per Apple's own
no-glass-in-scroll-views guidance, and the two ChatView anti-patterns
(Header/FooterView's .ultraThinMaterial) which need a bigger safeAreaBar
restructuring, planned as Phase 2.
This commit is contained in:
2026-08-21 14:38:25 +02:00
parent b8e1986cdb
commit ff26a57d26
2 changed files with 44 additions and 33 deletions
+9 -12
View File
@@ -96,6 +96,7 @@ struct ModelSelectorView: View {
}
// Filters + Sort
GlassEffectContainer(spacing: 12) {
HStack(spacing: 12) {
FilterToggle(isOn: $filterVision, icon: "\u{1F441}\u{FE0F}", label: "Vision")
FilterToggle(isOn: $filterTools, icon: "\u{1F527}", label: "Tools")
@@ -120,13 +121,11 @@ struct ModelSelectorView: View {
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(selectedCategory != nil ? selectedCategory!.color.opacity(0.18) : Color.gray.opacity(0.1))
.foregroundColor(selectedCategory != nil ? selectedCategory!.color : .secondary)
.cornerRadius(6)
.overlay(
RoundedRectangle(cornerRadius: 6)
.strokeBorder(selectedCategory != nil ? selectedCategory!.color.opacity(0.4) : Color.clear, lineWidth: 1)
.glassEffect(
selectedCategory != nil ? .regular.tint(selectedCategory!.color) : .regular,
in: .rect(cornerRadius: 6)
)
.foregroundColor(selectedCategory != nil ? selectedCategory!.color : .secondary)
}
.buttonStyle(.plain)
.help("Filter by category")
@@ -145,9 +144,8 @@ struct ModelSelectorView: View {
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(filterFavorites ? Color.yellow.opacity(0.25) : Color.gray.opacity(0.1))
.glassEffect(filterFavorites ? .regular.tint(.yellow) : .regular, in: .rect(cornerRadius: 6))
.foregroundColor(filterFavorites ? .yellow : .secondary)
.cornerRadius(6)
}
.buttonStyle(.plain)
.help("Show favorites only")
@@ -174,13 +172,13 @@ struct ModelSelectorView: View {
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(sortOrder != .default ? Color.blue.opacity(0.2) : Color.gray.opacity(0.1))
.glassEffect(sortOrder != .default ? .regular.tint(.blue) : .regular, in: .rect(cornerRadius: 6))
.foregroundColor(sortOrder != .default ? .blue : .secondary)
.cornerRadius(6)
}
.menuStyle(.borderlessButton)
.fixedSize()
}
}
.padding(.horizontal)
.padding(.bottom, 12)
@@ -299,9 +297,8 @@ struct FilterToggle: View {
.font(.caption)
.padding(.horizontal, 10)
.padding(.vertical, 6)
.background(isOn ? Color.blue.opacity(0.2) : Color.gray.opacity(0.1))
.glassEffect(isOn ? .regular.tint(.blue) : .regular, in: .rect(cornerRadius: 6))
.foregroundColor(isOn ? .blue : .secondary)
.cornerRadius(6)
}
.buttonStyle(.plain)
.fixedSize()
+35 -21
View File
@@ -226,22 +226,24 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
.padding(.bottom, 4)
// Icon toolbar Core | separator | Extras
HStack(spacing: 0) {
tabButton(0, icon: "gear", label: "General")
tabButton(1, icon: "folder.badge.gearshape", label: "MCP")
tabButton(2, icon: "paintbrush", label: "Appearance")
tabButton(3, icon: "slider.horizontal.3", label: "Advanced")
GlassEffectContainer(spacing: 8) {
HStack(spacing: 0) {
tabButton(0, icon: "gear", label: "General")
tabButton(1, icon: "folder.badge.gearshape", label: "MCP")
tabButton(2, icon: "paintbrush", label: "Appearance")
tabButton(3, icon: "slider.horizontal.3", label: "Advanced")
Divider().frame(height: 44).padding(.horizontal, 4)
Divider().frame(height: 44).padding(.horizontal, 4)
tabButton(6, icon: "command", label: "Shortcuts")
tabButton(7, icon: "brain", label: "Skills")
tabButton(4, icon: "arrow.triangle.2.circlepath", label: "Sync")
tabButton(5, icon: "envelope", label: "Email")
tabButton(8, icon: "doc.text", label: "Paperless", beta: true)
tabButton(9, icon: "icloud.and.arrow.up", label: "Backup")
tabButton(10, icon: "square.stack.3d.up", label: "Anytype")
tabButton(11, icon: "server.rack", label: "Jarvis")
tabButton(6, icon: "command", label: "Shortcuts")
tabButton(7, icon: "brain", label: "Skills")
tabButton(4, icon: "arrow.triangle.2.circlepath", label: "Sync")
tabButton(5, icon: "envelope", label: "Email")
tabButton(8, icon: "doc.text", label: "Paperless", beta: true)
tabButton(9, icon: "icloud.and.arrow.up", label: "Backup")
tabButton(10, icon: "square.stack.3d.up", label: "Anytype")
tabButton(11, icon: "server.rack", label: "Jarvis")
}
}
.padding(.horizontal, 16)
.padding(.bottom, 12)
@@ -645,8 +647,13 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
}
.padding(.horizontal, 10)
.padding(.vertical, 7)
.background(selectedMCPSubsection == section ? Color.blue.opacity(0.1) : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 8))
.background {
if selectedMCPSubsection == section {
Color.clear.glassEffect(.regular.tint(.blue), in: .rect(cornerRadius: 8))
} else {
Color.clear
}
}
}
.buttonStyle(.plain)
.foregroundStyle(selectedMCPSubsection == section ? .blue : .primary)
@@ -655,9 +662,11 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
@ViewBuilder
private var mcpTabWithSidebar: some View {
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 2) {
ForEach(MCPSubsection.visibleCases) { mcpSidebarRow($0) }
Spacer()
GlassEffectContainer(spacing: 4) {
VStack(alignment: .leading, spacing: 2) {
ForEach(MCPSubsection.visibleCases) { mcpSidebarRow($0) }
Spacer()
}
}
.padding(10)
.frame(width: 180)
@@ -3330,8 +3339,13 @@ It's better to admit "I need more information" or "I cannot do that" than to fak
.frame(minWidth: 55)
.padding(.vertical, 6)
.padding(.horizontal, 4)
.background(selectedTab == tag ? Color.blue.opacity(0.1) : Color.clear)
.clipShape(RoundedRectangle(cornerRadius: 8))
.background {
if selectedTab == tag {
Color.clear.glassEffect(.regular.tint(.blue), in: .rect(cornerRadius: 8))
} else {
Color.clear
}
}
}
.buttonStyle(.plain)
}