Skip to content

Commit c8f2f27

Browse files
Restructured the code for DropdownTextField
1 parent 75a3d20 commit c8f2f27

File tree

2 files changed

+357
-79
lines changed

2 files changed

+357
-79
lines changed

Sources/DropdownTextField/DropdownTextField.swift

Lines changed: 73 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -14,87 +14,30 @@
1414
import SwiftUI
1515
import Combine
1616

17-
// MARK: - SearchableMenu
18-
public struct SearchableMenu: View {
19-
// MARK: - State
20-
@State private var isOptionSelected: Bool = false
21-
@State private var keyboardHeight: CGFloat = 0
22-
@FocusState private var isSearchFieldFocused: Bool
23-
17+
public struct DropdownTextField: View {
2418
@Binding var searchText: String
2519
@Binding var isDropdownVisible: Bool
2620

27-
// MARK: - Core Options
28-
public var options: [String]
29-
public var placeholder: String
30-
public var addNew: Bool
31-
public var onTap: () -> Void
32-
33-
// MARK: - Customization
34-
public var textColor: Color = .primary
35-
public var placeholderColor: Color = .gray
36-
public var accentColor: Color = .blue
37-
public var successColor: Color = .green
38-
public var destructiveColor: Color = .red
39-
public var borderColor: Color? = nil
40-
public var font: Font = .system(size: 16)
41-
public var height: CGFloat = 40
42-
public var cornerRadius: CGFloat = 8
43-
public var dropdownIcon: Image = Image(systemName: "chevron.down")
44-
public var noMatchText: String = "No match"
45-
public var addNewTextFormat: String = "Add %@"
46-
47-
// MARK: - Filtering Logic
48-
var filteredOptions: [String]{
49-
if searchText.isEmpty {
50-
return options
51-
} else {
52-
let allowedCharacters = CharacterSet.alphanumerics
53-
let normalizedSearchText = searchText.lowercased()
54-
.components(separatedBy: allowedCharacters.inverted)
55-
.joined()
56-
var results = [String]()
57-
let normalizedOptions = options.map {
58-
$0.lowercased()
59-
.components(separatedBy: allowedCharacters.inverted)
60-
.joined()
61-
}
62-
/// Exact matches
63-
results += options.enumerated().filter {
64-
normalizedOptions[$0.offset] == normalizedSearchText
65-
}.map { $0.element }
66-
67-
/// Prefix matches
68-
results += options.enumerated().filter {
69-
normalizedOptions[$0.offset].hasPrefix(normalizedSearchText) && !results.contains($0.element)
70-
}.map { $0.element }
71-
72-
/// Contains matches
73-
results += options.enumerated().filter {
74-
normalizedOptions[$0.offset].contains(normalizedSearchText) && !results.contains($0.element)
75-
}.map { $0.element }
76-
77-
return results
78-
}
79-
}
21+
@State private var isOptionSelected: Bool = false
22+
@State private var keyboardHeight: CGFloat = 0
23+
@FocusState private var isSearchFieldFocused: Bool
8024

81-
// MARK: - Computed Border Color
82-
var computedBorderColor: Color {
83-
if let borderColor = borderColor {
84-
return borderColor
85-
} else {
86-
if isOptionSelected && searchText.isEmpty {
87-
return textColor
88-
}
89-
if isOptionSelected {
90-
return successColor
91-
}
92-
if isDropdownVisible {
93-
return accentColor
94-
}
95-
return textColor
96-
}
97-
}
25+
private var options: [String]
26+
private var placeholder: String
27+
private var addNew: Bool
28+
private var textColor: Color = .primary
29+
private var placeholderColor: Color = .gray
30+
private var accentColor: Color = .blue
31+
private var successColor: Color = .green
32+
private var destructiveColor: Color = .red
33+
private var borderColor: Color? = nil
34+
private var font: Font = .system(size: 16)
35+
private var height: CGFloat = 40
36+
private var cornerRadius: CGFloat = 8
37+
private var dropdownIcon: Image = Image(systemName: "chevron.down")
38+
private var noMatchText: String = "No match"
39+
private var addNewTextFormat: String = "Add %@"
40+
private var onTap: () -> Void
9841

9942
// MARK: - Init
10043
public init(
@@ -137,7 +80,6 @@ public struct SearchableMenu: View {
13780
self.addNewTextFormat = addNewTextFormat
13881
}
13982

140-
// MARK: - Body
14183
public var body: some View {
14284
VStack(spacing: 2){
14385
HStack(spacing: 0){
@@ -275,11 +217,63 @@ public struct SearchableMenu: View {
275217
}
276218
}
277219

278-
// MARK: - Selection
279220
private func selectOption(_ option: String){
280221
searchText = option
281222
isOptionSelected = true
282223
isDropdownVisible = false
283224
isSearchFieldFocused = false
284225
}
285226
}
227+
228+
//MARK: - Private Helpers
229+
extension DropdownTextField {
230+
private var filteredOptions: [String]{
231+
if searchText.isEmpty {
232+
return options
233+
} else {
234+
let allowedCharacters = CharacterSet.alphanumerics
235+
let normalizedSearchText = searchText.lowercased()
236+
.components(separatedBy: allowedCharacters.inverted)
237+
.joined()
238+
var results = [String]()
239+
let normalizedOptions = options.map {
240+
$0.lowercased()
241+
.components(separatedBy: allowedCharacters.inverted)
242+
.joined()
243+
}
244+
/// Exact matches
245+
results += options.enumerated().filter {
246+
normalizedOptions[$0.offset] == normalizedSearchText
247+
}.map { $0.element }
248+
249+
/// Prefix matches
250+
results += options.enumerated().filter {
251+
normalizedOptions[$0.offset].hasPrefix(normalizedSearchText) && !results.contains($0.element)
252+
}.map { $0.element }
253+
254+
/// Contains matches
255+
results += options.enumerated().filter {
256+
normalizedOptions[$0.offset].contains(normalizedSearchText) && !results.contains($0.element)
257+
}.map { $0.element }
258+
259+
return results
260+
}
261+
}
262+
263+
private var computedBorderColor: Color {
264+
if let borderColor = borderColor {
265+
return borderColor
266+
} else {
267+
if isOptionSelected && searchText.isEmpty {
268+
return textColor
269+
}
270+
if isOptionSelected {
271+
return successColor
272+
}
273+
if isDropdownVisible {
274+
return accentColor
275+
}
276+
return textColor
277+
}
278+
}
279+
}

0 commit comments

Comments
 (0)