Present view options and search filters as popovers on macOS

Plain-Form sheets rendered as columns-style content floating inside
oversized fixed frames on macOS. Anchor these panels to their toolbar
buttons as popovers instead, sized to content; iOS/tvOS keep sheets.

- Search and PeerTube explore filters apply live (Cancel never
  reverted anything anyway) and dismiss on outside click
- Subscriptions/Manage Channels "Subscriptions Data" opens as a sheet
  from the popover's onDisappear via a pending flag, since presenting
  while the popover dismisses gets swallowed
- Media browser view options drop navigation chrome on macOS
This commit is contained in:
Arkadiusz Fal
2026-06-06 22:59:36 +02:00
parent dbe494f18a
commit 271aff86b5
13 changed files with 554 additions and 333 deletions

View File

@@ -15,46 +15,15 @@ struct PeerTubeFiltersSheet: View {
let onApply: () -> Void
var body: some View {
#if os(macOS)
// Popover content: filters apply live, click-outside dismisses.
filtersForm
.onChange(of: filters) { _, _ in onApply() }
.padding()
.frame(width: 300)
#else
NavigationStack {
Form {
// Language filter
if !languages.isEmpty {
Section {
Picker(String(localized: "peertube.filter.language"), selection: $filters.language) {
Text(String(localized: "common.any")).tag(nil as String?)
ForEach(languages, id: \.self) { lang in
Text(languageDisplayName(lang)).tag(lang as String?)
}
}
}
}
// Country filter
if !countries.isEmpty {
Section {
Picker(String(localized: "peertube.filter.country"), selection: $filters.country) {
Text(String(localized: "common.any")).tag(nil as String?)
ForEach(countries, id: \.self) { country in
Text(countryDisplayName(country)).tag(country as String?)
}
}
}
}
// Reset button
Section {
Button(role: .destructive) {
filters = PeerTubeDirectoryFilters()
} label: {
HStack {
Spacer()
Text(String(localized: "common.reset"))
Spacer()
}
}
.disabled(filters.isDefault)
}
}
filtersForm
#if os(tvOS)
.scrollClipDisabled()
.padding(.horizontal, 40)
@@ -63,9 +32,7 @@ struct PeerTubeFiltersSheet: View {
.onChange(of: filters.country) { _, _ in onApply() }
#else
.navigationTitle(String(localized: "peertube.explore.filters"))
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(String(localized: "common.cancel")) {
@@ -83,9 +50,50 @@ struct PeerTubeFiltersSheet: View {
}
#if os(iOS)
.presentationDetents([.medium])
#elseif os(macOS)
.frame(minWidth: 350, minHeight: 250)
#endif
#endif
}
private var filtersForm: some View {
Form {
// Language filter
if !languages.isEmpty {
Section {
Picker(String(localized: "peertube.filter.language"), selection: $filters.language) {
Text(String(localized: "common.any")).tag(nil as String?)
ForEach(languages, id: \.self) { lang in
Text(languageDisplayName(lang)).tag(lang as String?)
}
}
}
}
// Country filter
if !countries.isEmpty {
Section {
Picker(String(localized: "peertube.filter.country"), selection: $filters.country) {
Text(String(localized: "common.any")).tag(nil as String?)
ForEach(countries, id: \.self) { country in
Text(countryDisplayName(country)).tag(country as String?)
}
}
}
}
// Reset button
Section {
Button(role: .destructive) {
filters = PeerTubeDirectoryFilters()
} label: {
HStack {
Spacer()
Text(String(localized: "common.reset"))
Spacer()
}
}
.disabled(filters.isDefault)
}
}
}
private func languageDisplayName(_ code: String) -> String {

View File

@@ -93,17 +93,23 @@ struct PeerTubeInstancesExploreView: View {
.task {
await loadAllInstances()
}
#if !os(macOS)
.sheet(isPresented: $showFiltersSheet) {
PeerTubeFiltersSheet(
filters: $filters,
languages: availableLanguages,
countries: availableCountries,
onApply: {
// Reset display limit when filters change
displayLimit = pageSize
}
)
filtersSheetContent
}
#endif
}
private var filtersSheetContent: some View {
PeerTubeFiltersSheet(
filters: $filters,
languages: availableLanguages,
countries: availableCountries,
onApply: {
// Reset display limit when filters change
displayLimit = pageSize
}
)
}
// MARK: - Content
@@ -227,6 +233,11 @@ struct PeerTubeInstancesExploreView: View {
systemImage: filters.isDefault ? "line.3.horizontal.decrease.circle" : "line.3.horizontal.decrease.circle.fill"
)
}
#if os(macOS)
.popover(isPresented: $showFiltersSheet, arrowEdge: .bottom) {
filtersSheetContent
}
#endif
}
}