@momo-kits/native-kits 0.89.33 → 0.110.1-beta.5
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/build.gradle.kts +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Components.kt +35 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +53 -40
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +12 -8
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +29 -10
- package/ios/Checkbox/Checkbox.swift +62 -19
- package/ios/Icon/Icon.swift +53 -0
- package/ios/Input/Input.swift +104 -46
- package/ios/Input/InputSearch.swift +182 -0
- package/ios/Input/InputTextArea.swift +242 -0
- package/ios/Typography/Typography.swift +47 -4
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
|
@@ -3,6 +3,7 @@ plugins {
|
|
|
3
3
|
alias(libs.plugins.jetbrains.kotlin.multiplatform)
|
|
4
4
|
alias(libs.plugins.compose)
|
|
5
5
|
alias(libs.plugins.kotlin.compose.compiler)
|
|
6
|
+
kotlin("plugin.serialization")
|
|
6
7
|
id("maven-publish")
|
|
7
8
|
}
|
|
8
9
|
|
|
@@ -39,6 +40,7 @@ kotlin {
|
|
|
39
40
|
implementation(libs.coil.multiplatform.compose)
|
|
40
41
|
implementation(libs.coil.multiplatform.core)
|
|
41
42
|
implementation(libs.coil.multiplatform.network.ktor)
|
|
43
|
+
implementation(libs.jetbrains.serialization.json)
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
val androidMain by getting {
|
|
@@ -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()
|
|
@@ -75,11 +73,19 @@ fun HeaderBackground(
|
|
|
75
73
|
targetValue = (1 - (scrollState / 200f)).coerceIn(0f, 1f),
|
|
76
74
|
)
|
|
77
75
|
|
|
76
|
+
val backgroundColor = if (scrollState == 0) {
|
|
77
|
+
Color.Transparent
|
|
78
|
+
} else {
|
|
79
|
+
AppTheme.current.colors.background.surface
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
78
83
|
when (headerType) {
|
|
79
84
|
HeaderType.DEFAULT -> {
|
|
80
85
|
val height = statusBarHeight + 52.dp
|
|
81
86
|
Box(
|
|
82
87
|
modifier = Modifier.fillMaxWidth().height(height)
|
|
88
|
+
.background(backgroundColor)
|
|
83
89
|
.bottomBorder(
|
|
84
90
|
strokeWidth = 1.dp,
|
|
85
91
|
color = AppTheme.current.colors.border.default
|
|
@@ -103,6 +109,7 @@ fun HeaderBackground(
|
|
|
103
109
|
)
|
|
104
110
|
if (AppTheme.current.assets.headerBackground != null) {
|
|
105
111
|
Image(
|
|
112
|
+
loading = false,
|
|
106
113
|
source = AppTheme.current.assets.headerBackground!!,
|
|
107
114
|
modifier = Modifier.fillMaxWidth().height(height),
|
|
108
115
|
options = Options(
|
|
@@ -115,6 +122,7 @@ fun HeaderBackground(
|
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
HeaderType.EXTENDED -> {
|
|
125
|
+
|
|
118
126
|
Box(
|
|
119
127
|
modifier = Modifier.fillMaxWidth().height(154.dp)
|
|
120
128
|
.alpha(opacityAni)
|
|
@@ -130,31 +138,28 @@ fun HeaderBackground(
|
|
|
130
138
|
)
|
|
131
139
|
) {
|
|
132
140
|
if (AppTheme.current.assets.headerBackground != null) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
Box(
|
|
142
|
+
modifier = Modifier.fillMaxWidth()
|
|
143
|
+
.background(backgroundColor)
|
|
144
|
+
) {
|
|
145
|
+
Image(
|
|
146
|
+
loading = false,
|
|
147
|
+
source = AppTheme.current.assets.headerBackground!!,
|
|
148
|
+
modifier = Modifier.fillMaxWidth().height(154.dp),
|
|
149
|
+
options = Options(
|
|
150
|
+
contentScale = ContentScale.FillWidth
|
|
151
|
+
)
|
|
138
152
|
)
|
|
139
|
-
|
|
153
|
+
}
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
}
|
|
157
|
+
|
|
143
158
|
else -> {
|
|
144
159
|
Box(modifier = Modifier.fillMaxWidth())
|
|
145
160
|
}
|
|
146
161
|
}
|
|
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
|
-
)
|
|
162
|
+
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
@Composable
|
|
@@ -163,10 +168,9 @@ fun Header(
|
|
|
163
168
|
titlePosition: TitlePosition,
|
|
164
169
|
title: String,
|
|
165
170
|
headerRight: @Composable (() -> Unit)? = null,
|
|
166
|
-
goBack: (() -> Unit)
|
|
171
|
+
goBack: (() -> Unit)? = null,
|
|
167
172
|
opacity: Float? = null,
|
|
168
173
|
animatedHeader: AnimatedHeader? = null,
|
|
169
|
-
useSearchInput: Boolean? = false,
|
|
170
174
|
scrollState: Int = 0,
|
|
171
175
|
inputSearchProps: InputSearchProps?
|
|
172
176
|
) {
|
|
@@ -206,7 +210,7 @@ fun Header(
|
|
|
206
210
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
207
211
|
) {
|
|
208
212
|
Box {
|
|
209
|
-
if (goBack
|
|
213
|
+
if (goBack != null) {
|
|
210
214
|
Box(
|
|
211
215
|
modifier = Modifier
|
|
212
216
|
.size(28.dp)
|
|
@@ -238,12 +242,11 @@ fun Header(
|
|
|
238
242
|
}
|
|
239
243
|
}
|
|
240
244
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
245
|
+
|
|
246
|
+
Box(Modifier.graphicsLayer {
|
|
247
|
+
alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
|
|
248
|
+
}) {
|
|
249
|
+
headerRight?.invoke()
|
|
247
250
|
}
|
|
248
251
|
}
|
|
249
252
|
|
|
@@ -254,13 +257,12 @@ fun Header(
|
|
|
254
257
|
.padding(horizontal = Spacing.M),
|
|
255
258
|
verticalAlignment = Alignment.CenterVertically,
|
|
256
259
|
) {
|
|
257
|
-
if (goBack
|
|
260
|
+
if (goBack != null && titlePosition == TitlePosition.LEFT) {
|
|
258
261
|
Spacer(Modifier.width(40.dp))
|
|
259
262
|
}
|
|
260
|
-
if (
|
|
263
|
+
if (inputSearchProps != null) {
|
|
261
264
|
InputSearch(inputSearchProps = inputSearchProps.copy(backgroundColor = backgroundSearch))
|
|
262
|
-
}
|
|
263
|
-
else{
|
|
265
|
+
} else {
|
|
264
266
|
Box(
|
|
265
267
|
Modifier.weight(1f).graphicsLayer {
|
|
266
268
|
alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
|
|
@@ -292,7 +294,7 @@ fun HeaderTitle(
|
|
|
292
294
|
modifier = Modifier.then(modifier).zIndex(1f),
|
|
293
295
|
text = title,
|
|
294
296
|
textAlign = textAlign,
|
|
295
|
-
style = Typography.
|
|
297
|
+
style = Typography.actionS,
|
|
296
298
|
color = color,
|
|
297
299
|
maxLines = 1
|
|
298
300
|
)
|
|
@@ -1,64 +1,77 @@
|
|
|
1
1
|
package vn.momo.kits.application
|
|
2
2
|
|
|
3
|
-
import androidx.compose.foundation.layout.WindowInsets
|
|
4
|
-
import androidx.compose.foundation.layout.asPaddingValues
|
|
5
|
-
import androidx.compose.foundation.layout.systemBars
|
|
6
3
|
import androidx.compose.runtime.Composable
|
|
7
4
|
import androidx.compose.runtime.CompositionLocalProvider
|
|
5
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
6
|
+
import androidx.compose.runtime.getValue
|
|
7
|
+
import androidx.compose.runtime.mutableStateOf
|
|
8
|
+
import androidx.compose.runtime.remember
|
|
9
|
+
import androidx.compose.runtime.rememberCoroutineScope
|
|
10
|
+
import androidx.compose.runtime.setValue
|
|
8
11
|
import androidx.compose.runtime.staticCompositionLocalOf
|
|
12
|
+
import androidx.compose.ui.unit.Dp
|
|
13
|
+
import kotlinx.coroutines.launch
|
|
14
|
+
import kotlinx.serialization.json.Json
|
|
15
|
+
import kotlinx.serialization.json.jsonObject
|
|
16
|
+
import kotlinx.serialization.json.jsonPrimitive
|
|
9
17
|
import vn.momo.kits.const.AppStatusBar
|
|
10
18
|
import vn.momo.kits.const.AppTheme
|
|
11
19
|
import vn.momo.kits.const.Theme
|
|
20
|
+
import vn.momo.kits.const.ThemeAssets
|
|
12
21
|
import vn.momo.kits.const.defaultTheme
|
|
13
22
|
import vn.momo.kits.platform.disableOverscroll
|
|
14
23
|
import vn.momo.kits.platform.getStatusBarHeight
|
|
15
24
|
|
|
16
|
-
val
|
|
17
|
-
error("no navigator provided!")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class Navigator {
|
|
21
|
-
fun push(content: @Composable () -> Unit) {
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
fun present(content: @Composable () -> Unit) {
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
fun reset(content: @Composable () -> Unit) {
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
fun pop() {
|
|
33
|
-
}
|
|
25
|
+
val PlatformApi = staticCompositionLocalOf<Any?> { null }
|
|
34
26
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
fun
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
fun showModal(
|
|
42
|
-
onClose: () -> Unit = {},
|
|
43
|
-
canBackgroundClose: Boolean = true,
|
|
44
|
-
content: @Composable () -> Unit,
|
|
45
|
-
) {
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
fun showBottomSheet(content: @Composable () -> Unit) {
|
|
49
|
-
}
|
|
27
|
+
interface ComposeApi {
|
|
28
|
+
fun request(funcName: String, params: String?): String
|
|
29
|
+
fun request(funcName: String, params: String?, onResponse: ((String) -> Unit)?): String
|
|
30
|
+
fun requestCallback(funcName: String, params: String?, onResponse: ((String) -> Unit)?)
|
|
31
|
+
fun removeCallback(id: String)
|
|
50
32
|
}
|
|
51
33
|
|
|
52
34
|
@Composable
|
|
53
35
|
fun ApplicationContainer(
|
|
54
36
|
theme: Theme = defaultTheme,
|
|
55
|
-
|
|
37
|
+
composeApi: ComposeApi? = null,
|
|
38
|
+
statusBarHeight: Dp? = AppStatusBar.current ?: getStatusBarHeight(),
|
|
39
|
+
content: @Composable () -> Unit
|
|
56
40
|
) {
|
|
41
|
+
var appTheme by remember { mutableStateOf(theme) }
|
|
42
|
+
val coroutineScope = rememberCoroutineScope()
|
|
43
|
+
|
|
44
|
+
LaunchedEffect(Unit) {
|
|
45
|
+
try {
|
|
46
|
+
composeApi?.request("getConfig", "DESIGN_SYSTEM") {
|
|
47
|
+
val json = Json { ignoreUnknownKeys = true }
|
|
48
|
+
val response = json.parseToJsonElement(it).jsonObject["response"]
|
|
49
|
+
val headerBar = response
|
|
50
|
+
?.jsonObject
|
|
51
|
+
?.get("headerBar")
|
|
52
|
+
?.jsonPrimitive
|
|
53
|
+
?.content
|
|
54
|
+
if (headerBar != null && appTheme.assets.headerBackground == null) {
|
|
55
|
+
coroutineScope.launch {
|
|
56
|
+
appTheme = appTheme.copy(
|
|
57
|
+
assets = ThemeAssets(
|
|
58
|
+
headerBackground = headerBar
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e: Exception) {
|
|
66
|
+
print("@@ == NavigationContainer fetch config error $e")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
disableOverscroll()
|
|
58
71
|
CompositionLocalProvider(
|
|
59
|
-
AppTheme provides
|
|
60
|
-
|
|
61
|
-
AppStatusBar provides
|
|
72
|
+
AppTheme provides appTheme,
|
|
73
|
+
PlatformApi provides composeApi,
|
|
74
|
+
AppStatusBar provides statusBarHeight,
|
|
62
75
|
) {
|
|
63
76
|
content()
|
|
64
77
|
}
|
|
@@ -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
|
) {
|
|
@@ -89,7 +87,9 @@ fun Screen(
|
|
|
89
87
|
)
|
|
90
88
|
|
|
91
89
|
val headerAnimated =
|
|
92
|
-
@Composable {
|
|
90
|
+
@Composable {
|
|
91
|
+
animatedHeader?.composable?.invoke(scrollState.value)
|
|
92
|
+
}
|
|
93
93
|
|
|
94
94
|
Box(
|
|
95
95
|
Modifier.fillMaxSize()
|
|
@@ -97,17 +97,19 @@ fun Screen(
|
|
|
97
97
|
) {
|
|
98
98
|
|
|
99
99
|
if (animatedHeader === null) {
|
|
100
|
-
HeaderBackground(
|
|
100
|
+
HeaderBackground(
|
|
101
|
+
headerType = headerType,
|
|
102
|
+
scrollState = scrollState.value,
|
|
103
|
+
)
|
|
101
104
|
} else {
|
|
102
105
|
Box(
|
|
103
|
-
Modifier.zIndex(
|
|
106
|
+
Modifier.zIndex(2f)
|
|
104
107
|
.background(Color.White.copy(alpha = opacity))
|
|
105
108
|
.graphicsLayer { alpha = opacity }
|
|
106
109
|
) {
|
|
107
110
|
HeaderBackground(
|
|
108
111
|
headerType = headerType,
|
|
109
|
-
|
|
110
|
-
scrollState = scrollState.value
|
|
112
|
+
scrollState = scrollState.value,
|
|
111
113
|
)
|
|
112
114
|
}
|
|
113
115
|
}
|
|
@@ -120,7 +122,6 @@ fun Screen(
|
|
|
120
122
|
goBack = goBack,
|
|
121
123
|
opacity = opacity,
|
|
122
124
|
animatedHeader = animatedHeader,
|
|
123
|
-
useSearchInput = useSearchInput,
|
|
124
125
|
inputSearchProps = inputSearchProps,
|
|
125
126
|
scrollState = scrollState.value
|
|
126
127
|
)
|
|
@@ -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,12 @@ 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,
|
|
101
|
+
val onClearPress: () -> Unit = {}
|
|
93
102
|
)
|
|
94
103
|
|
|
95
104
|
@Composable
|
|
@@ -98,7 +107,6 @@ fun InputSearch(
|
|
|
98
107
|
text = remember { mutableStateOf("") },
|
|
99
108
|
iconColor = AppTheme.current.colors.text.default
|
|
100
109
|
),
|
|
101
|
-
|
|
102
110
|
) {
|
|
103
111
|
var isFocused by remember { mutableStateOf(false) }
|
|
104
112
|
var isBlurred = false
|
|
@@ -124,8 +132,9 @@ fun InputSearch(
|
|
|
124
132
|
lineHeight = 20.sp,
|
|
125
133
|
fontWeight = inputSearchProps.fontWeight.value
|
|
126
134
|
),
|
|
127
|
-
keyboardOptions =
|
|
128
|
-
|
|
135
|
+
keyboardOptions = inputSearchProps.keyboardOptions.copy(keyboardType = inputSearchProps.keyboardType),
|
|
136
|
+
keyboardActions = inputSearchProps.keyboardActions,
|
|
137
|
+
modifier = inputSearchProps.modifier.height(36.dp).onFocusChanged {
|
|
129
138
|
isFocused = it.isFocused
|
|
130
139
|
if (it.isFocused) {
|
|
131
140
|
inputSearchProps.onFocus()
|
|
@@ -137,7 +146,12 @@ fun InputSearch(
|
|
|
137
146
|
decorationBox = { innerTextField ->
|
|
138
147
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
139
148
|
Row(
|
|
140
|
-
modifier = Modifier.weight(
|
|
149
|
+
modifier = Modifier.weight(
|
|
150
|
+
inputSearchProps.showButtonText.ifTrue(
|
|
151
|
+
8f,
|
|
152
|
+
1f
|
|
153
|
+
)
|
|
154
|
+
)
|
|
141
155
|
.background(
|
|
142
156
|
inputSearchProps.backgroundColor,
|
|
143
157
|
RoundedCornerShape(Radius.XL)
|
|
@@ -152,7 +166,6 @@ fun InputSearch(
|
|
|
152
166
|
),
|
|
153
167
|
verticalAlignment = Alignment.CenterVertically
|
|
154
168
|
) {
|
|
155
|
-
//leading icon
|
|
156
169
|
Icon(
|
|
157
170
|
source = "navigation_search",
|
|
158
171
|
modifier = Modifier.padding(end = Spacing.XS),
|
|
@@ -168,12 +181,14 @@ fun InputSearch(
|
|
|
168
181
|
lineHeight = 24.sp,
|
|
169
182
|
fontWeight = inputSearchProps.fontWeight.value
|
|
170
183
|
),
|
|
171
|
-
|
|
184
|
+
maxLines = 1,
|
|
185
|
+
color = placeholderColor,
|
|
186
|
+
overflow = TextOverflow.Ellipsis
|
|
172
187
|
)
|
|
173
188
|
}
|
|
174
189
|
innerTextField()
|
|
175
190
|
}
|
|
176
|
-
if (isFocused && inputSearchProps.text.value.isNotEmpty()) {
|
|
191
|
+
if (inputSearchProps.clearCondition?.invoke() == true || (isFocused && inputSearchProps.text.value.isNotEmpty())) {
|
|
177
192
|
Row {
|
|
178
193
|
Spacer(Modifier.width(Spacing.XS))
|
|
179
194
|
Icon(
|
|
@@ -181,7 +196,10 @@ fun InputSearch(
|
|
|
181
196
|
size = 16.dp,
|
|
182
197
|
color = AppTheme.current.colors.text.hint,
|
|
183
198
|
modifier = Modifier.clickable(
|
|
184
|
-
onClick = {
|
|
199
|
+
onClick = {
|
|
200
|
+
inputSearchProps.text.value = ""
|
|
201
|
+
inputSearchProps.onClearPress.invoke()
|
|
202
|
+
},
|
|
185
203
|
interactionSource = remember { MutableInteractionSource() },
|
|
186
204
|
indication = null
|
|
187
205
|
)
|
|
@@ -192,7 +210,8 @@ fun InputSearch(
|
|
|
192
210
|
inputSearchProps.loading,
|
|
193
211
|
inputSearchProps.icon,
|
|
194
212
|
iconTintColor,
|
|
195
|
-
inputSearchProps.onRightIconPressed
|
|
213
|
+
inputSearchProps.onRightIconPressed,
|
|
214
|
+
iconModifier = inputSearchProps.iconModifier
|
|
196
215
|
)
|
|
197
216
|
}
|
|
198
217
|
}
|
|
@@ -5,34 +5,77 @@ public struct Checkbox: View{
|
|
|
5
5
|
@Binding var checked: Bool
|
|
6
6
|
var disabled: Bool
|
|
7
7
|
var onChange: ((Bool)->Void)?
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
var indeterminate: Bool
|
|
9
|
+
var title: String
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
public init(_ checked: Binding<Bool>,disabled: Bool = false, onChange: ( (Bool) -> Void)?,
|
|
13
|
+
indeterminate: Bool = false,
|
|
14
|
+
title: String = ""){
|
|
10
15
|
self._checked = checked
|
|
11
|
-
self.onChange = onChange
|
|
12
16
|
self.disabled = disabled
|
|
17
|
+
self.onChange = onChange
|
|
18
|
+
self.indeterminate = indeterminate
|
|
19
|
+
self.title = title
|
|
13
20
|
}
|
|
14
|
-
|
|
21
|
+
|
|
15
22
|
func onCheck(){
|
|
16
23
|
checked.toggle()
|
|
17
24
|
onChange?(checked)
|
|
18
25
|
}
|
|
19
|
-
|
|
20
|
-
public var body: some View{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
|
|
27
|
+
public var body: some View {
|
|
28
|
+
HStack(spacing: Spacing.S) {
|
|
29
|
+
// Checkbox
|
|
30
|
+
ZStack {
|
|
31
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
32
|
+
.fill(backgroundColor)
|
|
33
|
+
.frame(width: 20, height: 20)
|
|
34
|
+
.overlay(
|
|
35
|
+
RoundedRectangle(cornerRadius: Radius.XS)
|
|
36
|
+
.stroke(borderColor, lineWidth: Spacing.XXS)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if checked {
|
|
40
|
+
Image(systemName: indeterminate ? "minus" : "checkmark")
|
|
25
41
|
.resizable()
|
|
26
|
-
.foregroundColor(
|
|
27
|
-
.frame(width:
|
|
42
|
+
.foregroundColor(Colors.black01)
|
|
43
|
+
.frame(width: 12, height: 12)
|
|
28
44
|
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
45
|
+
}
|
|
46
|
+
.onTapGesture {
|
|
47
|
+
if !disabled {
|
|
48
|
+
onChange?(checked)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Title
|
|
53
|
+
if !title.isEmpty {
|
|
54
|
+
Text(title)
|
|
55
|
+
.font(.description1)
|
|
56
|
+
}
|
|
33
57
|
}
|
|
34
|
-
.opacity(disabled ? 0.4 : 1)
|
|
35
|
-
.disabled(disabled)
|
|
36
58
|
}
|
|
37
|
-
|
|
59
|
+
|
|
60
|
+
private var borderColor: Color {
|
|
61
|
+
if disabled {
|
|
62
|
+
return Colors.black03
|
|
63
|
+
} else if checked {
|
|
64
|
+
return Colors.primary
|
|
65
|
+
} else {
|
|
66
|
+
return Colors.text
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private var backgroundColor: Color {
|
|
71
|
+
if disabled && checked {
|
|
72
|
+
return Colors.pink08
|
|
73
|
+
} else if checked {
|
|
74
|
+
return Colors.primary
|
|
75
|
+
} else {
|
|
76
|
+
return Colors.black01
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
38
81
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
struct IconSource: Codable {
|
|
4
|
+
let uri: String
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
func loadIconSources() -> [String: IconSource] {
|
|
8
|
+
guard let url = Bundle.main.url(forResource: "icon", withExtension: "json") else {
|
|
9
|
+
print("Error: icon.json not found")
|
|
10
|
+
return [:]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
do {
|
|
14
|
+
let data = try Data(contentsOf: url)
|
|
15
|
+
let decoded = try JSONDecoder().decode([String: IconSource].self, from: data)
|
|
16
|
+
return decoded
|
|
17
|
+
} catch {
|
|
18
|
+
print("Error decoding icon.json: \(error)")
|
|
19
|
+
return [:]
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Load the JSON data into a global variable
|
|
24
|
+
let iconSources: [String: IconSource] = loadIconSources()
|
|
25
|
+
|
|
26
|
+
// Icon ViewModel
|
|
27
|
+
public struct Icon: View {
|
|
28
|
+
var source: String
|
|
29
|
+
var size: CGFloat
|
|
30
|
+
var color: Color?
|
|
31
|
+
var accessibilityLabel: String?
|
|
32
|
+
|
|
33
|
+
public init(
|
|
34
|
+
source: String,
|
|
35
|
+
size: CGFloat = 24,
|
|
36
|
+
color: Color? = nil,
|
|
37
|
+
accessibilityLabel: String? = nil
|
|
38
|
+
) {
|
|
39
|
+
self.source = source
|
|
40
|
+
self.size = size
|
|
41
|
+
self.color = color
|
|
42
|
+
self.accessibilityLabel = accessibilityLabel
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public var body: some View {
|
|
46
|
+
if let uri = iconSources[source]?.uri {
|
|
47
|
+
ImageView(uri)
|
|
48
|
+
.frame(width: size, height: size)
|
|
49
|
+
.foregroundColor(color)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
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,95 @@ public struct Input: View {
|
|
|
93
113
|
}
|
|
94
114
|
|
|
95
115
|
public var body: some View {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
})
|
|
102
|
-
.font(.paragraph)
|
|
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
|
+
Icon(source: leadingIcon, size: 16, color: leadingIconColor)
|
|
115
121
|
}
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
if !isSecure {
|
|
124
|
+
TextField("", text: $value, onEditingChanged: { isChange in
|
|
125
|
+
self.focused = isChange
|
|
126
|
+
})
|
|
127
|
+
.disabled(disabled)
|
|
128
|
+
.font(fontWeight == .bold ? .headline : .body)
|
|
129
|
+
} else {
|
|
130
|
+
DotTextField(value: $value)
|
|
131
|
+
.disabled(disabled)
|
|
132
|
+
}
|
|
118
133
|
|
|
134
|
+
if loading {
|
|
135
|
+
ActivityIndicator(isAnimating: .constant(true), style: .medium)
|
|
136
|
+
.frame(width: 20, height: 20)
|
|
137
|
+
} else if !value.isEmpty {
|
|
138
|
+
SwiftButton(action: onCancel){
|
|
139
|
+
Image(systemName: "x.circle.fill")
|
|
140
|
+
.foregroundColor(Colors.text)
|
|
141
|
+
.frame(width: 16, height: 16)
|
|
142
|
+
.padding(.leading, -4)
|
|
143
|
+
.padding(.trailing, 8)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
rightIcon
|
|
119
148
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
.
|
|
128
|
-
.padding(.
|
|
129
|
-
.padding(.
|
|
149
|
+
.padding(.horizontal, 12)
|
|
150
|
+
.frame(height: size == .small ? 40 : 48)
|
|
151
|
+
.background(disabled ? Colors.black01 : Color.white)
|
|
152
|
+
.overlay(RoundedRectangle(cornerRadius: 8).stroke(focused ? Colors.pink03 : Colors.border, lineWidth: 1))
|
|
153
|
+
|
|
154
|
+
if value.isEmpty {
|
|
155
|
+
Text(placeholder + (required ? " *" : ""))
|
|
156
|
+
.foregroundColor(Colors.black09)
|
|
157
|
+
.padding(.horizontal, leadingIcon == nil ? 12 : 32)
|
|
158
|
+
.padding(.top, 12)
|
|
159
|
+
.allowsHitTesting(false)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if !floatingValue.isEmpty {
|
|
163
|
+
FloatingComponent(floatingValue, floatingIcon: floatingIconURL.map { url in
|
|
164
|
+
AnyView(
|
|
165
|
+
Icon(source: url, size: 16, color: floatingIconColor)
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
|
|
130
169
|
}
|
|
131
170
|
}
|
|
171
|
+
|
|
172
|
+
if !errorMessage.isEmpty {
|
|
173
|
+
Text(errorMessage)
|
|
174
|
+
.foregroundColor(.red)
|
|
175
|
+
.font(.caption)
|
|
176
|
+
} else if let hint = hintText {
|
|
177
|
+
Text(hint)
|
|
178
|
+
.foregroundColor(Colors.black09)
|
|
179
|
+
.font(.caption)
|
|
180
|
+
}
|
|
132
181
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
.allowsHitTesting(false)
|
|
144
|
-
}
|
|
145
|
-
if (!floatingValue.isEmpty) {FloatingComponent(floatingValue, floatingIcon: floatingIcon)}
|
|
146
|
-
}.padding(.bottom, 8)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
struct ActivityIndicator: UIViewRepresentable {
|
|
187
|
+
@Binding var isAnimating: Bool
|
|
188
|
+
let style: UIActivityIndicatorView.Style
|
|
189
|
+
|
|
190
|
+
func makeUIView(context: UIViewRepresentableContext<ActivityIndicator>) -> UIActivityIndicatorView {
|
|
191
|
+
return UIActivityIndicatorView(style: style)
|
|
147
192
|
}
|
|
148
|
-
|
|
193
|
+
|
|
194
|
+
func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<ActivityIndicator>) {
|
|
195
|
+
isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public enum InputSize {
|
|
200
|
+
case small
|
|
201
|
+
case large
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public enum InputFontWeight {
|
|
205
|
+
case regular
|
|
206
|
+
case bold
|
|
149
207
|
}
|
|
@@ -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
|
+
|
|
@@ -4,18 +4,61 @@ public extension Font {
|
|
|
4
4
|
static func appFont(size: CGFloat) -> Font {
|
|
5
5
|
return Font.custom("SFProText", size: size)
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
// New supported typography styles
|
|
9
|
+
static let headline_default_bold = appFont(size: 24).weight(.bold)
|
|
10
|
+
static let header_m_bold = appFont(size: 18).weight(.bold)
|
|
11
|
+
static let header_default_bold = appFont(size: 16).weight(.bold)
|
|
12
|
+
static let header_s_semibold = appFont(size: 14).weight(.semibold)
|
|
13
|
+
static let header_xs_semibold = appFont(size: 12).weight(.semibold)
|
|
14
|
+
static let body_default_regular = appFont(size: 14).weight(.regular)
|
|
15
|
+
static let description_default_regular = appFont(size: 12).weight(.regular)
|
|
16
|
+
static let description_xs_regular = appFont(size: 10).weight(.regular)
|
|
17
|
+
static let label_default_medium = appFont(size: 14).weight(.medium)
|
|
18
|
+
static let label_s_medium = appFont(size: 12).weight(.medium)
|
|
19
|
+
static let label_xs_medium = appFont(size: 10).weight(.medium)
|
|
20
|
+
static let action_default_bold = appFont(size: 16).weight(.bold)
|
|
21
|
+
static let action_s_bold = appFont(size: 14).weight(.bold)
|
|
22
|
+
static let action_xs_bold = appFont(size: 12).weight(.bold)
|
|
23
|
+
static let action_xxs_bold = appFont(size: 10).weight(.bold)
|
|
24
|
+
|
|
25
|
+
// Legacy styles
|
|
26
|
+
static let headline_default = appFont(size: 28).weight(.semibold)
|
|
27
|
+
static let headline_s = appFont(size: 24).weight(.semibold)
|
|
28
|
+
static let headline_l = appFont(size: 32).weight(.semibold)
|
|
29
|
+
static let headline_xl = appFont(size: 36).weight(.semibold)
|
|
30
|
+
static let title_default = appFont(size: 20).weight(.bold)
|
|
31
|
+
static let title_xs = appFont(size: 16).weight(.bold)
|
|
32
|
+
static let title_s = appFont(size: 18).weight(.bold)
|
|
33
|
+
static let header_default = appFont(size: 15).weight(.semibold)
|
|
34
|
+
static let header_xs = appFont(size: 12).weight(.semibold)
|
|
35
|
+
static let header_s = appFont(size: 14).weight(.semibold)
|
|
36
|
+
static let paragraph_default = appFont(size: 15).weight(.regular)
|
|
37
|
+
static let paragraph_bold = appFont(size: 15).weight(.bold)
|
|
38
|
+
static let description_default = appFont(size: 14).weight(.regular)
|
|
39
|
+
static let description_xs = appFont(size: 10).weight(.regular)
|
|
40
|
+
static let description_s = appFont(size: 12).weight(.regular)
|
|
41
|
+
static let label_default = appFont(size: 14).weight(.medium)
|
|
42
|
+
static let label_xxs = appFont(size: 8).weight(.medium)
|
|
43
|
+
static let label_xs = appFont(size: 10).weight(.medium)
|
|
44
|
+
static let label_s = appFont(size: 12).weight(.medium)
|
|
45
|
+
static let action_default = appFont(size: 16).weight(.bold)
|
|
46
|
+
static let action_xxs = appFont(size: 10).weight(.bold)
|
|
47
|
+
static let action_xs = appFont(size: 12).weight(.bold)
|
|
48
|
+
static let action_s = appFont(size: 15).weight(.bold)
|
|
49
|
+
|
|
50
|
+
// deprecated
|
|
51
|
+
static let h1 = appFont(size: 36).weight(.semibold) //headline_xl
|
|
9
52
|
static let h2 = appFont(size: 32).weight(.semibold)
|
|
10
53
|
static let h3 = appFont(size: 28).weight(.semibold)
|
|
11
54
|
static let h4 = appFont(size: 24).weight(.semibold)
|
|
12
55
|
static let title1 = appFont(size: 20).weight(.bold)
|
|
13
56
|
static let title2 = appFont(size: 18).weight(.bold)
|
|
14
57
|
static let title3 = appFont(size: 16).weight(.bold)
|
|
15
|
-
static let headerText1 = appFont(size: 15).weight(.semibold)
|
|
58
|
+
static let headerText1 = appFont(size: 15).weight(.semibold) //header_default
|
|
16
59
|
static let headerText2 = appFont(size: 14).weight(.semibold)
|
|
17
60
|
static let headerText3 = appFont(size: 12).weight(.semibold)
|
|
18
|
-
static let paragraph = appFont(size: 15).weight(.regular)
|
|
61
|
+
static let paragraph = appFont(size: 15).weight(.regular) //paragraph_default
|
|
19
62
|
static let description1 = appFont(size: 14).weight(.regular)
|
|
20
63
|
static let description2 = appFont(size: 12).weight(.regular)
|
|
21
64
|
static let description3 = appFont(size: 10).weight(.regular)
|