@momo-kits/native-kits 0.89.33 → 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.
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Components.kt +13 -27
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +2 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +12 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +24 -9
- package/ios/Checkbox/Checkbox.swift +80 -17
- package/ios/Input/Input.swift +144 -44
- package/ios/Input/InputSearch.swift +182 -0
- package/ios/Input/InputTextArea.swift +242 -0
- package/package.json +1 -1
|
@@ -50,7 +50,6 @@ import vn.momo.kits.const.AppTheme
|
|
|
50
50
|
import vn.momo.kits.const.Colors
|
|
51
51
|
import vn.momo.kits.const.Spacing
|
|
52
52
|
import vn.momo.kits.const.Typography
|
|
53
|
-
import vn.momo.kits.modifier.shadow
|
|
54
53
|
import vn.momo.kits.platform.getStatusBarHeight
|
|
55
54
|
import vn.momo.kits.utils.bottomBorder
|
|
56
55
|
|
|
@@ -67,7 +66,6 @@ data class AnimatedHeader(
|
|
|
67
66
|
@Composable
|
|
68
67
|
fun HeaderBackground(
|
|
69
68
|
headerType: HeaderType = HeaderType.DEFAULT,
|
|
70
|
-
useShadow: Boolean? = true,
|
|
71
69
|
scrollState: Int
|
|
72
70
|
) {
|
|
73
71
|
val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
|
|
@@ -140,21 +138,12 @@ fun HeaderBackground(
|
|
|
140
138
|
}
|
|
141
139
|
}
|
|
142
140
|
}
|
|
141
|
+
|
|
143
142
|
else -> {
|
|
144
143
|
Box(modifier = Modifier.fillMaxWidth())
|
|
145
144
|
}
|
|
146
145
|
}
|
|
147
|
-
|
|
148
|
-
modifier = Modifier.fillMaxWidth().height(statusBarHeight + 52.dp)
|
|
149
|
-
.alpha(1 - opacityAni)
|
|
150
|
-
.background(Colors.black_01)
|
|
151
|
-
.then(
|
|
152
|
-
if (useShadow == true) Modifier.shadow() else Modifier.bottomBorder(
|
|
153
|
-
1.dp,
|
|
154
|
-
Colors.black_04
|
|
155
|
-
)
|
|
156
|
-
)
|
|
157
|
-
)
|
|
146
|
+
|
|
158
147
|
}
|
|
159
148
|
|
|
160
149
|
@Composable
|
|
@@ -163,10 +152,9 @@ fun Header(
|
|
|
163
152
|
titlePosition: TitlePosition,
|
|
164
153
|
title: String,
|
|
165
154
|
headerRight: @Composable (() -> Unit)? = null,
|
|
166
|
-
goBack: (() -> Unit)
|
|
155
|
+
goBack: (() -> Unit)? = null,
|
|
167
156
|
opacity: Float? = null,
|
|
168
157
|
animatedHeader: AnimatedHeader? = null,
|
|
169
|
-
useSearchInput: Boolean? = false,
|
|
170
158
|
scrollState: Int = 0,
|
|
171
159
|
inputSearchProps: InputSearchProps?
|
|
172
160
|
) {
|
|
@@ -206,7 +194,7 @@ fun Header(
|
|
|
206
194
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
207
195
|
) {
|
|
208
196
|
Box {
|
|
209
|
-
if (goBack
|
|
197
|
+
if (goBack != null) {
|
|
210
198
|
Box(
|
|
211
199
|
modifier = Modifier
|
|
212
200
|
.size(28.dp)
|
|
@@ -238,12 +226,11 @@ fun Header(
|
|
|
238
226
|
}
|
|
239
227
|
}
|
|
240
228
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
229
|
+
|
|
230
|
+
Box(Modifier.graphicsLayer {
|
|
231
|
+
alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
|
|
232
|
+
}) {
|
|
233
|
+
headerRight?.invoke()
|
|
247
234
|
}
|
|
248
235
|
}
|
|
249
236
|
|
|
@@ -254,13 +241,12 @@ fun Header(
|
|
|
254
241
|
.padding(horizontal = Spacing.M),
|
|
255
242
|
verticalAlignment = Alignment.CenterVertically,
|
|
256
243
|
) {
|
|
257
|
-
if (goBack
|
|
244
|
+
if (goBack != null && titlePosition == TitlePosition.LEFT) {
|
|
258
245
|
Spacer(Modifier.width(40.dp))
|
|
259
246
|
}
|
|
260
|
-
if (
|
|
247
|
+
if (inputSearchProps != null) {
|
|
261
248
|
InputSearch(inputSearchProps = inputSearchProps.copy(backgroundColor = backgroundSearch))
|
|
262
|
-
}
|
|
263
|
-
else{
|
|
249
|
+
} else{
|
|
264
250
|
Box(
|
|
265
251
|
Modifier.weight(1f).graphicsLayer {
|
|
266
252
|
alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
|
|
@@ -292,7 +278,7 @@ fun HeaderTitle(
|
|
|
292
278
|
modifier = Modifier.then(modifier).zIndex(1f),
|
|
293
279
|
text = title,
|
|
294
280
|
textAlign = textAlign,
|
|
295
|
-
style = Typography.
|
|
281
|
+
style = Typography.actionS,
|
|
296
282
|
color = color,
|
|
297
283
|
maxLines = 1
|
|
298
284
|
)
|
|
@@ -56,7 +56,7 @@ fun Screen(
|
|
|
56
56
|
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
57
57
|
title: String = "Stack",
|
|
58
58
|
titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
59
|
-
goBack: (() -> Unit) =
|
|
59
|
+
goBack: (() -> Unit)? = null,
|
|
60
60
|
scrollable: Boolean = true,
|
|
61
61
|
scrollState: ScrollState = rememberScrollState(),
|
|
62
62
|
onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
|
|
@@ -66,8 +66,6 @@ fun Screen(
|
|
|
66
66
|
fabProps: FabProps? = null,
|
|
67
67
|
animatedHeader: AnimatedHeader? = null,
|
|
68
68
|
layoutOffset: Dp = 56.dp,
|
|
69
|
-
useShadow: Boolean? = true,
|
|
70
|
-
useSearchInput: Boolean? = false,
|
|
71
69
|
inputSearchProps: InputSearchProps? = null,
|
|
72
70
|
content: @Composable () -> Unit,
|
|
73
71
|
) {
|
|
@@ -100,13 +98,12 @@ fun Screen(
|
|
|
100
98
|
HeaderBackground(headerType = headerType, scrollState = scrollState.value)
|
|
101
99
|
} else {
|
|
102
100
|
Box(
|
|
103
|
-
Modifier.zIndex(
|
|
101
|
+
Modifier.zIndex(2f)
|
|
104
102
|
.background(Color.White.copy(alpha = opacity))
|
|
105
103
|
.graphicsLayer { alpha = opacity }
|
|
106
104
|
) {
|
|
107
105
|
HeaderBackground(
|
|
108
106
|
headerType = headerType,
|
|
109
|
-
useShadow = useShadow,
|
|
110
107
|
scrollState = scrollState.value
|
|
111
108
|
)
|
|
112
109
|
}
|
|
@@ -120,7 +117,6 @@ fun Screen(
|
|
|
120
117
|
goBack = goBack,
|
|
121
118
|
opacity = opacity,
|
|
122
119
|
animatedHeader = animatedHeader,
|
|
123
|
-
useSearchInput = useSearchInput,
|
|
124
120
|
inputSearchProps = inputSearchProps,
|
|
125
121
|
scrollState = scrollState.value
|
|
126
122
|
)
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
package vn.momo.kits.components
|
|
2
2
|
|
|
3
|
+
import androidx.compose.foundation.layout.Box
|
|
3
4
|
import androidx.compose.foundation.layout.size
|
|
4
5
|
import androidx.compose.runtime.Composable
|
|
5
6
|
import androidx.compose.ui.Modifier
|
|
6
7
|
import androidx.compose.ui.graphics.Color
|
|
7
8
|
import androidx.compose.ui.graphics.ColorFilter
|
|
8
9
|
import androidx.compose.ui.layout.ContentScale
|
|
10
|
+
import androidx.compose.ui.semantics.contentDescription
|
|
11
|
+
import androidx.compose.ui.semantics.semantics
|
|
9
12
|
import androidx.compose.ui.unit.Dp
|
|
10
13
|
import androidx.compose.ui.unit.dp
|
|
14
|
+
import coil3.compose.AsyncImage
|
|
11
15
|
import vn.momo.kits.const.AppTheme
|
|
12
16
|
import vn.momo.kits.utils.Icons
|
|
13
17
|
|
|
@@ -23,14 +27,14 @@ fun Icon(
|
|
|
23
27
|
} else {
|
|
24
28
|
Icons[source] ?: ""
|
|
25
29
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
Box(modifier = modifier.size(size).semantics {contentDescription = "img|$icon"}) {
|
|
31
|
+
AsyncImage(
|
|
32
|
+
modifier = Modifier.matchParentSize(),
|
|
33
|
+
model = icon,
|
|
34
|
+
contentDescription = null,
|
|
31
35
|
contentScale = ContentScale.Fit,
|
|
32
|
-
colorFilter = color?.let { ColorFilter.tint(it) }
|
|
33
|
-
)
|
|
34
|
-
|
|
36
|
+
colorFilter = color?.let { ColorFilter.tint(it) },
|
|
37
|
+
)
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -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
|
+
|