Move channel tab picker to toolbar on macOS

Replace the centered avatar/name toolbar title with the
About/Videos/Shorts/Streams/Playlists segmented picker. The channel
avatar and name move to the leading edge next to the view options
button, in their own glass capsule (sharedBackgroundVisibility(.hidden)
+ glassEffect on macOS 26). Add spacing between the channel banner and
the content below now that the inline picker is gone.
This commit is contained in:
Arkadiusz Fal
2026-07-11 22:26:48 +02:00
parent cc50c88d75
commit aa81fdac7d

View File

@@ -307,6 +307,14 @@ struct ChannelView: View {
.id("channelTop") .id("channelTop")
// Content based on instance type // Content based on instance type
// On macOS the content type picker lives in the toolbar instead.
#if os(macOS)
// Breathing room between the banner and the content below.
Color.clear.frame(height: 16)
if !supportsChannelTabs {
channelDescription(channel)
}
#else
if supportsChannelTabs { if supportsChannelTabs {
// Pill-style content type switcher // Pill-style content type switcher
contentTypePicker contentTypePicker
@@ -316,6 +324,7 @@ struct ChannelView: View {
// Non-tab instances: show description // Non-tab instances: show description
channelDescription(channel) channelDescription(channel)
} }
#endif
} }
// Tab content or search results // Tab content or search results
@@ -366,8 +375,11 @@ struct ChannelView: View {
)) ))
.toolbar { .toolbar {
#if os(macOS) #if os(macOS)
ToolbarItem(placement: .principal) { if supportsChannelTabs && !(isSearchActive && hasSearched) {
collapsedToolbarTitle(name: channel.name, thumbnailURL: channel.thumbnailURL) ToolbarItem(placement: .principal) {
contentTypePicker
.fixedSize()
}
} }
#else #else
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
@@ -410,6 +422,22 @@ struct ChannelView: View {
#endif #endif
} }
// macOS: channel avatar + name sit on the leading edge, next to view
// options. Excluded from the shared glass background so the view
// options button keeps its own capsule and the title renders plain.
#if os(macOS)
if #available(macOS 26, *) {
ToolbarItem(placement: .navigation) {
collapsedToolbarTitle(name: channel.name, thumbnailURL: channel.thumbnailURL)
}
.sharedBackgroundVisibility(.hidden)
} else {
ToolbarItem(placement: .navigation) {
collapsedToolbarTitle(name: channel.name, thumbnailURL: channel.thumbnailURL)
}
}
#endif
// iOS keeps a fixed gap between the trailing view options and channel // iOS keeps a fixed gap between the trailing view options and channel
// menu buttons; on macOS view options moves to the leading edge, so no // menu buttons; on macOS view options moves to the leading edge, so no
// gap is needed here. // gap is needed here.
@@ -471,11 +499,16 @@ struct ChannelView: View {
.id("channelTop") .id("channelTop")
// Show tab picker during loading (doesn't depend on channel) // Show tab picker during loading (doesn't depend on channel)
// On macOS the content type picker lives in the toolbar instead.
#if os(macOS)
Color.clear.frame(height: 16)
#else
if supportsChannelTabs { if supportsChannelTabs {
contentTypePicker contentTypePicker
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical, 8) .padding(.vertical, 8)
} }
#endif
// Centered spinner for content area below tabs // Centered spinner for content area below tabs
ProgressView() ProgressView()
@@ -491,8 +524,11 @@ struct ChannelView: View {
.ignoresSafeArea(edges: .top) .ignoresSafeArea(edges: .top)
.toolbar { .toolbar {
#if os(macOS) #if os(macOS)
ToolbarItem(placement: .principal) { if supportsChannelTabs {
collapsedToolbarTitle(name: cached.name, thumbnailURL: cached.thumbnailURL) ToolbarItem(placement: .principal) {
contentTypePicker
.fixedSize()
}
} }
#else #else
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
@@ -535,6 +571,22 @@ struct ChannelView: View {
#endif #endif
} }
// macOS: channel avatar + name sit on the leading edge, next to view
// options. Excluded from the shared glass background so the view
// options button keeps its own capsule and the title renders plain.
#if os(macOS)
if #available(macOS 26, *) {
ToolbarItem(placement: .navigation) {
collapsedToolbarTitle(name: cached.name, thumbnailURL: cached.thumbnailURL)
}
.sharedBackgroundVisibility(.hidden)
} else {
ToolbarItem(placement: .navigation) {
collapsedToolbarTitle(name: cached.name, thumbnailURL: cached.thumbnailURL)
}
}
#endif
// iOS keeps a fixed gap between the trailing view options and channel // iOS keeps a fixed gap between the trailing view options and channel
// menu buttons; on macOS view options moves to the leading edge, so no // menu buttons; on macOS view options moves to the leading edge, so no
// gap is needed here. // gap is needed here.
@@ -865,8 +917,9 @@ struct ChannelView: View {
// MARK: - Header // MARK: - Header
#if os(macOS) #if os(macOS)
@ViewBuilder
private func collapsedToolbarTitle(name: String, thumbnailURL: URL?) -> some View { private func collapsedToolbarTitle(name: String, thumbnailURL: URL?) -> some View {
HStack(spacing: 8) { let label = HStack(spacing: 8) {
LazyImage(url: thumbnailURL) { state in LazyImage(url: thumbnailURL) { state in
if let image = state.image { if let image = state.image {
image image
@@ -885,6 +938,16 @@ struct ChannelView: View {
.lineLimit(1) .lineLimit(1)
} }
.padding(.horizontal, 10) .padding(.horizontal, 10)
// Own glass capsule; the toolbar item opts out of the shared background
// so this doesn't merge with the view options button.
if #available(macOS 26, *) {
label
.padding(.vertical, 5)
.glassEffect()
} else {
label
}
} }
#endif #endif