@momo-kits/native-kits 0.153.2-newsfeed.8 → 0.154.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.
Files changed (117) hide show
  1. package/CODE_OF_CONDUCT.md +133 -0
  2. package/CONTRIBUTING.md +114 -0
  3. package/LICENSE +20 -0
  4. package/README.md +5 -175
  5. package/build.gradle.kts +32 -0
  6. package/compose/MoMoComposeKits.podspec +54 -0
  7. package/compose/build.gradle.kts +149 -0
  8. package/compose/src/androidMain/AndroidManifest.xml +2 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +105 -0
  10. package/compose/src/commonMain/composeResources/files/lottie_circle_loader.json +1 -0
  11. package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
  12. package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
  13. package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
  14. package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
  15. package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
  16. package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
  17. package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
  18. package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
  19. package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
  20. package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
  21. package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
  22. package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
  23. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
  24. package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +308 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +715 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +214 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +404 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +78 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +27 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +334 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +345 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +90 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +133 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +69 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +143 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +179 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +111 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +393 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +164 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +234 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +226 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +221 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +246 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +233 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +50 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +34 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +85 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +33 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +338 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +95 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +64 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +19 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +89 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +91 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +83 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +208 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +172 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +199 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +237 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +13 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +189 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +59 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +49 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +232 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +111 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +94 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +159 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +302 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +483 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +169 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +215 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +180 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +251 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +31 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +385 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +125 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +38 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
  109. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +144 -0
  110. package/gradle.properties +19 -0
  111. package/gradlew +240 -0
  112. package/gradlew.bat +91 -0
  113. package/ios/Theme.md +18 -0
  114. package/ios/native-kits.podspec +16 -18
  115. package/local.properties +8 -0
  116. package/package.json +4 -2
  117. package/settings.gradle.kts +25 -0
