@momo-kits/native-kits 0.152.4-beta.6 → 0.152.4-maxapi

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