@momo-kits/native-kits 0.157.7 → 0.157.8-debug
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/build.gradle.kts +11 -0
- package/compose/build.gradle.kts +180 -0
- package/compose/build.gradle.kts.backup +180 -0
- package/compose/compose.podspec +54 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +117 -0
- package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +107 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +720 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +405 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +85 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +340 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +448 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +233 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +254 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +352 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +130 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +239 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +14 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +253 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +133 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +99 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +164 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +333 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +552 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +162 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +279 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +132 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +167 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +46 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +88 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +161 -0
- package/gradle/libs.versions.toml +57 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/package.json +1 -1
- package/settings.gradle.kts +52 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.border
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
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.Spacer
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.height
|
|
12
|
+
import androidx.compose.foundation.layout.padding
|
|
13
|
+
import androidx.compose.foundation.layout.size
|
|
14
|
+
import androidx.compose.foundation.layout.width
|
|
15
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
16
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
17
|
+
import androidx.compose.runtime.Composable
|
|
18
|
+
import androidx.compose.runtime.remember
|
|
19
|
+
import androidx.compose.ui.Alignment
|
|
20
|
+
import androidx.compose.ui.Modifier
|
|
21
|
+
import androidx.compose.ui.graphics.Color
|
|
22
|
+
import androidx.compose.ui.text.style.TextAlign
|
|
23
|
+
import androidx.compose.ui.unit.Dp
|
|
24
|
+
import androidx.compose.ui.unit.dp
|
|
25
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
26
|
+
import vn.momo.kits.const.AppTheme
|
|
27
|
+
import vn.momo.kits.const.Colors
|
|
28
|
+
import vn.momo.kits.const.Radius
|
|
29
|
+
import vn.momo.kits.const.Spacing
|
|
30
|
+
import vn.momo.kits.const.Typography
|
|
31
|
+
import vn.momo.kits.modifier.activeOpacityClickable
|
|
32
|
+
import vn.momo.kits.modifier.conditional
|
|
33
|
+
|
|
34
|
+
// ─── Data model ────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
data class StepItem(
|
|
37
|
+
val title: String,
|
|
38
|
+
val description: String? = null,
|
|
39
|
+
val error: Boolean = false,
|
|
40
|
+
val time: String? = null,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
// ─── Enumerations ──────────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
enum class StepsSize { SMALL, LARGE }
|
|
46
|
+
|
|
47
|
+
enum class StepsAlign { LEFT, RIGHT, CENTER, STRETCH }
|
|
48
|
+
|
|
49
|
+
// ─── Internal status ───────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
private enum class StepStatus { INCOMPLETE, CURRENT, COMPLETED, ERROR, DISABLED }
|
|
52
|
+
|
|
53
|
+
private fun resolveStatus(
|
|
54
|
+
activeIndex: Int,
|
|
55
|
+
currentIndex: Int,
|
|
56
|
+
error: Boolean,
|
|
57
|
+
disabled: Boolean,
|
|
58
|
+
): StepStatus = when {
|
|
59
|
+
disabled -> StepStatus.DISABLED
|
|
60
|
+
error -> StepStatus.ERROR
|
|
61
|
+
activeIndex == currentIndex -> StepStatus.CURRENT
|
|
62
|
+
activeIndex > currentIndex -> StepStatus.COMPLETED
|
|
63
|
+
else -> StepStatus.INCOMPLETE
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ─── Color helpers ─────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
@Composable
|
|
69
|
+
private fun resolveIconBgAndBorder(status: StepStatus): Pair<Color, Color> {
|
|
70
|
+
val theme = AppTheme.current
|
|
71
|
+
return when (status) {
|
|
72
|
+
StepStatus.CURRENT, StepStatus.COMPLETED ->
|
|
73
|
+
theme.colors.primary to theme.colors.background.tonal
|
|
74
|
+
StepStatus.ERROR ->
|
|
75
|
+
theme.colors.error.primary to theme.colors.error.container
|
|
76
|
+
StepStatus.DISABLED ->
|
|
77
|
+
Colors.pink_08 to Colors.pink_10
|
|
78
|
+
StepStatus.INCOMPLETE ->
|
|
79
|
+
Colors.black_06 to theme.colors.border.default
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Composable
|
|
84
|
+
private fun resolveTitleColor(status: StepStatus): Color {
|
|
85
|
+
val theme = AppTheme.current
|
|
86
|
+
return when (status) {
|
|
87
|
+
StepStatus.CURRENT -> theme.colors.primary
|
|
88
|
+
StepStatus.ERROR -> theme.colors.error.primary
|
|
89
|
+
StepStatus.DISABLED -> theme.colors.text.disable
|
|
90
|
+
else -> theme.colors.text.default
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ─── Step bubble ───────────────────────────────────────────────────────────────
|
|
95
|
+
|
|
96
|
+
@Composable
|
|
97
|
+
private fun StepBubble(
|
|
98
|
+
index: Int,
|
|
99
|
+
isActive: Boolean,
|
|
100
|
+
error: Boolean,
|
|
101
|
+
size: StepsSize,
|
|
102
|
+
status: StepStatus,
|
|
103
|
+
useNumber: Boolean,
|
|
104
|
+
customIcon: String?,
|
|
105
|
+
modifier: Modifier = Modifier,
|
|
106
|
+
) {
|
|
107
|
+
val bubbleSize: Dp = if (size == StepsSize.SMALL) 16.dp else 24.dp
|
|
108
|
+
val innerSize: Dp = if (size == StepsSize.SMALL) 12.dp else 16.dp
|
|
109
|
+
val dotSize: Dp = if (size == StepsSize.SMALL) 6.dp else 8.dp
|
|
110
|
+
|
|
111
|
+
val (bgColor, borderColor) = resolveIconBgAndBorder(status)
|
|
112
|
+
|
|
113
|
+
Box(
|
|
114
|
+
modifier = modifier
|
|
115
|
+
.size(bubbleSize)
|
|
116
|
+
.background(color = bgColor, shape = CircleShape)
|
|
117
|
+
.border(width = 2.dp, color = borderColor, shape = CircleShape)
|
|
118
|
+
.conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) },
|
|
119
|
+
contentAlignment = Alignment.Center,
|
|
120
|
+
) {
|
|
121
|
+
when {
|
|
122
|
+
useNumber -> {
|
|
123
|
+
Text(
|
|
124
|
+
text = (index + 1).toString(),
|
|
125
|
+
style = Typography.headerXsSemibold,
|
|
126
|
+
color = Colors.black_01,
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
isActive -> {
|
|
130
|
+
if (!customIcon.isNullOrEmpty()) {
|
|
131
|
+
Icon(
|
|
132
|
+
source = customIcon,
|
|
133
|
+
size = innerSize,
|
|
134
|
+
color = Colors.black_01,
|
|
135
|
+
)
|
|
136
|
+
} else {
|
|
137
|
+
// current-step dot (white circle matching RN currIcon style)
|
|
138
|
+
Box(
|
|
139
|
+
modifier = Modifier
|
|
140
|
+
.size(dotSize)
|
|
141
|
+
.background(color = Colors.black_01, shape = CircleShape),
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else -> {
|
|
146
|
+
Icon(
|
|
147
|
+
source = if (error) "navigation_close" else "notifications_check",
|
|
148
|
+
size = innerSize,
|
|
149
|
+
color = Colors.black_01,
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ─── Horizontal layout ─────────────────────────────────────────────────────────
|
|
157
|
+
|
|
158
|
+
@Composable
|
|
159
|
+
private fun StepsHorizontal(
|
|
160
|
+
steps: List<StepItem>,
|
|
161
|
+
activeIndex: Int,
|
|
162
|
+
size: StepsSize,
|
|
163
|
+
useNumber: Boolean,
|
|
164
|
+
align: StepsAlign,
|
|
165
|
+
customIcon: String?,
|
|
166
|
+
disabled: Boolean,
|
|
167
|
+
onPress: ((StepItem, Int) -> Unit)?,
|
|
168
|
+
) {
|
|
169
|
+
val theme = AppTheme.current
|
|
170
|
+
// completed-line colour is primary at 20 % opacity (mirrors RN primary + '33')
|
|
171
|
+
val completedLine = remember(theme.colors.primary) { theme.colors.primary.copy(alpha = 0.2f) }
|
|
172
|
+
val defaultLine = theme.colors.background.default
|
|
173
|
+
|
|
174
|
+
Row(modifier = Modifier.fillMaxWidth()) {
|
|
175
|
+
steps.forEachIndexed { index, item ->
|
|
176
|
+
val status = resolveStatus(activeIndex, index, item.error, disabled)
|
|
177
|
+
val isActiveOrError = status == StepStatus.CURRENT || status == StepStatus.ERROR
|
|
178
|
+
|
|
179
|
+
// Horizontal always uses the xs/description typography scale (checkSizeAndStepType=true)
|
|
180
|
+
val currentTitleStyle = if (isActiveOrError)
|
|
181
|
+
Typography.headerXsSemibold else Typography.descriptionDefaultRegular
|
|
182
|
+
val currentDescStyle = if (size == StepsSize.SMALL)
|
|
183
|
+
Typography.descriptionXsRegular else Typography.descriptionDefaultRegular
|
|
184
|
+
|
|
185
|
+
val titleColor = resolveTitleColor(status)
|
|
186
|
+
val subColor = if (disabled) theme.colors.text.disable else theme.colors.text.hint
|
|
187
|
+
|
|
188
|
+
val colAlign = when (align) {
|
|
189
|
+
StepsAlign.LEFT -> Alignment.Start
|
|
190
|
+
StepsAlign.RIGHT -> Alignment.End
|
|
191
|
+
StepsAlign.STRETCH -> when (index) {
|
|
192
|
+
0 -> Alignment.Start
|
|
193
|
+
steps.size - 1 -> Alignment.End
|
|
194
|
+
else -> Alignment.CenterHorizontally
|
|
195
|
+
}
|
|
196
|
+
StepsAlign.CENTER -> Alignment.CenterHorizontally
|
|
197
|
+
}
|
|
198
|
+
val textAlign = when (align) {
|
|
199
|
+
StepsAlign.LEFT -> TextAlign.Start
|
|
200
|
+
StepsAlign.RIGHT -> TextAlign.End
|
|
201
|
+
StepsAlign.STRETCH -> when (index) {
|
|
202
|
+
0 -> TextAlign.Start
|
|
203
|
+
steps.size - 1 -> TextAlign.End
|
|
204
|
+
else -> TextAlign.Center
|
|
205
|
+
}
|
|
206
|
+
StepsAlign.CENTER -> TextAlign.Center
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
val hideLineLeft = align == StepsAlign.LEFT ||
|
|
210
|
+
(align == StepsAlign.STRETCH && index == 0)
|
|
211
|
+
val hideLineRight = align == StepsAlign.RIGHT ||
|
|
212
|
+
(align == StepsAlign.STRETCH && index == steps.size - 1)
|
|
213
|
+
|
|
214
|
+
val lineLeft = when {
|
|
215
|
+
index == 0 -> Color.Transparent
|
|
216
|
+
activeIndex >= index -> completedLine
|
|
217
|
+
else -> defaultLine
|
|
218
|
+
}
|
|
219
|
+
val lineRight = when {
|
|
220
|
+
index == steps.size - 1 -> Color.Transparent
|
|
221
|
+
activeIndex > index -> completedLine
|
|
222
|
+
else -> defaultLine
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
val clickable = status == StepStatus.COMPLETED && onPress != null && !disabled
|
|
226
|
+
|
|
227
|
+
Column(
|
|
228
|
+
modifier = Modifier
|
|
229
|
+
.weight(1f)
|
|
230
|
+
.then(
|
|
231
|
+
if (clickable) Modifier.activeOpacityClickable { onPress?.invoke(item, index) }
|
|
232
|
+
else Modifier
|
|
233
|
+
),
|
|
234
|
+
horizontalAlignment = colAlign,
|
|
235
|
+
) {
|
|
236
|
+
// time label shown above the icon row
|
|
237
|
+
if (!item.time.isNullOrEmpty()) {
|
|
238
|
+
Text(
|
|
239
|
+
text = item.time,
|
|
240
|
+
style = Typography.descriptionXsRegular,
|
|
241
|
+
color = subColor,
|
|
242
|
+
textAlign = textAlign,
|
|
243
|
+
)
|
|
244
|
+
} else {
|
|
245
|
+
Spacer(modifier = Modifier.height(0.dp))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// icon row with horizontal connector lines
|
|
249
|
+
Row(
|
|
250
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
251
|
+
modifier = Modifier.fillMaxWidth(),
|
|
252
|
+
) {
|
|
253
|
+
if (!hideLineLeft) {
|
|
254
|
+
Box(
|
|
255
|
+
modifier = Modifier
|
|
256
|
+
.weight(1f)
|
|
257
|
+
.height(2.dp)
|
|
258
|
+
.background(
|
|
259
|
+
color = lineLeft,
|
|
260
|
+
shape = RoundedCornerShape(
|
|
261
|
+
topEnd = Radius.XS,
|
|
262
|
+
bottomEnd = Radius.XS,
|
|
263
|
+
),
|
|
264
|
+
),
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
StepBubble(
|
|
268
|
+
index = index,
|
|
269
|
+
isActive = activeIndex == index,
|
|
270
|
+
error = item.error,
|
|
271
|
+
size = size,
|
|
272
|
+
status = status,
|
|
273
|
+
useNumber = useNumber,
|
|
274
|
+
customIcon = customIcon,
|
|
275
|
+
modifier = Modifier.padding(
|
|
276
|
+
start = if (!hideLineLeft) Spacing.XS else 0.dp,
|
|
277
|
+
end = if (!hideLineRight) Spacing.XS else 0.dp,
|
|
278
|
+
top = Spacing.XS,
|
|
279
|
+
bottom = Spacing.XS,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
if (!hideLineRight) {
|
|
283
|
+
Box(
|
|
284
|
+
modifier = Modifier
|
|
285
|
+
.weight(1f)
|
|
286
|
+
.height(2.dp)
|
|
287
|
+
.background(
|
|
288
|
+
color = lineRight,
|
|
289
|
+
shape = RoundedCornerShape(
|
|
290
|
+
topStart = Radius.XS,
|
|
291
|
+
bottomStart = Radius.XS,
|
|
292
|
+
),
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (item.title.isNotEmpty()) {
|
|
299
|
+
Text(
|
|
300
|
+
text = item.title,
|
|
301
|
+
style = currentTitleStyle,
|
|
302
|
+
color = titleColor,
|
|
303
|
+
textAlign = textAlign,
|
|
304
|
+
modifier = Modifier.padding(bottom = Spacing.XXS),
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!item.description.isNullOrEmpty()) {
|
|
309
|
+
Text(
|
|
310
|
+
text = item.description,
|
|
311
|
+
style = currentDescStyle,
|
|
312
|
+
color = subColor,
|
|
313
|
+
textAlign = textAlign,
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ─── Vertical layout ───────────────────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
@Composable
|
|
324
|
+
private fun StepsVertical(
|
|
325
|
+
steps: List<StepItem>,
|
|
326
|
+
activeIndex: Int,
|
|
327
|
+
size: StepsSize,
|
|
328
|
+
useNumber: Boolean,
|
|
329
|
+
customIcon: String?,
|
|
330
|
+
disabled: Boolean,
|
|
331
|
+
onPress: ((StepItem, Int) -> Unit)?,
|
|
332
|
+
) {
|
|
333
|
+
val theme = AppTheme.current
|
|
334
|
+
val completedLine = remember(theme.colors.primary) { theme.colors.primary.copy(alpha = 0.2f) }
|
|
335
|
+
val defaultLine = theme.colors.background.default
|
|
336
|
+
val useSmall = size == StepsSize.SMALL
|
|
337
|
+
// Fixed connector height; grows proportionally with content in practice
|
|
338
|
+
val connectorHeight: Dp = if (useSmall) 24.dp else 32.dp
|
|
339
|
+
|
|
340
|
+
Column(modifier = Modifier.fillMaxWidth()) {
|
|
341
|
+
steps.forEachIndexed { index, item ->
|
|
342
|
+
val status = resolveStatus(activeIndex, index, item.error, disabled)
|
|
343
|
+
val isActiveOrError = status == StepStatus.CURRENT || status == StepStatus.ERROR
|
|
344
|
+
val isLast = index == steps.size - 1
|
|
345
|
+
|
|
346
|
+
// Vertical large: header_s / body; vertical small: header_xs / description
|
|
347
|
+
val currentTitleStyle = if (isActiveOrError) {
|
|
348
|
+
if (useSmall) Typography.headerXsSemibold else Typography.headerSSemibold
|
|
349
|
+
} else {
|
|
350
|
+
if (useSmall) Typography.descriptionDefaultRegular else Typography.bodyDefaultRegular
|
|
351
|
+
}
|
|
352
|
+
val currentDescStyle = if (useSmall)
|
|
353
|
+
Typography.descriptionXsRegular else Typography.descriptionDefaultRegular
|
|
354
|
+
|
|
355
|
+
val titleColor = resolveTitleColor(status)
|
|
356
|
+
val subColor = if (disabled) theme.colors.text.disable else theme.colors.text.hint
|
|
357
|
+
val lineColor = if (activeIndex > index) completedLine else defaultLine
|
|
358
|
+
|
|
359
|
+
val clickable = status == StepStatus.COMPLETED && onPress != null && !disabled
|
|
360
|
+
|
|
361
|
+
Row(
|
|
362
|
+
modifier = Modifier
|
|
363
|
+
.fillMaxWidth()
|
|
364
|
+
.then(
|
|
365
|
+
if (clickable) Modifier.activeOpacityClickable { onPress?.invoke(item, index) }
|
|
366
|
+
else Modifier
|
|
367
|
+
),
|
|
368
|
+
) {
|
|
369
|
+
// Left column: bubble + vertical connector line
|
|
370
|
+
Column(
|
|
371
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
372
|
+
modifier = Modifier.padding(end = Spacing.M),
|
|
373
|
+
) {
|
|
374
|
+
StepBubble(
|
|
375
|
+
index = index,
|
|
376
|
+
isActive = activeIndex == index,
|
|
377
|
+
error = item.error,
|
|
378
|
+
size = size,
|
|
379
|
+
status = status,
|
|
380
|
+
useNumber = useNumber,
|
|
381
|
+
customIcon = customIcon,
|
|
382
|
+
)
|
|
383
|
+
if (!isLast) {
|
|
384
|
+
Box(
|
|
385
|
+
modifier = Modifier
|
|
386
|
+
.width(2.dp)
|
|
387
|
+
.height(connectorHeight)
|
|
388
|
+
.padding(vertical = Spacing.XS)
|
|
389
|
+
.background(color = lineColor),
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Right column: title + time on the same row, description below
|
|
395
|
+
Column(
|
|
396
|
+
modifier = Modifier
|
|
397
|
+
.weight(1f)
|
|
398
|
+
.padding(bottom = if (isLast) 0.dp else Spacing.S),
|
|
399
|
+
) {
|
|
400
|
+
Row(
|
|
401
|
+
modifier = Modifier.fillMaxWidth(),
|
|
402
|
+
horizontalArrangement = Arrangement.SpaceBetween,
|
|
403
|
+
verticalAlignment = Alignment.Top,
|
|
404
|
+
) {
|
|
405
|
+
Text(
|
|
406
|
+
text = item.title,
|
|
407
|
+
style = currentTitleStyle,
|
|
408
|
+
color = titleColor,
|
|
409
|
+
maxLines = 2,
|
|
410
|
+
modifier = Modifier
|
|
411
|
+
.weight(1f)
|
|
412
|
+
.padding(end = Spacing.S),
|
|
413
|
+
)
|
|
414
|
+
if (!item.time.isNullOrEmpty()) {
|
|
415
|
+
Text(
|
|
416
|
+
text = item.time,
|
|
417
|
+
style = Typography.descriptionXsRegular,
|
|
418
|
+
color = subColor,
|
|
419
|
+
)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (!item.description.isNullOrEmpty()) {
|
|
423
|
+
Spacer(modifier = Modifier.height(Spacing.XXS))
|
|
424
|
+
Text(
|
|
425
|
+
text = item.description,
|
|
426
|
+
style = currentDescStyle,
|
|
427
|
+
color = subColor,
|
|
428
|
+
)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ─── Public API ────────────────────────────────────────────────────────────────
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Steps — guided progress indicator through a sequence of procedural steps.
|
|
440
|
+
*
|
|
441
|
+
* Mirrors the React Native `Steps` component from `@momo-kits/step`.
|
|
442
|
+
*
|
|
443
|
+
* @param steps Ordered list of [StepItem] values to display.
|
|
444
|
+
* @param activeIndex Zero-based index of the currently active step.
|
|
445
|
+
* @param modifier Modifier applied to the outermost container.
|
|
446
|
+
* @param horizontal `true` renders a horizontal layout; defaults to vertical.
|
|
447
|
+
* @param size [StepsSize.SMALL] or [StepsSize.LARGE] — affects bubble and typography scale.
|
|
448
|
+
* @param useNumber Shows the 1-based step number inside the bubble instead of a check/dot icon.
|
|
449
|
+
* @param align Text and icon alignment in horizontal mode: LEFT, RIGHT, CENTER, or STRETCH.
|
|
450
|
+
* @param customIcon Icon source name rendered inside the active-step bubble (replaces the dot).
|
|
451
|
+
* @param disabled Dims all steps and suppresses all press callbacks.
|
|
452
|
+
* @param onPress Invoked with the tapped [StepItem] and its index; only fires for completed steps.
|
|
453
|
+
*/
|
|
454
|
+
@Composable
|
|
455
|
+
fun Steps(
|
|
456
|
+
steps: List<StepItem>,
|
|
457
|
+
activeIndex: Int,
|
|
458
|
+
modifier: Modifier = Modifier,
|
|
459
|
+
horizontal: Boolean = false,
|
|
460
|
+
size: StepsSize = StepsSize.LARGE,
|
|
461
|
+
useNumber: Boolean = false,
|
|
462
|
+
align: StepsAlign = StepsAlign.CENTER,
|
|
463
|
+
customIcon: String? = null,
|
|
464
|
+
disabled: Boolean = false,
|
|
465
|
+
onPress: ((StepItem, Int) -> Unit)? = null,
|
|
466
|
+
) {
|
|
467
|
+
Box(
|
|
468
|
+
modifier = modifier
|
|
469
|
+
.conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) },
|
|
470
|
+
) {
|
|
471
|
+
if (horizontal) {
|
|
472
|
+
StepsHorizontal(
|
|
473
|
+
steps = steps,
|
|
474
|
+
activeIndex = activeIndex,
|
|
475
|
+
size = size,
|
|
476
|
+
useNumber = useNumber,
|
|
477
|
+
align = align,
|
|
478
|
+
customIcon = customIcon,
|
|
479
|
+
disabled = disabled,
|
|
480
|
+
onPress = onPress,
|
|
481
|
+
)
|
|
482
|
+
} else {
|
|
483
|
+
StepsVertical(
|
|
484
|
+
steps = steps,
|
|
485
|
+
activeIndex = activeIndex,
|
|
486
|
+
size = size,
|
|
487
|
+
useNumber = useNumber,
|
|
488
|
+
customIcon = customIcon,
|
|
489
|
+
disabled = disabled,
|
|
490
|
+
onPress = onPress,
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.AnimatedVisibility
|
|
4
|
+
import androidx.compose.animation.fadeIn
|
|
5
|
+
import androidx.compose.animation.fadeOut
|
|
6
|
+
import androidx.compose.foundation.background
|
|
7
|
+
import androidx.compose.foundation.border
|
|
8
|
+
import androidx.compose.foundation.layout.Row
|
|
9
|
+
import androidx.compose.foundation.layout.Spacer
|
|
10
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
+
import androidx.compose.foundation.layout.padding
|
|
12
|
+
import androidx.compose.foundation.layout.size
|
|
13
|
+
import androidx.compose.foundation.layout.width
|
|
14
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
15
|
+
import androidx.compose.runtime.Composable
|
|
16
|
+
import androidx.compose.runtime.remember
|
|
17
|
+
import androidx.compose.ui.Alignment
|
|
18
|
+
import androidx.compose.ui.Modifier
|
|
19
|
+
import androidx.compose.ui.draw.clip
|
|
20
|
+
import androidx.compose.ui.text.SpanStyle
|
|
21
|
+
import androidx.compose.ui.text.buildAnnotatedString
|
|
22
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
23
|
+
import androidx.compose.ui.text.withStyle
|
|
24
|
+
import androidx.compose.ui.unit.dp
|
|
25
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
26
|
+
import vn.momo.kits.const.AppTheme
|
|
27
|
+
import vn.momo.kits.const.Colors
|
|
28
|
+
import vn.momo.kits.const.Radius
|
|
29
|
+
import vn.momo.kits.const.Spacing
|
|
30
|
+
import vn.momo.kits.const.Typography
|
|
31
|
+
import vn.momo.kits.modifier.activeOpacityClickable
|
|
32
|
+
import vn.momo.kits.modifier.conditional
|
|
33
|
+
import vn.momo.kits.modifier.shadow
|
|
34
|
+
|
|
35
|
+
@Composable
|
|
36
|
+
fun SuggestAction(
|
|
37
|
+
message: String,
|
|
38
|
+
buttonTitle: String,
|
|
39
|
+
boldMessage: String = "",
|
|
40
|
+
image: String = "",
|
|
41
|
+
visible: Boolean = false,
|
|
42
|
+
onClose: () -> Unit = {},
|
|
43
|
+
onPressButton: () -> Unit = {},
|
|
44
|
+
modifier: Modifier = Modifier,
|
|
45
|
+
) {
|
|
46
|
+
val theme = AppTheme.current
|
|
47
|
+
|
|
48
|
+
AnimatedVisibility(
|
|
49
|
+
visible = visible,
|
|
50
|
+
enter = fadeIn(),
|
|
51
|
+
exit = fadeOut(),
|
|
52
|
+
modifier = modifier,
|
|
53
|
+
) {
|
|
54
|
+
Row(
|
|
55
|
+
modifier = Modifier
|
|
56
|
+
.fillMaxWidth()
|
|
57
|
+
.shadow(
|
|
58
|
+
color = theme.colors.text.default.copy(alpha = 0.12f),
|
|
59
|
+
borderRadius = Radius.S,
|
|
60
|
+
blurRadius = 16f,
|
|
61
|
+
offsetY = 4.dp,
|
|
62
|
+
)
|
|
63
|
+
.clip(RoundedCornerShape(Radius.S))
|
|
64
|
+
.background(
|
|
65
|
+
color = theme.colors.background.surface,
|
|
66
|
+
shape = RoundedCornerShape(Radius.S),
|
|
67
|
+
)
|
|
68
|
+
.conditional(IsShowBaseLineDebug) {
|
|
69
|
+
border(1.dp, Colors.blue_03)
|
|
70
|
+
}
|
|
71
|
+
.padding(Spacing.M),
|
|
72
|
+
verticalAlignment = Alignment.CenterVertically,
|
|
73
|
+
) {
|
|
74
|
+
if (image.isNotEmpty()) {
|
|
75
|
+
Image(
|
|
76
|
+
source = image,
|
|
77
|
+
modifier = Modifier
|
|
78
|
+
.size(32.dp)
|
|
79
|
+
.clip(RoundedCornerShape(Radius.XS)),
|
|
80
|
+
)
|
|
81
|
+
Spacer(modifier = Modifier.width(Spacing.S))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
val annotatedMessage = remember(message, boldMessage) {
|
|
85
|
+
if (boldMessage.isNotEmpty()) {
|
|
86
|
+
val index = message.lowercase().indexOf(boldMessage.lowercase())
|
|
87
|
+
if (index != -1) {
|
|
88
|
+
buildAnnotatedString {
|
|
89
|
+
append(message.substring(0, index))
|
|
90
|
+
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
|
|
91
|
+
append(message.substring(index, index + boldMessage.length))
|
|
92
|
+
}
|
|
93
|
+
append(message.substring(index + boldMessage.length))
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
buildAnnotatedString { append(message) }
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
buildAnnotatedString { append(message) }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Text(
|
|
104
|
+
text = annotatedMessage,
|
|
105
|
+
style = Typography.bodyDefaultRegular,
|
|
106
|
+
modifier = Modifier
|
|
107
|
+
.weight(1f)
|
|
108
|
+
.padding(end = Spacing.M),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
Button(
|
|
112
|
+
title = buttonTitle,
|
|
113
|
+
type = ButtonType.TONAL,
|
|
114
|
+
size = Size.SMALL,
|
|
115
|
+
isFull = false,
|
|
116
|
+
onClick = {
|
|
117
|
+
onPressButton()
|
|
118
|
+
},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
Spacer(modifier = Modifier.width(Spacing.S))
|
|
122
|
+
|
|
123
|
+
Icon(
|
|
124
|
+
source = "navigation_close",
|
|
125
|
+
size = 24.dp,
|
|
126
|
+
color = theme.colors.text.default,
|
|
127
|
+
modifier = Modifier.activeOpacityClickable { onClose() },
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|