@@ -0,0 +1,393 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.clickable
6
+ import androidx.compose.foundation.interaction.MutableInteractionSource
7
+ import androidx.compose.foundation.layout.Box
8
+ import androidx.compose.foundation.layout.Column
9
+ import androidx.compose.foundation.layout.Row
10
+ import androidx.compose.foundation.layout.Spacer
11
+ import androidx.compose.foundation.layout.fillMaxWidth
12
+ import androidx.compose.foundation.layout.height
13
+ import androidx.compose.foundation.layout.offset
14
+ import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.layout.size
16
+ import androidx.compose.foundation.layout.width
17
+ import androidx.compose.foundation.layout.wrapContentSize
18
+ import androidx.compose.foundation.shape.RoundedCornerShape
19
+ import androidx.compose.foundation.text.BasicTextField
20
+ import androidx.compose.foundation.text.KeyboardOptions
21
+ import androidx.compose.material3.CircularProgressIndicator
22
+ import androidx.compose.runtime.Composable
23
+ import androidx.compose.runtime.MutableState
24
+ import androidx.compose.runtime.getValue
25
+ import androidx.compose.runtime.mutableStateOf
26
+ import androidx.compose.runtime.remember
27
+ import androidx.compose.runtime.setValue
28
+ import androidx.compose.ui.Alignment
29
+ import androidx.compose.ui.Modifier
30
+ import androidx.compose.ui.focus.onFocusChanged
31
+ import androidx.compose.ui.graphics.Color
32
+ import androidx.compose.ui.text.TextStyle
33
+ import androidx.compose.ui.text.font.FontWeight
34
+ import androidx.compose.ui.text.input.KeyboardType
35
+ import androidx.compose.ui.text.input.PasswordVisualTransformation
36
+ import androidx.compose.ui.text.input.VisualTransformation
37
+ import androidx.compose.ui.text.style.TextAlign
38
+ import androidx.compose.ui.unit.Dp
39
+ import androidx.compose.ui.unit.dp
40
+ import androidx.compose.ui.unit.sp
41
+ import androidx.compose.ui.zIndex
42
+ import vn.momo.kits.const.AppTheme
43
+ import vn.momo.kits.const.Radius
44
+ import vn.momo.kits.const.Spacing
45
+ import vn.momo.kits.const.Typography
46
+ import vn.momo.kits.const.scaleSize
47
+ import vn.momo.kits.modifier.setAutomationId
48
+
49
+ data class InputSizeDetail(
50
+ val borderWidth: Dp,
51
+ val borderRadius: Dp,
52
+ val height: Dp,
53
+ )
54
+
55
+ enum class InputSize(val values: InputSizeDetail) {
56
+ SMALL(
57
+ InputSizeDetail(
58
+ borderWidth = 1.dp,
59
+ borderRadius = Radius.S,
60
+ height = 48.dp
61
+ )
62
+ ),
63
+ LARGE(
64
+ InputSizeDetail(
65
+ borderWidth = 1.dp,
66
+ borderRadius = Radius.S,
67
+ height = 56.dp
68
+ )
69
+ )
70
+ }
71
+
72
+ enum class InputFontWeight(val value: FontWeight) {
73
+ REGULAR(
74
+ FontWeight(400)
75
+ ),
76
+ BOLD(
77
+ FontWeight(700)
78
+ )
79
+ }
80
+
81
+ // Consolidated input state for better performance
82
+ data class InputState(
83
+ val isFocused: Boolean = false,
84
+ val passHidden: Boolean = false,
85
+ val hasBeenBlurred: Boolean = false
86
+ )
87
+
88
+ @Composable
89
+ fun getBorderColor(isFocused: Boolean, error: String, disabled: Boolean): Color {
90
+ val theme = AppTheme.current
91
+ return remember(isFocused, error, disabled, theme) {
92
+ when {
93
+ disabled -> theme.colors.border.disable
94
+ error.isNotEmpty() -> theme.colors.error.primary
95
+ isFocused -> theme.colors.primary
96
+ else -> theme.colors.border.default
97
+ }
98
+ }
99
+ }
100
+
101
+ @Composable
102
+ fun RenderRightIcon(loading: Boolean, icon: String, color: Color, onClick: () -> Unit) {
103
+ if (loading) {
104
+ Box {
105
+ CircularProgressIndicator(
106
+ modifier = Modifier.size(16.dp),
107
+ color = color,
108
+ trackColor = Color.Transparent,
109
+ strokeWidth = 2.dp
110
+ )
111
+ }
112
+ }
113
+ if (icon.isNotEmpty()) {
114
+ Icon(
115
+ source = icon,
116
+ color = color,
117
+ size = 24.dp,
118
+ modifier = Modifier.clickable(
119
+ onClick = onClick,
120
+ interactionSource = remember { MutableInteractionSource() },
121
+ indication = null
122
+ )
123
+ )
124
+ }
125
+ }
126
+
127
+ @Composable
128
+ fun ErrorView(errorMessage: String, errorSpacing: Boolean, hintText: String) {
129
+ val theme = AppTheme.current
130
+ val errorColor = remember(theme) { theme.colors.error.primary }
131
+ val hintColor = remember(theme) { theme.colors.text.hint }
132
+ val hintTextDefault = remember(hintText) { hintText.ifEmpty { "Không thể chỉnh sửa" } }
133
+
134
+ if (errorMessage.isNotEmpty() || hintText.isNotEmpty()) {
135
+ val color = if (errorMessage.isNotEmpty()) errorColor else hintColor
136
+ Row(
137
+ Modifier.padding(top = Spacing.XS),
138
+ verticalAlignment = Alignment.CenterVertically
139
+ ) {
140
+ Icon(
141
+ size = 16.dp,
142
+ color = color,
143
+ source = "ic_error"
144
+ )
145
+ Text(
146
+ errorMessage.ifEmpty { hintTextDefault },
147
+ style = Typography.descriptionDefaultRegular,
148
+ modifier = Modifier.padding(start = Spacing.XS),
149
+ color = color,
150
+ )
151
+ }
152
+ } else if (errorSpacing) {
153
+ Spacer(Modifier.padding(top = Spacing.XS).height(18.dp))
154
+ }
155
+ }
156
+
157
+ @Composable
158
+ fun Input(
159
+ text: MutableState<String> = remember { mutableStateOf("") },
160
+ floatingValue: String = "",
161
+ floatingValueColor: Color = AppTheme.current.colors.text.hint,
162
+ floatingIcon: String = "",
163
+ floatingIconColor: Color = AppTheme.current.colors.text.default,
164
+ placeholder: String = "",
165
+ size: InputSize = InputSize.SMALL,
166
+ onChangeText: (String) -> Unit = {},
167
+ hintText: String = "",
168
+ error: String = "",
169
+ errorSpacing: Boolean = false,
170
+ disabled: Boolean = false,
171
+ readOnly: Boolean = false,
172
+ secureTextEntry: Boolean = false,
173
+ icon: String = "",
174
+ iconColor: Color = AppTheme.current.colors.text.default,
175
+ onRightIconPressed: () -> Unit = {},
176
+ leadingIcon: String = "",
177
+ leadingIconColor: Color = AppTheme.current.colors.text.hint,
178
+ onFocus: () -> Unit = {},
179
+ onBlur: () -> Unit = {},
180
+ loading: Boolean = false,
181
+ required: Boolean = false,
182
+ fontWeight: InputFontWeight = InputFontWeight.REGULAR,
183
+ keyboardType: KeyboardType = KeyboardType.Text,
184
+ modifier: Modifier = Modifier,
185
+ ) {
186
+ // Consolidated state management
187
+ var inputState by remember { mutableStateOf(InputState()) }
188
+
189
+ // Memoized color calculations
190
+ val theme = AppTheme.current
191
+ val colors = remember(disabled, theme) {
192
+ if (disabled) {
193
+ val disabledColor = theme.colors.text.disable
194
+ Triple(disabledColor, disabledColor, disabledColor)
195
+ } else {
196
+ Triple(
197
+ theme.colors.text.default, // textColor
198
+ theme.colors.text.hint, // placeholderColor
199
+ iconColor // iconTintColor
200
+ )
201
+ }
202
+ }
203
+
204
+ val (textColor, placeholderColor, iconTintColor) = colors
205
+
206
+ val floatingTitleColor = remember(disabled, floatingValueColor, theme) {
207
+ if (disabled) theme.colors.text.disable else floatingValueColor
208
+ }
209
+
210
+ val floatingIconTintColor = remember(disabled, floatingIconColor, theme) {
211
+ if (disabled) theme.colors.text.disable else floatingIconColor
212
+ }
213
+
214
+ val testId = remember(disabled, floatingValue) {
215
+ if (disabled) "input_${floatingValue}_disabled" else "input_$floatingValue"
216
+ }
217
+
218
+ val fontSize = 16.sp
219
+ val lineHeight = 24.sp
220
+ val scaleFontSize = scaleSize(fontSize)
221
+ val scaleLineHeight = scaleSize(lineHeight)
222
+
223
+ val textStyle = remember(textColor, fontWeight) {
224
+ TextStyle(
225
+ color = textColor,
226
+ fontSize = scaleFontSize,
227
+ lineHeight = scaleLineHeight,
228
+ fontWeight = fontWeight.value
229
+ )
230
+ }
231
+
232
+ val placeholderStyle = remember(placeholderColor, fontWeight) {
233
+ TextStyle(
234
+ fontSize = fontSize,
235
+ lineHeight = lineHeight,
236
+ fontWeight = fontWeight.value,
237
+ textAlign = TextAlign.Center
238
+ )
239
+ }
240
+
241
+ val keyboardOptionsConfig = remember(secureTextEntry, keyboardType) {
242
+ KeyboardOptions.Default.copy(
243
+ keyboardType = if (secureTextEntry) KeyboardType.Ascii else keyboardType
244
+ )
245
+ }
246
+
247
+ val visualTransformation = remember(secureTextEntry, inputState.passHidden) {
248
+ if (secureTextEntry && !inputState.passHidden)
249
+ PasswordVisualTransformation()
250
+ else
251
+ VisualTransformation.None
252
+ }
253
+
254
+ Column(modifier = modifier.setAutomationId(testId)) {
255
+ BasicTextField(
256
+ enabled = !disabled,
257
+ readOnly = readOnly,
258
+ singleLine = true,
259
+ value = text.value,
260
+ textStyle = textStyle,
261
+ visualTransformation = visualTransformation,
262
+ keyboardOptions = keyboardOptionsConfig,
263
+ modifier = Modifier
264
+ .height(size.values.height)
265
+ .onFocusChanged { focusState ->
266
+ val wasFocused = inputState.isFocused
267
+ inputState = inputState.copy(
268
+ isFocused = focusState.isFocused,
269
+ hasBeenBlurred = inputState.hasBeenBlurred || wasFocused
270
+ )
271
+
272
+ if (focusState.isFocused) {
273
+ onFocus()
274
+ } else if (inputState.hasBeenBlurred) {
275
+ onBlur()
276
+ }
277
+ },
278
+ onValueChange = onChangeText,
279
+ decorationBox = { innerTextField ->
280
+ // Floating label
281
+ if (floatingValue.isNotEmpty() || floatingIcon.isNotEmpty()) {
282
+ Box(
283
+ modifier = Modifier
284
+ .wrapContentSize()
285
+ .offset(y = (-size.values.height / 2), x = Spacing.S)
286
+ .background(theme.colors.background.surface)
287
+ .zIndex(10f),
288
+ ) {
289
+ Row(
290
+ modifier = Modifier.padding(horizontal = Spacing.S),
291
+ verticalAlignment = Alignment.CenterVertically
292
+ ) {
293
+ Text(
294
+ floatingValue,
295
+ style = Typography.labelSMedium,
296
+ color = floatingTitleColor
297
+ )
298
+ if (required) {
299
+ Text(
300
+ "*",
301
+ style = Typography.labelSMedium,
302
+ color = theme.colors.error.primary,
303
+ )
304
+ }
305
+ if (floatingIcon.isNotEmpty()) {
306
+ Icon(
307
+ source = floatingIcon,
308
+ modifier = Modifier.padding(start = Spacing.XS),
309
+ size = 16.dp,
310
+ color = floatingIconTintColor
311
+ )
312
+ }
313
+ }
314
+ }
315
+ }
316
+
317
+ // Input container
318
+ Box(
319
+ modifier = Modifier
320
+ .fillMaxWidth()
321
+ .border(
322
+ 1.dp,
323
+ getBorderColor(inputState.isFocused, error, disabled),
324
+ RoundedCornerShape(size.values.borderRadius)
325
+ ),
326
+ contentAlignment = Alignment.CenterStart
327
+ ) {
328
+ Row(
329
+ modifier = Modifier.padding(horizontal = Spacing.M),
330
+ verticalAlignment = Alignment.CenterVertically
331
+ ) {
332
+ // Leading icon
333
+ if (leadingIcon.isNotEmpty()) {
334
+ Icon(
335
+ source = leadingIcon,
336
+ modifier = Modifier.padding(end = Spacing.M),
337
+ size = if (size == InputSize.SMALL) 24.dp else 32.dp,
338
+ color = leadingIconColor
339
+ )
340
+ }
341
+
342
+ Box(Modifier.weight(1f), contentAlignment = Alignment.CenterStart) {
343
+ if (text.value.isEmpty()) {
344
+ Text(
345
+ text = placeholder,
346
+ style = placeholderStyle,
347
+ color = placeholderColor
348
+ )
349
+
350
+ }
351
+ innerTextField()
352
+ }
353
+
354
+ // Clear button
355
+ if (inputState.isFocused && text.value.isNotEmpty()) {
356
+ Row {
357
+ Spacer(Modifier.width(Spacing.XS))
358
+ Icon(
359
+ source = "24_navigation_close_circle_full",
360
+ size = 16.dp,
361
+ color = theme.colors.text.hint,
362
+ modifier = Modifier.clickable(
363
+ onClick = { text.value = "" },
364
+ interactionSource = remember { MutableInteractionSource() },
365
+ indication = null
366
+ )
367
+ )
368
+ }
369
+ }
370
+
371
+ // Right icon (password toggle or custom icon)
372
+ when {
373
+ secureTextEntry -> {
374
+ val iconName = if (inputState.passHidden) "24_security_eye_open" else "24_security_eye_off"
375
+ val togglePassword = {
376
+ onRightIconPressed()
377
+ inputState = inputState.copy(passHidden = !inputState.passHidden)
378
+ }
379
+ RenderRightIcon(loading, iconName, iconTintColor, togglePassword)
380
+ }
381
+ else -> {
382
+ RenderRightIcon(loading, icon, iconTintColor, onRightIconPressed)
383
+ }
384
+ }
385
+ }
386
+ }
387
+ },
388
+ )
389
+
390
+ // Error/hint display
391
+ ErrorView(error, errorSpacing, hintText)
392
+ }
393
+ }
@@ -0,0 +1,164 @@
1
+ package vn.momo.kits.components
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.border
5
+ import androidx.compose.foundation.clickable
6
+ import androidx.compose.foundation.layout.Box
7
+ import androidx.compose.foundation.layout.Column
8
+ import androidx.compose.foundation.layout.Row
9
+ import androidx.compose.foundation.layout.fillMaxWidth
10
+ import androidx.compose.foundation.layout.height
11
+ import androidx.compose.foundation.layout.offset
12
+ import androidx.compose.foundation.layout.padding
13
+ import androidx.compose.foundation.layout.wrapContentSize
14
+ import androidx.compose.foundation.shape.RoundedCornerShape
15
+ import androidx.compose.runtime.Composable
16
+ import androidx.compose.runtime.MutableState
17
+ import androidx.compose.runtime.getValue
18
+ import androidx.compose.runtime.mutableStateOf
19
+ import androidx.compose.runtime.remember
20
+ import androidx.compose.ui.Alignment
21
+ import androidx.compose.ui.Modifier
22
+ import androidx.compose.ui.graphics.Color
23
+ import androidx.compose.ui.semantics.contentDescription
24
+ import androidx.compose.ui.semantics.semantics
25
+ import androidx.compose.ui.semantics.testTag
26
+ import androidx.compose.ui.text.TextStyle
27
+ import androidx.compose.ui.unit.dp
28
+ import androidx.compose.ui.unit.sp
29
+ import androidx.compose.ui.zIndex
30
+ import vn.momo.kits.const.AppTheme
31
+ import vn.momo.kits.const.Spacing
32
+ import vn.momo.kits.const.Typography
33
+ import vn.momo.kits.const.scaleSize
34
+
35
+ @Composable
36
+ fun InputDropDown(
37
+ value: MutableState<String> = remember { mutableStateOf("") },
38
+ floatingValue: String = "",
39
+ floatingValueColor: Color = AppTheme.current.colors.text.hint,
40
+ floatingIcon: String = "",
41
+ floatingIconColor: Color = AppTheme.current.colors.text.default,
42
+ placeholder: String = "",
43
+ size: InputSize = InputSize.SMALL,
44
+ onPress: () -> Unit = {},
45
+ hintText: String = "",
46
+ error: String = "",
47
+ errorSpacing: Boolean = false,
48
+ disabled: Boolean = false,
49
+ icon: String = "arrow_chevron_down_small",
50
+ iconColor: Color = AppTheme.current.colors.text.default,
51
+ onRightIconPressed: () -> Unit = {},
52
+ leadingIcon: String = "",
53
+ leadingIconColor: Color = AppTheme.current.colors.text.hint,
54
+ loading: Boolean = false,
55
+ required: Boolean = false,
56
+ ) {
57
+ val isFocused by remember { mutableStateOf(false) }
58
+
59
+ val disabledColor = AppTheme.current.colors.text.disable
60
+ var textColor = AppTheme.current.colors.text.default
61
+ var placeholderColor = AppTheme.current.colors.text.hint
62
+ var iconTintColor = iconColor
63
+ val floatingTitleColor = when {
64
+ disabled -> AppTheme.current.colors.text.disable
65
+ else -> floatingValueColor
66
+ }
67
+ val floatingIconTintColor = when {
68
+ disabled -> AppTheme.current.colors.text.disable
69
+ else -> floatingIconColor
70
+ }
71
+
72
+ if (disabled) {
73
+ textColor = disabledColor
74
+ placeholderColor = disabledColor
75
+ iconTintColor = disabledColor
76
+ }
77
+
78
+ val testId = if (disabled) "input_${floatingValue}_disabled" else "input_$floatingValue"
79
+
80
+ val fontSize = scaleSize(16.sp)
81
+ val lineHeight = scaleSize(24.sp)
82
+
83
+ Column(modifier = Modifier.clickable(enabled = !disabled, onClick = onPress).semantics {
84
+ contentDescription = floatingValue; testTag = testId
85
+ }) {
86
+ Box {
87
+ if (floatingValue.isNotEmpty() || floatingIcon.isNotEmpty()) {
88
+ Box(
89
+ modifier = Modifier.wrapContentSize()
90
+ .offset(y = -Spacing.S, x = Spacing.S)
91
+ .background(AppTheme.current.colors.background.surface)
92
+ .zIndex(10f),
93
+ ) {
94
+ Row(
95
+ modifier = Modifier
96
+ .padding(horizontal = Spacing.S),
97
+ verticalAlignment = Alignment.CenterVertically
98
+ ) {
99
+ Text(
100
+ floatingValue,
101
+ style = Typography.labelSMedium,
102
+ color = floatingTitleColor
103
+ )
104
+ if (required) {
105
+ Text(
106
+ "*",
107
+ style = Typography.labelSMedium,
108
+ color = AppTheme.current.colors.error.primary,
109
+ )
110
+ }
111
+ if (floatingIcon.isNotEmpty()) {
112
+ Icon(
113
+ source = floatingIcon,
114
+ modifier = Modifier.padding(start = Spacing.XS),
115
+ size = 16.dp,
116
+ color = floatingIconTintColor
117
+ )
118
+ }
119
+ }
120
+ }
121
+ }
122
+ Box(
123
+ modifier = Modifier.fillMaxWidth()
124
+ .height(if (size == InputSize.SMALL) 48.dp else 56.dp)
125
+ .border(
126
+ 1.dp,
127
+ getBorderColor(isFocused, error, disabled),
128
+ RoundedCornerShape(size.values.borderRadius)
129
+ ),
130
+ contentAlignment = Alignment.CenterStart
131
+ ) {
132
+ Row(
133
+ modifier = Modifier.padding(horizontal = Spacing.M),
134
+ verticalAlignment = Alignment.CenterVertically
135
+ ) {
136
+ //leading icon
137
+ if (leadingIcon.isNotEmpty()) {
138
+ Icon(
139
+ source = leadingIcon,
140
+ modifier = Modifier.padding(end = Spacing.M),
141
+ size = if (size.name == InputSize.SMALL.name) 24.dp else 32.dp,
142
+ color = leadingIconColor
143
+ )
144
+ }
145
+ Box(Modifier.weight(1f)) {
146
+ Text(
147
+ text = value.value.ifEmpty { placeholder },
148
+ style = TextStyle(
149
+ fontSize = fontSize,
150
+ lineHeight = lineHeight
151
+ ),
152
+ color = if (value.value.isEmpty()) placeholderColor else textColor
153
+ )
154
+ }
155
+ RenderRightIcon(
156
+ loading, icon, iconTintColor,
157
+ onRightIconPressed
158
+ )
159
+ }
160
+ }
161
+ }
162
+ ErrorView(error, errorSpacing, hintText)
163
+ }
164
+ }