forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSearchBar.swift
More file actions
100 lines (94 loc) · 4 KB
/
SearchBar.swift
File metadata and controls
100 lines (94 loc) · 4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import SharedUIComponents
import SwiftUI
/// Reusable search control with a toggleable magnifying glass button that expands
/// into a styled search field with clear button, focus handling, and auto-hide
/// when focus is lost and the text is empty.
///
/// Usage:
/// SearchBar(isVisible: $isSearchBarVisible, text: $searchText)
struct SearchBar: View {
@Binding var isVisible: Bool
@Binding var text: String
@FocusState private var isFocused: Bool
var placeholder: String = "Search..."
var accessibilityIdentifier: String = "searchTextField"
var body: some View {
Group {
if isVisible {
HStack(spacing: 5) {
Image(systemName: "magnifyingglass")
.foregroundColor(.secondary)
.onTapGesture { withAnimation(.easeInOut) {
isVisible = false
} }
TextField(placeholder, text: $text)
.accessibilityIdentifier(accessibilityIdentifier)
.textFieldStyle(PlainTextFieldStyle())
.focused($isFocused)
if !text.isEmpty {
Button(action: { text = "" }) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
}
.buttonStyle(PlainButtonStyle())
.help("Clear search")
}
}
.padding(.leading, 7)
.padding(.trailing, 3)
.padding(.vertical, 3)
.background(
RoundedRectangle(cornerRadius: 5)
.fill(Color(.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(
isFocused
? Color(red: 0, green: 0.48, blue: 1).opacity(0.5)
: Color.gray.opacity(0.4),
lineWidth: isFocused ? 3 : 1
)
)
.cornerRadius(5)
.frame(width: 212, height: 20, alignment: .leading)
.shadow(color: Color(red: 0, green: 0.48, blue: 1).opacity(0.5), radius: isFocused ? 1.25 : 0, x: 0, y: 0)
.shadow(color: .black.opacity(0.05), radius: 0, x: 0, y: 0)
.shadow(color: .black.opacity(0.3), radius: 1.25, x: 0, y: 0.5)
.padding(2)
// Removed the move(edge: .trailing) to prevent overlap; keep a clean fade instead
.transition(.asymmetric(insertion: .opacity, removal: .opacity))
.onChange(of: isFocused) { focused in
if !focused && text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
withAnimation(.easeInOut) {
isVisible = false
}
}
}
.onChange(of: isVisible) { newValue in
if newValue {
// Delay to ensure the field is mounted before requesting focus.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
isFocused = true
}
}
}
} else {
Button(action: {
withAnimation(.easeInOut) {
isVisible = true
}
}) {
Image(systemName: "magnifyingglass")
.padding(.trailing, 2)
}
.buttonStyle(HoverButtonStyle())
.frame(height: 24)
.transition(.opacity)
.help("Show search")
}
}
.contentShape(Rectangle())
.onTapGesture { if isFocused { isFocused = false } }
}
}