Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

157 lines
4.9 KiB
Swift

import SwiftUI
import MacToolsPluginKit
struct PluginFilterBarView: View {
@Binding var searchText: String
@Binding var selectedFilter: PluginCategoryFilter
let countsByFilter: [PluginCategoryFilter: Int]
var searchPrompt: String = AppL10n.plugins("plugin.filter.searchPrompt", defaultValue: "搜索插件名称或简介")
var body: some View {
VStack(alignment: .leading, spacing: 10) {
searchField
if visibleFilters.count > 1 {
chipsRow
}
}
}
private var searchField: some View {
HStack(spacing: 8) {
Image(systemName: "magnifyingglass")
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
TextField(searchPrompt, text: $searchText)
.textFieldStyle(.plain)
.font(PluginSettingsTheme.Typography.rowTitle)
if !searchText.isEmpty {
Button {
searchText = ""
} label: {
Image(systemName: "xmark.circle.fill")
.font(PluginSettingsTheme.Typography.rowDescription)
.foregroundStyle(.secondary)
}
.buttonStyle(.plain)
.help(AppL10n.plugins("plugin.filter.clearSearch", defaultValue: "清除搜索"))
}
}
.padding(.horizontal, 10)
.padding(.vertical, 7)
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(Color(nsColor: .controlBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.strokeBorder(Color(nsColor: .separatorColor).opacity(0.4), lineWidth: 1)
)
}
private var chipsRow: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 6) {
ForEach(visibleFilters) { filter in
PluginFilterChip(
filter: filter,
count: countsByFilter[filter] ?? 0,
isSelected: selectedFilter == filter,
action: { selectedFilter = filter }
)
}
}
.padding(.vertical, 1)
}
}
private var visibleFilters: [PluginCategoryFilter] {
var filters: [PluginCategoryFilter] = [.all]
for category in PluginCategory.orderedCases {
let filter = PluginCategoryFilter.category(category)
if (countsByFilter[filter] ?? 0) > 0 || filter == selectedFilter {
filters.append(filter)
}
}
return filters
}
}
private struct PluginFilterChip: View {
let filter: PluginCategoryFilter
let count: Int
let isSelected: Bool
let action: () -> Void
@State private var isHovered = false
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
Image(systemName: filter.iconName)
.font(PluginSettingsTheme.Typography.secondaryLabel.weight(.semibold))
.foregroundStyle(iconColor)
Text(filter.displayName)
.font(PluginSettingsTheme.Typography.secondaryLabel.weight(.medium))
.foregroundStyle(textColor)
Text("\(count)")
.font(PluginSettingsTheme.Typography.statusBadge.weight(.semibold))
.foregroundStyle(countTextColor)
.padding(.horizontal, 6)
.padding(.vertical, 1)
.background(
Capsule(style: .continuous)
.fill(countBackground)
)
}
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background(
Capsule(style: .continuous)
.fill(chipBackground)
)
.contentShape(Capsule(style: .continuous))
}
.buttonStyle(.plain)
.onHover { isHovered = $0 }
.help(filter.displayName)
}
private var iconColor: Color {
isSelected ? .white : filter.iconTint
}
private var textColor: Color {
isSelected ? .white : .primary
}
private var countTextColor: Color {
isSelected ? .white : .secondary
}
private var countBackground: Color {
if isSelected {
return Color.white.opacity(0.22)
}
return Color(nsColor: .quaternaryLabelColor).opacity(0.7)
}
private var chipBackground: Color {
if isSelected {
return Color.accentColor
}
if isHovered {
return Color(nsColor: .controlBackgroundColor)
}
return Color(nsColor: .controlBackgroundColor).opacity(0.5)
}
}