@momo-kits/native-kits 0.89.33-rc.5 → 0.110.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.size
|
|
|
15
15
|
import androidx.compose.foundation.layout.width
|
|
16
16
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
17
17
|
import androidx.compose.foundation.text.BasicTextField
|
|
18
|
+
import androidx.compose.foundation.text.KeyboardActions
|
|
18
19
|
import androidx.compose.foundation.text.KeyboardOptions
|
|
19
20
|
import androidx.compose.material3.CircularProgressIndicator
|
|
20
21
|
import androidx.compose.runtime.Composable
|
|
@@ -29,6 +30,7 @@ import androidx.compose.ui.focus.onFocusChanged
|
|
|
29
30
|
import androidx.compose.ui.graphics.Color
|
|
30
31
|
import androidx.compose.ui.text.TextStyle
|
|
31
32
|
import androidx.compose.ui.text.input.KeyboardType
|
|
33
|
+
import androidx.compose.ui.text.style.TextOverflow
|
|
32
34
|
import androidx.compose.ui.unit.dp
|
|
33
35
|
import androidx.compose.ui.unit.sp
|
|
34
36
|
import vn.momo.kits.const.AppTheme
|
|
@@ -44,6 +46,7 @@ fun RenderRightIconSearch(
|
|
|
44
46
|
icon: String,
|
|
45
47
|
color: Color,
|
|
46
48
|
onClick: () -> Unit,
|
|
49
|
+
iconModifier: Modifier = Modifier
|
|
47
50
|
) {
|
|
48
51
|
if (loading) {
|
|
49
52
|
Box(Modifier.padding(horizontal = Spacing.S)) {
|
|
@@ -64,7 +67,7 @@ fun RenderRightIconSearch(
|
|
|
64
67
|
source = icon,
|
|
65
68
|
color = color,
|
|
66
69
|
size = 24.dp,
|
|
67
|
-
modifier =
|
|
70
|
+
modifier = iconModifier.padding(start = Spacing.S, end = 0.dp).clickable(
|
|
68
71
|
onClick = onClick,
|
|
69
72
|
interactionSource = remember { MutableInteractionSource() },
|
|
70
73
|
indication = null
|
|
@@ -90,6 +93,11 @@ data class InputSearchProps(
|
|
|
90
93
|
val fontWeight: InputFontWeight = InputFontWeight.REGULAR,
|
|
91
94
|
val keyboardType: KeyboardType = KeyboardType.Text,
|
|
92
95
|
val backgroundColor: Color = Colors.black_01,
|
|
96
|
+
val clearCondition: (() -> Boolean)? = null,
|
|
97
|
+
val keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
|
98
|
+
val keyboardActions: KeyboardActions = KeyboardActions.Default,
|
|
99
|
+
val modifier: Modifier = Modifier,
|
|
100
|
+
val iconModifier: Modifier = Modifier,
|
|
93
101
|
)
|
|
94
102
|
|
|
95
103
|
@Composable
|
|
@@ -98,7 +106,6 @@ fun InputSearch(
|
|
|
98
106
|
text = remember { mutableStateOf("") },
|
|
99
107
|
iconColor = AppTheme.current.colors.text.default
|
|
100
108
|
),
|
|
101
|
-
|
|
102
109
|
) {
|
|
103
110
|
var isFocused by remember { mutableStateOf(false) }
|
|
104
111
|
var isBlurred = false
|
|
@@ -124,8 +131,9 @@ fun InputSearch(
|
|
|
124
131
|
lineHeight = 20.sp,
|
|
125
132
|
fontWeight = inputSearchProps.fontWeight.value
|
|
126
133
|
),
|
|
127
|
-
keyboardOptions =
|
|
128
|
-
|
|
134
|
+
keyboardOptions = inputSearchProps.keyboardOptions.copy(keyboardType = inputSearchProps.keyboardType),
|
|
135
|
+
keyboardActions = inputSearchProps.keyboardActions,
|
|
136
|
+
modifier = inputSearchProps.modifier.height(36.dp).onFocusChanged {
|
|
129
137
|
isFocused = it.isFocused
|
|
130
138
|
if (it.isFocused) {
|
|
131
139
|
inputSearchProps.onFocus()
|
|
@@ -137,7 +145,12 @@ fun InputSearch(
|
|
|
137
145
|
decorationBox = { innerTextField ->
|
|
138
146
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
139
147
|
Row(
|
|
140
|
-
modifier = Modifier.weight(
|
|
148
|
+
modifier = Modifier.weight(
|
|
149
|
+
inputSearchProps.showButtonText.ifTrue(
|
|
150
|
+
8f,
|
|
151
|
+
1f
|
|
152
|
+
)
|
|
153
|
+
)
|
|
141
154
|
.background(
|
|
142
155
|
inputSearchProps.backgroundColor,
|
|
143
156
|
RoundedCornerShape(Radius.XL)
|
|
@@ -152,7 +165,6 @@ fun InputSearch(
|
|
|
152
165
|
),
|
|
153
166
|
verticalAlignment = Alignment.CenterVertically
|
|
154
167
|
) {
|
|
155
|
-
//leading icon
|
|
156
168
|
Icon(
|
|
157
169
|
source = "navigation_search",
|
|
158
170
|
modifier = Modifier.padding(end = Spacing.XS),
|
|
@@ -168,12 +180,14 @@ fun InputSearch(
|
|
|
168
180
|
lineHeight = 24.sp,
|
|
169
181
|
fontWeight = inputSearchProps.fontWeight.value
|
|
170
182
|
),
|
|
171
|
-
|
|
183
|
+
maxLines = 1,
|
|
184
|
+
color = placeholderColor,
|
|
185
|
+
overflow = TextOverflow.Ellipsis
|
|
172
186
|
)
|
|
173
187
|
}
|
|
174
188
|
innerTextField()
|
|
175
189
|
}
|
|
176
|
-
if (isFocused && inputSearchProps.text.value.isNotEmpty()) {
|
|
190
|
+
if (inputSearchProps.clearCondition?.invoke() == true || (isFocused && inputSearchProps.text.value.isNotEmpty())) {
|
|
177
191
|
Row {
|
|
178
192
|
Spacer(Modifier.width(Spacing.XS))
|
|
179
193
|
Icon(
|
|
@@ -192,7 +206,8 @@ fun InputSearch(
|
|
|
192
206
|
inputSearchProps.loading,
|
|
193
207
|
inputSearchProps.icon,
|
|
194
208
|
iconTintColor,
|
|
195
|
-
inputSearchProps.onRightIconPressed
|
|
209
|
+
inputSearchProps.onRightIconPressed,
|
|
210
|
+
iconModifier = inputSearchProps.iconModifier
|
|
196
211
|
)
|
|
197
212
|
}
|
|
198
213
|
}
|
|
@@ -4,35 +4,98 @@ import SwiftUI
|
|
|
4
4
|
public struct Checkbox: View{
|
|
5
5
|
@Binding var checked: Bool
|
|
6
6
|
var disabled: Bool
|
|
7
|
-
var onChange: ((Bool)->Void)?
|
|
7
|
+
// var onChange: ((Bool)->Void)?
|
|
8
|
+
var onCheckedChange: ((Bool) -> Void)?
|
|
9
|
+
var indeterminate: Bool
|
|
10
|
+
var title: String
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
|
|
13
|
+
public init(_ checked: Binding<Bool>,disabled: Bool = false, onCheckedChange: ( (Bool) -> Void)?,
|
|
14
|
+
indeterminate: Bool = false,
|
|
15
|
+
title: String = ""){
|
|
10
16
|
self._checked = checked
|
|
11
|
-
self.onChange = onChange
|
|
12
17
|
self.disabled = disabled
|
|
18
|
+
self.onCheckedChange = onCheckedChange
|
|
19
|
+
self.indeterminate = indeterminate
|
|
20
|
+
self.title = title
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
func onCheck(){
|
|
16
24
|
checked.toggle()
|
|
17
|
-
|
|
25
|
+
onCheckedChange?(checked)
|
|
18
26
|
}
|
|
19
27
|
|
|
20
|
-
public var body: some View{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
public var body: some View {
|
|
29
|
+
HStack(spacing: Spacing.S) {
|
|
30
|
+
// Checkbox
|
|
31
|
+
ZStack {
|
|
32
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
33
|
+
.fill(backgroundColor)
|
|
34
|
+
.frame(width: 20, height: 20)
|
|
35
|
+
.overlay(
|
|
36
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
37
|
+
.stroke(borderColor, lineWidth: Spacing.XXS)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if checked {
|
|
41
|
+
Image(systemName: indeterminate ? "minus" : "checkmark")
|
|
25
42
|
.resizable()
|
|
26
|
-
.foregroundColor(
|
|
27
|
-
.frame(width:
|
|
43
|
+
.foregroundColor(Colors.black20)
|
|
44
|
+
.frame(width: 12, height: 12)
|
|
28
45
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
}
|
|
47
|
+
.onTapGesture {
|
|
48
|
+
if !disabled {
|
|
49
|
+
onCheckedChange?(checked)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Title
|
|
54
|
+
if !title.isEmpty {
|
|
55
|
+
Text(title)
|
|
56
|
+
.font(.description1)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private var borderColor: Color {
|
|
62
|
+
if disabled {
|
|
63
|
+
return Colors.black03
|
|
64
|
+
} else if checked {
|
|
65
|
+
return Colors.primary
|
|
66
|
+
} else {
|
|
67
|
+
return Colors.text
|
|
33
68
|
}
|
|
34
|
-
.opacity(disabled ? 0.4 : 1)
|
|
35
|
-
.disabled(disabled)
|
|
36
69
|
}
|
|
37
70
|
|
|
71
|
+
private var backgroundColor: Color {
|
|
72
|
+
if disabled && checked {
|
|
73
|
+
return Colors.pink08
|
|
74
|
+
} else if checked {
|
|
75
|
+
return Colors.primary
|
|
76
|
+
} else {
|
|
77
|
+
return Colors.black01
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// public var body: some View{
|
|
84
|
+
// return SwiftUI.Button(action: onCheck) {
|
|
85
|
+
// HStack {
|
|
86
|
+
// if (checked) {
|
|
87
|
+
// Image(systemName: "checkmark.square.fill")
|
|
88
|
+
// .resizable()
|
|
89
|
+
// .foregroundColor(disabled ? Colors.black07 : Colors.primary)
|
|
90
|
+
// .frame(width: 24, height: 24)
|
|
91
|
+
// }
|
|
92
|
+
// }.frame(width: 24, height: 24)
|
|
93
|
+
// .overlay(RoundedRectangle(cornerRadius: 4).stroke(Colors.black20,lineWidth: checked ? 0 : 1))
|
|
94
|
+
// .background(Colors.black01)
|
|
95
|
+
//
|
|
96
|
+
// }
|
|
97
|
+
// .opacity(disabled ? 0.4 : 1)
|
|
98
|
+
// .disabled(disabled)
|
|
99
|
+
// }
|
|
100
|
+
|
|
38
101
|
}
|
package/ios/Input/Input.swift
CHANGED
|
@@ -71,12 +71,22 @@ public struct Input: View {
|
|
|
71
71
|
var rightIcon: AnyView?
|
|
72
72
|
var onChange: ((String) -> Void)?
|
|
73
73
|
var onBlur: (() -> Void)?
|
|
74
|
+
var floatingIconURL: String?
|
|
75
|
+
var floatingIconColor: Color
|
|
76
|
+
var size: InputSize
|
|
77
|
+
var fontWeight: InputFontWeight
|
|
78
|
+
var disabled: Bool
|
|
79
|
+
var leadingIcon: String?
|
|
80
|
+
var leadingIconColor: Color
|
|
81
|
+
var loading: Bool
|
|
82
|
+
var required: Bool
|
|
83
|
+
var hintText: String?
|
|
74
84
|
|
|
75
85
|
func onCancel(){
|
|
76
86
|
self.value.removeAll()
|
|
77
87
|
}
|
|
78
88
|
|
|
79
|
-
public init(_ value: Binding<String>, placeholder: String = "", floatingValue: String = "", multilines: Bool = false, errorMessage: String = "", isSecure: Bool = false, keyboardType: UIKeyboardType = .default, floatingIcon: AnyView? = nil, rightIcon: AnyView? = nil, onChange: ((String)->Void)? = nil, onBlur: (()->Void)? = nil){
|
|
89
|
+
public init(_ value: Binding<String>, placeholder: String = "", floatingValue: String = "", multilines: Bool = false, errorMessage: String = "", isSecure: Bool = false, keyboardType: UIKeyboardType = .default, floatingIcon: AnyView? = nil, rightIcon: AnyView? = nil, onChange: ((String)->Void)? = nil, onBlur: (()->Void)? = nil, floatingIconURL: String? = nil, floatingIconColor: Color = Colors.pink03, size: InputSize = .small, fontWeight: InputFontWeight = .regular, disabled: Bool = false, leadingIcon: String? = nil, leadingIconColor: Color = Colors.pink03, loading: Bool = false, required: Bool = false, hintText: String? = nil){
|
|
80
90
|
self.placeholder = placeholder
|
|
81
91
|
self.floatingValue = floatingValue
|
|
82
92
|
self.errorMessage = errorMessage
|
|
@@ -86,6 +96,16 @@ public struct Input: View {
|
|
|
86
96
|
self.rightIcon = rightIcon
|
|
87
97
|
self.floatingIcon = floatingIcon
|
|
88
98
|
self.onChange = onChange
|
|
99
|
+
self.floatingIconURL = floatingIconURL
|
|
100
|
+
self.floatingIconColor = floatingIconColor
|
|
101
|
+
self.size = size
|
|
102
|
+
self.fontWeight = fontWeight
|
|
103
|
+
self.disabled = disabled
|
|
104
|
+
self.leadingIcon = leadingIcon
|
|
105
|
+
self.leadingIconColor = leadingIconColor
|
|
106
|
+
self.loading = loading
|
|
107
|
+
self.required = required
|
|
108
|
+
self.hintText = hintText
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
func onChangeText(value: String){
|
|
@@ -93,57 +113,137 @@ public struct Input: View {
|
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
public var body: some View {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.padding(.horizontal, 12)
|
|
104
|
-
.accentColor(Colors.pink03)
|
|
105
|
-
.keyboardType(keyboardType)
|
|
106
|
-
.valueChanged(value: value, onChange: onChangeText)
|
|
107
|
-
|
|
108
|
-
HStack{
|
|
109
|
-
SwiftButton(action: onCancel){
|
|
110
|
-
Image(systemName: "x.circle.fill")
|
|
111
|
-
.foregroundColor(Colors.text)
|
|
112
|
-
.frame(width: 16, height: 16)
|
|
113
|
-
.padding(.leading, -4)
|
|
114
|
-
.padding(.trailing, 8)
|
|
116
|
+
VStack(alignment: .leading, spacing: 4) {
|
|
117
|
+
ZStack(alignment: .topLeading) {
|
|
118
|
+
HStack(alignment: .center) {
|
|
119
|
+
if let leadingIcon = leadingIcon {
|
|
120
|
+
ImageLoader(url: URL(string: leadingIcon)!)
|
|
121
|
+
.frame(width: 20, height: 20)
|
|
122
|
+
.foregroundColor(leadingIconColor)
|
|
115
123
|
}
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
if !isSecure {
|
|
126
|
+
TextField("", text: $value, onEditingChanged: { isChange in
|
|
127
|
+
self.focused = isChange
|
|
128
|
+
})
|
|
129
|
+
.disabled(disabled)
|
|
130
|
+
.font(fontWeight == .bold ? .headline : .body)
|
|
131
|
+
} else {
|
|
132
|
+
DotTextField(value: $value)
|
|
133
|
+
.disabled(disabled)
|
|
134
|
+
}
|
|
118
135
|
|
|
136
|
+
if loading {
|
|
137
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
138
|
+
.frame(width: 20, height: 20)
|
|
139
|
+
} else if !value.isEmpty {
|
|
140
|
+
SwiftButton(action: onCancel){
|
|
141
|
+
Image(systemName: "x.circle.fill")
|
|
142
|
+
.foregroundColor(Colors.text)
|
|
143
|
+
.frame(width: 16, height: 16)
|
|
144
|
+
.padding(.leading, -4)
|
|
145
|
+
.padding(.trailing, 8)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
rightIcon
|
|
119
150
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.
|
|
128
|
-
.padding(.
|
|
129
|
-
.padding(.
|
|
151
|
+
.padding(.horizontal, 12)
|
|
152
|
+
.frame(height: size == .small ? 40 : 48)
|
|
153
|
+
.background(disabled ? Colors.black01 : Color.white)
|
|
154
|
+
.overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
|
|
155
|
+
|
|
156
|
+
if value.isEmpty {
|
|
157
|
+
Text(placeholder + (required ? " *" : ""))
|
|
158
|
+
.foregroundColor(Colors.black09)
|
|
159
|
+
.padding(.horizontal, 12)
|
|
160
|
+
.padding(.top, 12)
|
|
161
|
+
.allowsHitTesting(false)
|
|
130
162
|
}
|
|
163
|
+
|
|
164
|
+
if !floatingValue.isEmpty {
|
|
165
|
+
FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
|
|
166
|
+
AnyView(ImageLoader(url: URL(string: url)!)
|
|
167
|
+
.frame(width: 20, height: 20)
|
|
168
|
+
.background(floatingIconColor))
|
|
169
|
+
})
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if !errorMessage.isEmpty {
|
|
174
|
+
Text(errorMessage)
|
|
175
|
+
.foregroundColor(.red)
|
|
176
|
+
.font(.caption)
|
|
177
|
+
} else if let hint = hintText {
|
|
178
|
+
Text(hint)
|
|
179
|
+
.foregroundColor(Colors.black09)
|
|
180
|
+
.font(.caption)
|
|
131
181
|
}
|
|
132
182
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
struct ImageLoader: View {
|
|
187
|
+
@ObservedObject private var loader: ImageLoaderObject
|
|
188
|
+
|
|
189
|
+
init(url: URL) {
|
|
190
|
+
loader = ImageLoaderObject(url: url)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
var body: some View {
|
|
194
|
+
Group {
|
|
195
|
+
if let image = loader.image {
|
|
196
|
+
Image(uiImage: image)
|
|
197
|
+
.resizable()
|
|
198
|
+
.aspectRatio(contentMode: .fit)
|
|
199
|
+
} else {
|
|
200
|
+
// Use UIActivityIndicatorView for earlier iOS versions
|
|
201
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
144
202
|
}
|
|
145
|
-
|
|
146
|
-
}.padding(.bottom, 8)
|
|
203
|
+
}
|
|
147
204
|
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
struct ActivityIndicator: UIViewRepresentable {
|
|
209
|
+
@Binding var isAnimating: Bool
|
|
210
|
+
let style: UIActivityIndicatorView.Style
|
|
211
|
+
|
|
212
|
+
func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
|
|
213
|
+
return UIActivityIndicatorView(style: style)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
|
|
217
|
+
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
class ImageLoaderObject: ObservableObject {
|
|
222
|
+
@Published var image: UIImage?
|
|
223
|
+
private let url: URL
|
|
148
224
|
|
|
225
|
+
init(url: URL) {
|
|
226
|
+
self.url = url
|
|
227
|
+
loadImage()
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private func loadImage() {
|
|
231
|
+
URLSession.shared.dataTask(with: url) { data, _, _ in
|
|
232
|
+
if let data = data, let loadedImage = UIImage(data: data) {
|
|
233
|
+
DispatchQueue.main.async {
|
|
234
|
+
self.image = loadedImage
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}.resume()
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
public enum InputSize {
|
|
242
|
+
case small
|
|
243
|
+
case large
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
public enum InputFontWeight {
|
|
247
|
+
case regular
|
|
248
|
+
case bold
|
|
149
249
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//
|
|
2
|
+
// InputSearch.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by dung.pham2 on 3/12/24.
|
|
6
|
+
//
|
|
7
|
+
import SwiftUI
|
|
8
|
+
|
|
9
|
+
// MARK: - RenderRightIconSearch
|
|
10
|
+
|
|
11
|
+
struct RenderRightIconSearch: View {
|
|
12
|
+
let loading: Bool
|
|
13
|
+
let icon: String
|
|
14
|
+
let color: Color
|
|
15
|
+
let onClick: () -> Void
|
|
16
|
+
|
|
17
|
+
var body: some View {
|
|
18
|
+
HStack(spacing: 0) {
|
|
19
|
+
if loading {
|
|
20
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
21
|
+
.frame(width: 16, height: 16)
|
|
22
|
+
.padding(.horizontal, Spacing.S)
|
|
23
|
+
}
|
|
24
|
+
if !icon.isEmpty {
|
|
25
|
+
Divider()
|
|
26
|
+
.frame(width: 1)
|
|
27
|
+
.background(Colors.primary)
|
|
28
|
+
.padding(.leading, Spacing.S)
|
|
29
|
+
|
|
30
|
+
Image(systemName: icon)
|
|
31
|
+
.foregroundColor(color)
|
|
32
|
+
.frame(width: 24, height: 24)
|
|
33
|
+
.padding(.leading, Spacing.S)
|
|
34
|
+
.onTapGesture(perform: onClick)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// MARK: - InputSearch
|
|
41
|
+
|
|
42
|
+
public struct InputSearch: View {
|
|
43
|
+
@Binding var text: String
|
|
44
|
+
var buttonText: String = ""
|
|
45
|
+
var showButtonText: Bool = false
|
|
46
|
+
var showBorder: Bool = true
|
|
47
|
+
var placeholder: String = ""
|
|
48
|
+
var onChangeText: (String) -> Void = { _ in }
|
|
49
|
+
var onPressButtonText: () -> Void = {}
|
|
50
|
+
var error: String = ""
|
|
51
|
+
var disabled: Bool = false
|
|
52
|
+
var icon: String = ""
|
|
53
|
+
var iconColor: Color = Colors.text
|
|
54
|
+
var onRightIconPressed: () -> Void = {}
|
|
55
|
+
var onFocus: () -> Void = {}
|
|
56
|
+
var onBlur: () -> Void = {}
|
|
57
|
+
var loading: Bool = false
|
|
58
|
+
var fontWeight: Font.Weight = .regular
|
|
59
|
+
var keyboardType: UIKeyboardType = .default
|
|
60
|
+
|
|
61
|
+
@State private var isFocused = false
|
|
62
|
+
@State private var isBlurred = false
|
|
63
|
+
|
|
64
|
+
public init(text: Binding<String>,
|
|
65
|
+
buttonText: String = "",
|
|
66
|
+
showButtonText: Bool = false,
|
|
67
|
+
showBorder: Bool = true,
|
|
68
|
+
placeholder: String = "",
|
|
69
|
+
onChangeText: @escaping (String) -> Void = { _ in },
|
|
70
|
+
onPressButtonText: @escaping () -> Void = {},
|
|
71
|
+
error: String = "",
|
|
72
|
+
disabled: Bool = false,
|
|
73
|
+
icon: String = "",
|
|
74
|
+
iconColor: Color = Colors.text,
|
|
75
|
+
onRightIconPressed: @escaping () -> Void = {},
|
|
76
|
+
onFocus: @escaping () -> Void = {},
|
|
77
|
+
onBlur: @escaping () -> Void = {},
|
|
78
|
+
loading: Bool = false,
|
|
79
|
+
fontWeight: Font.Weight = .regular,
|
|
80
|
+
keyboardType: UIKeyboardType = .default) {
|
|
81
|
+
self._text = text
|
|
82
|
+
self.buttonText = buttonText
|
|
83
|
+
self.showButtonText = showButtonText
|
|
84
|
+
self.showBorder = showBorder
|
|
85
|
+
self.placeholder = placeholder
|
|
86
|
+
self.onChangeText = onChangeText
|
|
87
|
+
self.onPressButtonText = onPressButtonText
|
|
88
|
+
self.error = error
|
|
89
|
+
self.disabled = disabled
|
|
90
|
+
self.icon = icon
|
|
91
|
+
self.iconColor = iconColor
|
|
92
|
+
self.onRightIconPressed = onRightIconPressed
|
|
93
|
+
self.onFocus = onFocus
|
|
94
|
+
self.onBlur = onBlur
|
|
95
|
+
self.loading = loading
|
|
96
|
+
self.fontWeight = fontWeight
|
|
97
|
+
self.keyboardType = keyboardType
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
public var body: some View {
|
|
102
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
103
|
+
HStack(spacing: 0) {
|
|
104
|
+
HStack {
|
|
105
|
+
Image(systemName: "magnifyingglass")
|
|
106
|
+
.foregroundColor(Colors.black12)
|
|
107
|
+
.frame(width: 24, height: 24)
|
|
108
|
+
.padding(.trailing, Spacing.XS)
|
|
109
|
+
|
|
110
|
+
ZStack(alignment: .leading) {
|
|
111
|
+
if text.isEmpty {
|
|
112
|
+
Text(placeholder)
|
|
113
|
+
.foregroundColor(disabled ? Colors.black08 : Colors.black12)
|
|
114
|
+
.font(.system(size: 16, weight: fontWeight))
|
|
115
|
+
}
|
|
116
|
+
TextField("", text: $text, onEditingChanged: { editing in
|
|
117
|
+
isFocused = editing
|
|
118
|
+
if editing {
|
|
119
|
+
onFocus()
|
|
120
|
+
} else if isBlurred {
|
|
121
|
+
onBlur()
|
|
122
|
+
}
|
|
123
|
+
if editing && !isBlurred {
|
|
124
|
+
isBlurred = true
|
|
125
|
+
}
|
|
126
|
+
}, onCommit: {})
|
|
127
|
+
.disabled(disabled)
|
|
128
|
+
.foregroundColor(disabled ? Colors.black08 : Colors.black17)
|
|
129
|
+
.font(.system(size: 15, weight: fontWeight))
|
|
130
|
+
.keyboardType(keyboardType)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if isFocused && !text.isEmpty {
|
|
134
|
+
SwiftButton(action: { text = "" }) {
|
|
135
|
+
Image(systemName: "xmark.circle.fill")
|
|
136
|
+
.foregroundColor(Colors.black12)
|
|
137
|
+
.frame(width: 16, height: 16)
|
|
138
|
+
}
|
|
139
|
+
.padding(.leading, Spacing.S)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
RenderRightIconSearch(loading: loading, icon: icon, color: disabled ? Colors.black08 : iconColor, onClick: onRightIconPressed)
|
|
143
|
+
}
|
|
144
|
+
.padding(.horizontal, Spacing.M)
|
|
145
|
+
.padding(.vertical, Spacing.S)
|
|
146
|
+
.background(Colors.black01)
|
|
147
|
+
.cornerRadius(Radius.XL)
|
|
148
|
+
.overlay(
|
|
149
|
+
RoundedRectangle(cornerRadius: Radius.XL)
|
|
150
|
+
.stroke(getBorderColor(isFocused: isFocused, error: error, disabled: disabled), lineWidth: showBorder ? 1 : 0)
|
|
151
|
+
)
|
|
152
|
+
.frame(maxWidth: showButtonText ? .infinity : nil, alignment: .leading)
|
|
153
|
+
|
|
154
|
+
if showButtonText {
|
|
155
|
+
SwiftButton(action: onPressButtonText) {
|
|
156
|
+
Text(buttonText)
|
|
157
|
+
.foregroundColor(Colors.black17)
|
|
158
|
+
.font(.system(size: 14, weight: .medium))
|
|
159
|
+
}
|
|
160
|
+
.padding(.leading, Spacing.L)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// MARK: - Helper Functions
|
|
168
|
+
|
|
169
|
+
func getBorderColor(isFocused: Bool, error: String, disabled: Bool) -> Color {
|
|
170
|
+
if disabled {
|
|
171
|
+
return Colors.black08
|
|
172
|
+
} else if !error.isEmpty {
|
|
173
|
+
return Colors.red03
|
|
174
|
+
} else if isFocused {
|
|
175
|
+
return Colors.primary
|
|
176
|
+
} else {
|
|
177
|
+
return Colors.black04
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
//
|
|
2
|
+
// InputTextArea.swift
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by dung.pham2 on 2/12/24.
|
|
6
|
+
//
|
|
7
|
+
import SwiftUI
|
|
8
|
+
|
|
9
|
+
// Make these constants public
|
|
10
|
+
public let MAX_LENGTH = 300
|
|
11
|
+
public let DEFAULT_HEIGHT: CGFloat = 104
|
|
12
|
+
|
|
13
|
+
struct UITextViewWrapper: UIViewRepresentable {
|
|
14
|
+
@Binding var text: String
|
|
15
|
+
var onDone: (() -> Void)?
|
|
16
|
+
var onFocus: (() -> Void)?
|
|
17
|
+
var onBlur: (() -> Void)?
|
|
18
|
+
|
|
19
|
+
func makeUIView(context: Context) -> UITextView {
|
|
20
|
+
let textView = UITextView()
|
|
21
|
+
textView.delegate = context.coordinator
|
|
22
|
+
return textView
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func updateUIView(_ uiView: UITextView, context: Context) {
|
|
26
|
+
uiView.text = text
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func makeCoordinator() -> Coordinator {
|
|
30
|
+
Coordinator(self)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class Coordinator: NSObject, UITextViewDelegate {
|
|
34
|
+
var parent: UITextViewWrapper
|
|
35
|
+
|
|
36
|
+
init(_ parent: UITextViewWrapper) {
|
|
37
|
+
self.parent = parent
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func textViewDidChange(_ textView: UITextView) {
|
|
41
|
+
parent.text = textView.text
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
func textViewDidBeginEditing(_ textView: UITextView) {
|
|
45
|
+
parent.onFocus?()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func textViewDidEndEditing(_ textView: UITextView) {
|
|
49
|
+
parent.onBlur?()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
|
53
|
+
if text == "\n" {
|
|
54
|
+
parent.onDone?()
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Make the struct public
|
|
63
|
+
public struct InputTextArea: View {
|
|
64
|
+
@Binding var text: String
|
|
65
|
+
var maxLength: Int = MAX_LENGTH
|
|
66
|
+
var height: CGFloat = DEFAULT_HEIGHT
|
|
67
|
+
var floatingValue: String = ""
|
|
68
|
+
var floatingValueColor: Color = .gray
|
|
69
|
+
var floatingIcon: String = ""
|
|
70
|
+
var floatingIconColor: Color = .black
|
|
71
|
+
var placeholder: String = ""
|
|
72
|
+
var onChangeText: (String) -> Void = { _ in }
|
|
73
|
+
var error: String = ""
|
|
74
|
+
var errorSpacing: Bool = false
|
|
75
|
+
var disabled: Bool = false
|
|
76
|
+
var icon: String = ""
|
|
77
|
+
var iconColor: Color = .black
|
|
78
|
+
var onRightIconPressed: () -> Void = {}
|
|
79
|
+
var onFocus: () -> Void = {}
|
|
80
|
+
var onBlur: () -> Void = {}
|
|
81
|
+
var loading: Bool = false
|
|
82
|
+
var required: Bool = false
|
|
83
|
+
var fontWeight: Font.Weight = .regular
|
|
84
|
+
var keyboardType: UIKeyboardType = .default
|
|
85
|
+
|
|
86
|
+
@State private var isFocused = false
|
|
87
|
+
@State private var isBlurred = false
|
|
88
|
+
|
|
89
|
+
// Make the initializer public
|
|
90
|
+
public init(
|
|
91
|
+
text: Binding<String>,
|
|
92
|
+
maxLength: Int = MAX_LENGTH,
|
|
93
|
+
height: CGFloat = DEFAULT_HEIGHT,
|
|
94
|
+
floatingValue: String = "",
|
|
95
|
+
floatingValueColor: Color = .gray,
|
|
96
|
+
floatingIcon: String = "",
|
|
97
|
+
floatingIconColor: Color = .black,
|
|
98
|
+
placeholder: String = "",
|
|
99
|
+
onChangeText: @escaping (String) -> Void = { _ in },
|
|
100
|
+
error: String = "",
|
|
101
|
+
errorSpacing: Bool = false,
|
|
102
|
+
disabled: Bool = false,
|
|
103
|
+
icon: String = "",
|
|
104
|
+
iconColor: Color = Colors.pink03,
|
|
105
|
+
onRightIconPressed: @escaping () -> Void = {},
|
|
106
|
+
onFocus: @escaping () -> Void = {},
|
|
107
|
+
onBlur: @escaping () -> Void = {},
|
|
108
|
+
loading: Bool = false,
|
|
109
|
+
required: Bool = false,
|
|
110
|
+
fontWeight: Font.Weight = .regular,
|
|
111
|
+
keyboardType: UIKeyboardType = .default
|
|
112
|
+
) {
|
|
113
|
+
self._text = text
|
|
114
|
+
self.maxLength = maxLength
|
|
115
|
+
self.height = height
|
|
116
|
+
self.floatingValue = floatingValue
|
|
117
|
+
self.floatingValueColor = floatingValueColor
|
|
118
|
+
self.floatingIcon = floatingIcon
|
|
119
|
+
self.floatingIconColor = floatingIconColor
|
|
120
|
+
self.placeholder = placeholder
|
|
121
|
+
self.onChangeText = onChangeText
|
|
122
|
+
self.error = error
|
|
123
|
+
self.errorSpacing = errorSpacing
|
|
124
|
+
self.disabled = disabled
|
|
125
|
+
self.icon = icon
|
|
126
|
+
self.iconColor = iconColor
|
|
127
|
+
self.onRightIconPressed = onRightIconPressed
|
|
128
|
+
self.onFocus = onFocus
|
|
129
|
+
self.onBlur = onBlur
|
|
130
|
+
self.loading = loading
|
|
131
|
+
self.required = required
|
|
132
|
+
self.fontWeight = fontWeight
|
|
133
|
+
self.keyboardType = keyboardType
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
public var body: some View {
|
|
137
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
138
|
+
ZStack(alignment: .topLeading) {
|
|
139
|
+
if !floatingValue.isEmpty || !floatingIcon.isEmpty {
|
|
140
|
+
HStack(spacing: 4) {
|
|
141
|
+
Text(floatingValue)
|
|
142
|
+
.font(.caption)
|
|
143
|
+
.foregroundColor(disabled ? .gray : floatingValueColor)
|
|
144
|
+
|
|
145
|
+
if required {
|
|
146
|
+
Text("*")
|
|
147
|
+
.font(.caption)
|
|
148
|
+
.foregroundColor(.red)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if !floatingIcon.isEmpty {
|
|
152
|
+
Image(systemName: floatingIcon)
|
|
153
|
+
.font(.caption)
|
|
154
|
+
.foregroundColor(disabled ? .gray : floatingIconColor)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
.padding(.horizontal, 8)
|
|
158
|
+
.background(Colors.black01)
|
|
159
|
+
.offset(x: Spacing.S, y: -height / 10)
|
|
160
|
+
.zIndex(10)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
VStack {
|
|
164
|
+
ZStack(alignment: .topLeading) {
|
|
165
|
+
if text.isEmpty {
|
|
166
|
+
Text(placeholder)
|
|
167
|
+
.foregroundColor(disabled ? .gray : .gray)
|
|
168
|
+
.font(.system(size: 16, weight: fontWeight))
|
|
169
|
+
.padding(.horizontal, 16)
|
|
170
|
+
.padding(.vertical, 12)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
UITextViewWrapper(text: $text, onDone: {
|
|
174
|
+
// Handle done action if needed
|
|
175
|
+
},
|
|
176
|
+
onFocus: {
|
|
177
|
+
isFocused = true
|
|
178
|
+
onFocus()
|
|
179
|
+
}, onBlur: {
|
|
180
|
+
isFocused = false
|
|
181
|
+
isBlurred = true
|
|
182
|
+
onBlur()
|
|
183
|
+
})
|
|
184
|
+
.frame(height: height)
|
|
185
|
+
.padding(.horizontal, 12)
|
|
186
|
+
.padding(.vertical, 8)
|
|
187
|
+
.disabled(disabled) }
|
|
188
|
+
|
|
189
|
+
HStack {
|
|
190
|
+
Spacer()
|
|
191
|
+
Text("\(text.count)/\(maxLength)")
|
|
192
|
+
.font(.caption)
|
|
193
|
+
.foregroundColor(.gray)
|
|
194
|
+
}
|
|
195
|
+
.padding(.horizontal, 16)
|
|
196
|
+
.padding(.bottom, 12)
|
|
197
|
+
}
|
|
198
|
+
.background(Color.white)
|
|
199
|
+
.cornerRadius(8)
|
|
200
|
+
.overlay(
|
|
201
|
+
RoundedRectangle(cornerRadius: 8)
|
|
202
|
+
.stroke(borderColor, lineWidth: 1)
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if !error.isEmpty {
|
|
207
|
+
Text(error)
|
|
208
|
+
.font(.caption)
|
|
209
|
+
.foregroundColor(.red)
|
|
210
|
+
.padding(.top, errorSpacing ? 8 : 4)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) { _ in
|
|
214
|
+
if !isBlurred {
|
|
215
|
+
isFocused = true
|
|
216
|
+
onFocus()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
.onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillHideNotification)) { _ in
|
|
220
|
+
if isFocused {
|
|
221
|
+
isFocused = false
|
|
222
|
+
isBlurred = true
|
|
223
|
+
onBlur()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private var borderColor: Color {
|
|
229
|
+
if disabled {
|
|
230
|
+
return Colors.black03
|
|
231
|
+
} else if !error.isEmpty {
|
|
232
|
+
return .red
|
|
233
|
+
} else if isFocused {
|
|
234
|
+
return Colors.pink03
|
|
235
|
+
} else {
|
|
236
|
+
return Colors.border
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
|