@@ -12,6 +12,9 @@ struct DiscoverView: View {
1212 @State private var searchText = " "
1313 @State private var selectedCategory : String ?
1414
15+ /// Max chips shown inline; the rest go into the "More" menu.
16+ private static let maxVisible = 8
17+
1518 private var filtered : [ MarketplacePlugin ] {
1619 plugins. filter { plugin in
1720 let matchesSearch = searchText. isEmpty
@@ -23,29 +26,56 @@ struct DiscoverView: View {
2326 }
2427 }
2528
26- private var categories : [ String ] {
27- Array ( Set ( plugins. compactMap { $0. category } ) ) . sorted ( )
29+ /// All categories sorted by frequency descending, then alphabetically.
30+ private var rankedCategories : [ String ] {
31+ var freq : [ String : Int ] = [ : ]
32+ for p in plugins { if let c = p. category { freq [ c, default: 0 ] += 1 } }
33+ return freq. keys. sorted { freq [ $0] ! != freq [ $1] ! ? freq [ $0] ! > freq [ $1] ! : $0 < $1 }
2834 }
2935
36+ private var visibleCategories : [ String ] { Array ( rankedCategories. prefix ( Self . maxVisible) ) }
37+ private var overflowCategories : [ String ] { Array ( rankedCategories. dropFirst ( Self . maxVisible) ) }
38+
3039 var body : some View {
3140 VStack ( spacing: 0 ) {
32- if !categories. isEmpty {
33- ScrollView ( . horizontal, showsIndicators: false ) {
34- HStack ( spacing: 8 ) {
35- CategoryChip ( label: " All " , isSelected: selectedCategory == nil ) {
36- selectedCategory = nil
41+ if !rankedCategories. isEmpty {
42+ FlowLayout ( hSpacing: 8 , vSpacing: 6 ) {
43+ CategoryChip ( label: " All " , isSelected: selectedCategory == nil ) {
44+ selectedCategory = nil
45+ }
46+ ForEach ( visibleCategories, id: \. self) { cat in
47+ CategoryChip ( label: cat. capitalized, isSelected: selectedCategory == cat) {
48+ selectedCategory = cat
3749 }
38- ForEach ( categories, id: \. self) { cat in
39- CategoryChip ( label: cat. capitalized, isSelected: selectedCategory == cat) {
40- selectedCategory = cat
50+ }
51+ if !overflowCategories. isEmpty {
52+ let isMoreSelected = selectedCategory. map { overflowCategories. contains ( $0) } ?? false
53+ Menu {
54+ Button ( " All Categories " ) { selectedCategory = nil }
55+ Divider ( )
56+ ForEach ( overflowCategories, id: \. self) { cat in
57+ Button ( cat. capitalized) { selectedCategory = cat }
58+ }
59+ } label: {
60+ HStack ( spacing: 4 ) {
61+ Text ( isMoreSelected ? selectedCategory!. capitalized : " More " )
62+ . font ( . caption)
63+ . lineLimit ( 1 )
64+ Image ( systemName: " chevron.down " )
65+ . font ( . caption2)
4166 }
67+ . padding ( . horizontal, 10 )
68+ . padding ( . vertical, 5 )
69+ . background ( isMoreSelected ? Color . accentColor : Color . secondary. opacity ( 0.1 ) , in: Capsule ( ) )
70+ . foregroundStyle ( isMoreSelected ? Color . white : Color . primary)
4271 }
72+ . menuStyle ( . borderlessButton)
73+ . menuIndicator ( . hidden)
74+ . fixedSize ( )
4375 }
44- . padding ( . horizontal, 16 )
45- . fixedSize ( horizontal: false , vertical: true )
4676 }
47- . frame ( maxWidth : . infinity )
48- . padding ( . vertical, 8 )
77+ . padding ( . horizontal , 16 )
78+ . padding ( . vertical, 10 )
4979 Divider ( )
5080 }
5181
@@ -154,6 +184,8 @@ private struct CategoryChip: View {
154184 Button ( action: action) {
155185 Text ( label)
156186 . font ( . caption)
187+ . lineLimit ( 1 )
188+ . fixedSize ( )
157189 . padding ( . horizontal, 10 )
158190 . padding ( . vertical, 5 )
159191 . background (
0 commit comments