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:
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user