Fix macOS native styling for search toolbar pickers and view options

The content type segmented picker rendered as a Liquid-Glass pill instead
of a native macOS segmented control. Wrapping a `.segmented` Picker in
`.opacity`/`.disabled`/`.accessibilityHidden`/`.fixedSize` (used to keep
toolbar items mounted-but-invisible for stable layout) inserts a hosting
layer that makes the toolbar fall back to the pill rendering.

- SearchView: filters button and content type picker are now always
  visible on macOS, rendered bare so `.segmented` keeps native styling.
- SearchView: don't reset the content type to .video on submit on macOS,
  so a pre-selected type (e.g. Playlists) is preserved (iOS unchanged).
- InstanceBrowseView: same pill fix, rendering the tab / content type
  pickers bare via conditional `if` instead of opacity-masking.
- ViewOptionsSheet / MediaBrowserViewOptionsSheet: use `.formStyle(.grouped)`
  for native macOS form styling in the options popover.
This commit is contained in:
Arkadiusz Fal
2026-06-15 08:07:40 +02:00
parent 77720ccea1
commit 1157eef201
4 changed files with 29 additions and 40 deletions

View File

@@ -156,7 +156,7 @@ struct ViewOptionsSheet: View {
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
#endif #endif
#if os(macOS) #if os(macOS)
.padding() .formStyle(.grouped)
.frame(width: 300) .frame(width: 300)
#endif #endif
} }

View File

@@ -357,30 +357,26 @@ struct InstanceBrowseView: View {
} }
#if os(macOS) #if os(macOS)
if #available(macOS 26, *) { if #available(macOS 26, *) {
// Search filters button appears only while showing search results. // Render the items bare (conditional `if`, no opacity/disabled
// Items stay mounted (invisible/inert) otherwise so the toolbar layout is stable. // wrappers): wrapping a `.segmented` Picker inserts a hosting layer
ToolbarItem(placement: .navigation) { // that makes the toolbar fall back to a Liquid-Glass pill instead of
searchFiltersToolbarButton // the native segmented control. The search filters button shows only
.opacity(showsSearchFiltersInToolbar ? 1 : 0) // while displaying search results; the principal picker swaps between
.disabled(!showsSearchFiltersInToolbar) // the browse tab picker and the search content type picker.
.accessibilityHidden(!showsSearchFiltersInToolbar) if showsSearchFiltersInToolbar {
ToolbarItem(placement: .navigation) {
searchFiltersToolbarButton
}
} }
.sharedBackgroundVisibility(showsSearchFiltersInToolbar ? .automatic : .hidden) if isToolbarPickerVisible {
// Search content type picker while showing results, browse tab picker otherwise ToolbarItem(placement: .principal) {
ToolbarItem(placement: .principal) {
Group {
if showsSearchFiltersInToolbar { if showsSearchFiltersInToolbar {
searchContentTypePicker searchContentTypePicker
} else { } else {
tabPicker tabPicker
} }
} }
.fixedSize()
.opacity(isToolbarPickerVisible ? 1 : 0)
.disabled(!isToolbarPickerVisible)
.accessibilityHidden(!isToolbarPickerVisible)
} }
.sharedBackgroundVisibility(isToolbarPickerVisible ? .automatic : .hidden)
} else if showsSearchFiltersInToolbar { } else if showsSearchFiltersInToolbar {
ToolbarItem(placement: .navigation) { ToolbarItem(placement: .navigation) {
searchFiltersToolbarButton searchFiltersToolbarButton

View File

@@ -24,7 +24,7 @@ struct MediaBrowserViewOptionsSheet: View {
#if os(macOS) #if os(macOS)
// Popover content: no navigation chrome, click-outside dismisses. // Popover content: no navigation chrome, click-outside dismisses.
formContent formContent
.padding() .formStyle(.grouped)
.frame(width: 300) .frame(width: 300)
.onAppear { .onAppear {
// Reset sort order if current selection is not available for this source type // Reset sort order if current selection is not available for this source type

View File

@@ -291,29 +291,18 @@ struct SearchView: View {
} }
#if os(macOS) #if os(macOS)
if searchInstance?.supportsSearchFilters == true { if searchInstance?.supportsSearchFilters == true {
if #available(macOS 26, *) { // Filters button + content type picker are always visible on macOS.
// Items stay in the toolbar even before a search is submitted // The picker is rendered bare (no opacity/disabled/fixedSize wrappers):
// (invisible and inert) so the search field keeps a stable size. // wrapping a `.segmented` Picker in those modifiers inserts a hosting
ToolbarItem(placement: .navigation) { // layer that makes the toolbar fall back to a Liquid-Glass pill instead
searchFiltersToolbarButton // of the native segmented control.
.opacity(hasSearched ? 1 : 0) ToolbarItem(placement: .navigation) {
.disabled(!hasSearched) searchFiltersToolbarButton
.accessibilityHidden(!hasSearched) }
} ToolbarItem(placement: .principal) {
.sharedBackgroundVisibility(hasSearched ? .automatic : .hidden) if #available(macOS 26, *) {
ToolbarItem(placement: .principal) {
contentTypePicker contentTypePicker
.fixedSize() } else {
.opacity(hasSearched ? 1 : 0)
.disabled(!hasSearched)
.accessibilityHidden(!hasSearched)
}
.sharedBackgroundVisibility(hasSearched ? .automatic : .hidden)
} else if hasSearched {
ToolbarItem(placement: .navigation) {
searchFiltersToolbarButton
}
ToolbarItem(placement: .principal) {
contentTypePicker contentTypePicker
.fixedSize() .fixedSize()
} }
@@ -331,7 +320,11 @@ struct SearchView: View {
.searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder"))) .searchable(text: searchTextBinding, prompt: Text(String(localized: "search.placeholder")))
.onSubmit(of: .search) { .onSubmit(of: .search) {
searchViewModel?.cancelSuggestions() searchViewModel?.cancelSuggestions()
#if !os(macOS)
// On macOS the content type picker is always visible, so preserve the
// user's chosen type instead of resetting it to .video on each submit.
searchViewModel?.filters.type = .video searchViewModel?.filters.type = .video
#endif
if let filters = searchViewModel?.filters { if let filters = searchViewModel?.filters {
saveFilters(filters) saveFilters(filters)
} }