@momo-kits/native-kits 0.300.3-debug → 0.300.4-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.
Files changed (218) hide show
  1. package/CLAUDE.md +51 -7
  2. package/README.md +2 -2
  3. package/compose/build.gradle.kts +10 -2
  4. package/compose/build.gradle.kts.backup +8 -0
  5. package/compose/compose.podspec +1 -3
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -14
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
  65. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
  66. package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
  67. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
  68. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
  69. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  70. package/gradle/libs.versions.toml +3 -14
  71. package/gradle/wrapper/gradle-wrapper.properties +0 -2
  72. package/gradle.properties +3 -5
  73. package/ios/Application/ApplicationEnvironment.swift +24 -6
  74. package/ios/Application/DesignSystemKits.swift +100 -0
  75. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  76. package/ios/Application/MaxApi.swift +30 -3
  77. package/ios/Avatar/Avatar.swift +4 -3
  78. package/ios/Badge/Badge.swift +6 -4
  79. package/ios/Badge/BadgeDot.swift +1 -1
  80. package/ios/Badge/BadgeRibbon.swift +8 -2
  81. package/ios/Button/Button.swift +15 -12
  82. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
  83. package/ios/Carousel/Carousel.swift +29 -12
  84. package/ios/Checkbox/Checkbox.swift +7 -3
  85. package/ios/Chip/Chip.swift +5 -4
  86. package/ios/Collapse/Collapse.swift +70 -19
  87. package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
  88. package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
  89. package/ios/DateTimePicker/DateTimePicker.swift +54 -20
  90. package/ios/DateTimePicker/WheelPicker.swift +103 -105
  91. package/ios/Divider/Divider.swift +37 -4
  92. package/ios/Extensions/Color++.swift +38 -9
  93. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  94. package/ios/Extensions/SkeletonUICompat.swift +11 -0
  95. package/ios/Icon/Icon.swift +21 -23
  96. package/ios/Icon/Icons.swift +1329 -0
  97. package/ios/IconButton/IconButton.swift +15 -7
  98. package/ios/Image/Image.swift +7 -23
  99. package/ios/Information/Information.swift +27 -9
  100. package/ios/Input/ErrorView.swift +2 -2
  101. package/ios/Input/Input.swift +171 -148
  102. package/ios/Input/InputPhoneNumber.swift +10 -9
  103. package/ios/Input/InputSearch.swift +107 -78
  104. package/ios/Input/InputTextArea.swift +133 -148
  105. package/ios/InputDropDown/InputDropDown.swift +10 -7
  106. package/ios/InputMoney/InputMoney.swift +32 -11
  107. package/ios/InputOTP/InputOTP.swift +22 -12
  108. package/ios/Layout/Card.swift +50 -0
  109. package/ios/Layout/GridContext.swift +187 -3
  110. package/ios/Layout/Item.swift +5 -4
  111. package/ios/Layout/ScreenLayout.swift +415 -0
  112. package/ios/Layout/Section.swift +4 -100
  113. package/ios/Loader/Loader.swift +5 -9
  114. package/ios/Lottie/LottieView.swift +14 -2
  115. package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
  116. package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
  117. package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
  118. package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
  119. package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
  120. package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
  121. package/ios/Navigation/Component/Header.swift +545 -0
  122. package/ios/Navigation/Component/HeaderBackground.swift +154 -0
  123. package/ios/Navigation/Component/HeaderJourney.swift +73 -0
  124. package/ios/Navigation/Component/HeaderLocation.swift +48 -0
  125. package/ios/Navigation/Component/HeaderRight.swift +425 -0
  126. package/ios/Navigation/Component/HeaderTitle.swift +36 -0
  127. package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
  128. package/ios/Navigation/Component/SideBar.swift +183 -0
  129. package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
  130. package/ios/Navigation/HostBackGestureBridge.swift +78 -0
  131. package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
  132. package/ios/Navigation/Navigation.swift +204 -0
  133. package/ios/Navigation/NavigationContainer.swift +195 -0
  134. package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
  135. package/ios/Navigation/StackScreen.swift +930 -0
  136. package/ios/Navigation/StackTransition.swift +400 -0
  137. package/ios/Navigation/SwipeBackHaptic.swift +33 -0
  138. package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
  139. package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
  140. package/ios/Pagination/PaginationScroll.swift +2 -2
  141. package/ios/Popup/PopupDisplay.swift +72 -135
  142. package/ios/Popup/PopupInput.swift +10 -13
  143. package/ios/Popup/PopupLayout.swift +138 -0
  144. package/ios/Popup/PopupNotify.swift +59 -69
  145. package/ios/Popup/PopupPromotion.swift +31 -47
  146. package/ios/PopupView/FullscreenPopup.swift +13 -19
  147. package/ios/PopupView/PopupView.swift +1 -1
  148. package/ios/PopupView/Utils++.swift +0 -8
  149. package/ios/ProgressInfo/ProgressInfo.swift +4 -2
  150. package/ios/Radio/Radio.swift +2 -1
  151. package/ios/Rating/Rating.swift +9 -1
  152. package/ios/Resources/dot_loading.json +558 -0
  153. package/ios/Resources/lottie_circle_loader.json +1 -0
  154. package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
  155. package/ios/Skeleton/Skeleton.swift +22 -27
  156. package/ios/Slider/Slider.swift +16 -18
  157. package/ios/Stepper/Stepper.swift +7 -3
  158. package/ios/Steps/Steps.swift +8 -6
  159. package/ios/SuggestAction/SuggestAction.swift +5 -0
  160. package/ios/Swipeable/SwipeCell.swift +10 -6
  161. package/ios/Switch/Switch.swift +22 -20
  162. package/ios/TabView/TabView.swift +102 -21
  163. package/ios/Tag/Tag.swift +15 -4
  164. package/ios/Template/Logo/Logo.swift +2 -1
  165. package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
  166. package/ios/Title/Title.swift +6 -17
  167. package/ios/Tooltip/Tooltip.swift +29 -14
  168. package/ios/Typography/FontScale.swift +17 -14
  169. package/ios/Typography/Text.swift +156 -20
  170. package/ios/Typography/Typography.swift +2 -2
  171. package/ios/Uploader/Uploader.swift +3 -1
  172. package/ios/native-kits.podspec +2 -3
  173. package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
  182. package/package.json +1 -1
  183. package/publish.sh +19 -2
  184. package/scripts/gen-ios-icons.mjs +36 -0
  185. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  186. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  187. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  188. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  189. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  190. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  191. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  192. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  193. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  194. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  195. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  196. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  197. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  198. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  199. package/ios/Application/Components.swift +0 -270
  200. package/ios/Application/ComposeApi.swift +0 -22
  201. package/ios/Application/HeaderRight.swift +0 -306
  202. package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
  203. package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
  204. package/ios/Application/Navigation/HeaderTitle.swift +0 -57
  205. package/ios/Application/Navigation/NavigationContainer.swift +0 -213
  206. package/ios/Application/Navigation/NavigationOptions.swift +0 -109
  207. package/ios/Application/Navigation/NavigatorManager.swift +0 -22
  208. package/ios/Application/Navigation/component/Header.swift +0 -178
  209. package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
  210. package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
  211. package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
  212. package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
  213. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
  214. package/ios/Application/Screen.swift +0 -250
  215. package/ios/Application/StackScreen.swift +0 -492
  216. package/ios/Typography/FontScaleStore.swift +0 -58
  217. package/ios/Typography/KitDesignSystem.swift +0 -17
  218. /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
@@ -43,9 +43,11 @@ private fun getValueForPosition(
43
43
  step: Float,
44
44
  ): Float {
45
45
  val availableSpace = containerWidth - thumbWidth
46
- if (availableSpace <= 0f) return min
47
- val relStepUnit = step / (max - min)
46
+ if (availableSpace <= 0f || max <= min) return min
48
47
  var relPosition = (positionInView - thumbWidth / 2f) / availableSpace
48
+ // A zero step would make the snap math NaN, and NaN.roundToInt() throws.
49
+ if (step <= 0f) return clamp(min + relPosition * (max - min), min, max)
50
+ val relStepUnit = step / (max - min)
49
51
  val relOffset = relPosition % relStepUnit
50
52
  relPosition -= relOffset
51
53
  if (relOffset / relStepUnit >= 0.5f) relPosition += relStepUnit
@@ -131,6 +133,11 @@ fun Slider(
131
133
  var draggingLow by remember { mutableStateOf(true) }
132
134
  var isDragging by remember { mutableStateOf(false) }
133
135
 
136
+ // pointerInput is not keyed on the callbacks, so read their latest values.
137
+ val currentOnValueChanged by rememberUpdatedState(onValueChanged)
138
+ val currentOnTouchStart by rememberUpdatedState(onSliderTouchStart)
139
+ val currentOnTouchEnd by rememberUpdatedState(onSliderTouchEnd)
140
+
134
141
  val thumbColor = if (disabled) theme.colors.text.disable else theme.colors.primary
135
142
  val trackActiveColor = if (disabled) theme.colors.text.disable else theme.colors.primary
136
143
  val trackInactiveColor = theme.colors.background.default
@@ -167,7 +174,7 @@ fun Slider(
167
174
 
168
175
  draggingLow = disableRange || isLowCloser(touchX, lowCenterX, highCenterX)
169
176
  isDragging = true
170
- onSliderTouchStart(lowValue, highValue)
177
+ currentOnTouchStart(lowValue, highValue)
171
178
 
172
179
  // Apply initial press
173
180
  applyDrag(touchX, trackWidthPx, thumbSizePx,
@@ -175,7 +182,7 @@ fun Slider(
175
182
  lowValue, highValue) { nl, nh ->
176
183
  lowValue = nl
177
184
  highValue = nh
178
- onValueChanged(nl, nh, true)
185
+ currentOnValueChanged(nl, nh, true)
179
186
  }
180
187
 
181
188
  // Track move / release
@@ -186,7 +193,7 @@ fun Slider(
186
193
 
187
194
  if (!moveChange.pressed) {
188
195
  isDragging = false
189
- onSliderTouchEnd(lowValue, highValue)
196
+ currentOnTouchEnd(lowValue, highValue)
190
197
  break
191
198
  }
192
199
 
@@ -195,7 +202,7 @@ fun Slider(
195
202
  lowValue, highValue) { nl, nh ->
196
203
  lowValue = nl
197
204
  highValue = nh
198
- onValueChanged(nl, nh, true)
205
+ currentOnValueChanged(nl, nh, true)
199
206
  }
200
207
  }
201
208
  }
@@ -8,8 +8,10 @@ import androidx.compose.foundation.gestures.detectHorizontalDragGestures
8
8
  import androidx.compose.foundation.layout.*
9
9
  import androidx.compose.foundation.shape.RoundedCornerShape
10
10
  import androidx.compose.runtime.Composable
11
+ import androidx.compose.runtime.getValue
11
12
  import androidx.compose.runtime.remember
12
13
  import androidx.compose.runtime.rememberCoroutineScope
14
+ import androidx.compose.runtime.rememberUpdatedState
13
15
  import androidx.compose.ui.Alignment
14
16
  import androidx.compose.ui.Modifier
15
17
  import androidx.compose.ui.draw.clip
@@ -62,6 +64,12 @@ fun Swipe(
62
64
 
63
65
  val offsetX = remember { Animatable(0f) }
64
66
 
67
+ // pointerInput is keyed on the action counts only, so read the latest callbacks.
68
+ val currentOnOpen by rememberUpdatedState(onSwipeableOpen)
69
+ val currentOnClose by rememberUpdatedState(onSwipeableClose)
70
+ val currentOnLeftOpen by rememberUpdatedState(onSwipeableLeftOpen)
71
+ val currentOnRightOpen by rememberUpdatedState(onSwipeableRightOpen)
72
+
65
73
  val shape = remember(itemRadius) {
66
74
  if (itemRadius > 0.dp) RoundedCornerShape(itemRadius) else RoundedCornerShape(0.dp)
67
75
  }
@@ -71,17 +79,17 @@ fun Swipe(
71
79
  val current = offsetX.value
72
80
  val target = when {
73
81
  current > leftWidthPx * 0.5f && left.isNotEmpty() -> {
74
- onSwipeableLeftOpen()
75
- onSwipeableOpen()
82
+ currentOnLeftOpen()
83
+ currentOnOpen()
76
84
  leftWidthPx
77
85
  }
78
86
  current < -rightWidthPx * 0.5f && right.isNotEmpty() -> {
79
- onSwipeableRightOpen()
80
- onSwipeableOpen()
87
+ currentOnRightOpen()
88
+ currentOnOpen()
81
89
  -rightWidthPx
82
90
  }
83
91
  else -> {
84
- onSwipeableClose()
92
+ currentOnClose()
85
93
  0f
86
94
  }
87
95
  }
@@ -43,9 +43,10 @@ fun Switch(
43
43
 
44
44
  val (circleBackgroundColor, backgroundColor) = colors
45
45
 
46
- // Memoize automation ID to avoid string concatenation on every render
46
+ // Both params default to "", so blank must fall back too or the id is always empty.
47
47
  val automationIdValue = remember(accessibilityId, title, value) {
48
- accessibilityId ?: "toggle_${title ?: "unknown"}_$value"
48
+ accessibilityId?.takeIf { it.isNotEmpty() }
49
+ ?: "toggle_${title?.takeIf { it.isNotEmpty() } ?: "unknown"}_$value"
49
50
  }
50
51
 
51
52
  // Memoize alignment calculation
@@ -10,9 +10,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
10
10
  import androidx.compose.runtime.*
11
11
  import androidx.compose.ui.Alignment
12
12
  import androidx.compose.ui.Modifier
13
+ import androidx.compose.ui.draw.clipToBounds
14
+ import androidx.compose.ui.geometry.Offset
13
15
  import androidx.compose.ui.graphics.Color
14
16
  import androidx.compose.ui.graphics.drawscope.withTransform
15
- import androidx.compose.ui.graphics.drawscope.DrawScope
16
17
  import androidx.compose.ui.graphics.Path
17
18
  import androidx.compose.ui.layout.onGloballyPositioned
18
19
  import androidx.compose.ui.layout.positionInParent
@@ -73,6 +74,8 @@ fun TabView(
73
74
  } else null
74
75
 
75
76
  val coroutineScope = rememberCoroutineScope()
77
+ // The collector below outlives recompositions; read the latest callback, not the first one.
78
+ val currentOnPageSelected by rememberUpdatedState(onPageSelected)
76
79
 
77
80
  // Keep selectedIndex in sync when user swipes the pager
78
81
  if (pagerState != null) {
@@ -80,7 +83,7 @@ fun TabView(
80
83
  snapshotFlow { pagerState.currentPage }.collect { page ->
81
84
  if (!lazyRendered.contains(page)) lazyRendered.add(page)
82
85
  selectedIndex = page
83
- onPageSelected(page)
86
+ currentOnPageSelected(page)
84
87
  }
85
88
  }
86
89
  }
@@ -362,7 +365,7 @@ private fun CardTabBar(
362
365
  }
363
366
  }
364
367
 
365
- val active = tabs[selectedIndex]
368
+ val active = tabs.getOrNull(selectedIndex) ?: return@BoxWithConstraints
366
369
  Row(
367
370
  modifier = Modifier
368
371
  // Pinned to the strip's BOTTOM edge so the two always end on the same
@@ -415,7 +418,8 @@ private val CARD_TAB_HEIGHT = 40.dp
415
418
  private val CARD_TAB_ACTIVE_HEIGHT = 44.dp
416
419
  @Composable
417
420
  private fun CardTabEar(width: Dp, mirrored: Boolean, color: Color, nudge: Dp) {
418
- Canvas(modifier = Modifier.offset(x = nudge).width(width).height(CARD_TAB_ACTIVE_HEIGHT)) {
421
+ // Clipped like react-kits' Svg, whose 28-wide path is cut at a 22pt `pathWidth`.
422
+ Canvas(modifier = Modifier.offset(x = nudge).width(width).height(CARD_TAB_ACTIVE_HEIGHT).clipToBounds()) {
419
423
  val path = Path().apply {
420
424
  moveTo(0f, 0f)
421
425
  lineTo(0.60765f, 0f)
@@ -425,8 +429,12 @@ private fun CardTabEar(width: Dp, mirrored: Boolean, color: Color, nudge: Dp) {
425
429
  lineTo(0f, 44f)
426
430
  close()
427
431
  }
428
- val draw: DrawScope.() -> Unit = { drawPath(path = path, color = color) }
429
- if (mirrored) withTransform({ scale(-1f, 1f) }) { draw() } else draw()
432
+ // Path coordinates are dp (react-kits viewBox), but DrawScope draws in px.
433
+ val pxPerDp = density
434
+ withTransform({
435
+ if (mirrored) scale(-1f, 1f)
436
+ scale(pxPerDp, pxPerDp, pivot = Offset.Zero)
437
+ }) { drawPath(path = path, color = color) }
430
438
  }
431
439
  }
432
440
 
@@ -30,6 +30,22 @@ enum class TagColor(val backgroundColor: Color, val textColor: Color) {
30
30
  Grey(Colors.black_04, Colors.black_12),
31
31
  }
32
32
 
33
+ // RN only accepts the `_03` palette as a custom tag color.
34
+ private val tagCustomColors = listOf(
35
+ Colors.black_03,
36
+ Colors.pink_03,
37
+ Colors.violet_03,
38
+ Colors.indigo_03,
39
+ Colors.blue_03,
40
+ Colors.mint_03,
41
+ Colors.green_03,
42
+ Colors.lime_03,
43
+ Colors.yellow_03,
44
+ Colors.gold_03,
45
+ Colors.orange_03,
46
+ Colors.red_03,
47
+ )
48
+
33
49
  @Composable
34
50
  fun Tag(
35
51
  label: String = "Label",
@@ -38,27 +54,9 @@ fun Tag(
38
54
  size: TagSize = TagSize.Large,
39
55
  customColor: Color? = null,
40
56
  ) {
41
- val primaryColors = listOf(
42
- Color(0xFFF0F0F0),
43
- Color(0xFFEB2F96),
44
- Color(0xFF962AF0),
45
- Color(0xFF4E4BFF),
46
- Color(0xFF007AFF),
47
- Color(0xFF13C2C2),
48
- Color(0xFF34C759),
49
- Color(0xFFA0D911),
50
- Color(0xFFFFCC00),
51
- Color(0xFFFA8C16),
52
- Color(0xFFFA541C),
53
- Color(0xFFF5222D)
54
- )
55
- var tagColor = color.backgroundColor
56
- var labelColor = color.textColor
57
-
58
- if (primaryColors.contains(customColor)) {
59
- tagColor = color.backgroundColor
60
- labelColor = color.textColor
61
- }
57
+ val custom = customColor?.takeIf { it in tagCustomColors }
58
+ val tagColor = custom ?: color.backgroundColor
59
+ val labelColor = if (custom != null) Colors.black_01 else color.textColor
62
60
 
63
61
  Box(
64
62
  modifier = Modifier
@@ -87,8 +87,8 @@ fun Text(
87
87
  color ?: theme.colors.text.default
88
88
  }
89
89
 
90
- // Memoize style copy operation
91
- val optimizedStyle = remember(style) {
90
+ // lineHeight is a key: it is scaled, so it changes with the font scale even when style doesn't.
91
+ val optimizedStyle = remember(style, lineHeight) {
92
92
  style.copy(
93
93
  fontWeight = null,
94
94
  lineHeight = if (lineHeight.value.isFinite() && lineHeight.value > 0) lineHeight else style.lineHeight
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.*
21
21
  import androidx.compose.ui.window.Popup
22
22
  import androidx.compose.ui.window.PopupPositionProvider
23
23
  import androidx.compose.ui.window.PopupProperties
24
+ import kotlinx.coroutines.flow.drop
24
25
  import vn.momo.kits.application.IsShowBaseLineDebug
25
26
  import vn.momo.kits.const.*
26
27
  import vn.momo.kits.modifier.activeOpacityClickable
@@ -147,8 +148,10 @@ fun Tooltip(
147
148
  val density = LocalDensity.current
148
149
  val offsetPx = with(density) { (TOOLTIP_OFFSET + ARROW_SIZE).roundToPx() }
149
150
 
150
- LaunchedEffect(state.isVisible) {
151
- onVisibleChange?.invoke(state.isVisible)
151
+ val currentOnVisibleChange by rememberUpdatedState(onVisibleChange)
152
+ LaunchedEffect(state) {
153
+ // changes only, like RN: the initial value is not reported
154
+ snapshotFlow { state.isVisible }.drop(1).collect { currentOnVisibleChange?.invoke(it) }
152
155
  }
153
156
 
154
157
  Box(modifier = modifier) {
@@ -128,11 +128,13 @@ fun getHours(hourMode: Int): List<String> {
128
128
  * Get a list of minutes
129
129
  */
130
130
  fun getMinutes(interval: Int): List<String> {
131
+ // A non-positive interval would never reach 60.
132
+ val step = interval.coerceAtLeast(1)
131
133
  val minutes = mutableListOf<String>()
132
134
  var min = 0
133
135
  while (min < 60) {
134
136
  minutes.add(paddingNum(min))
135
- min += interval
137
+ min += step
136
138
  }
137
139
  return minutes
138
140
  }
@@ -154,22 +154,14 @@ internal fun WheelPickerItem(
154
154
  .fillMaxWidth()
155
155
  .height(itemHeight)
156
156
  .graphicsLayer {
157
- val firstVisibleIndex = listState.firstVisibleItemIndex
158
- val visibleItemOffset = listState.firstVisibleItemScrollOffset
159
-
160
- val viewportHeight = 5 * itemHeight.value
161
- val itemHeightValue = itemHeight.value
162
-
163
- val absoluteOffset =
164
- (index - firstVisibleIndex) * itemHeightValue - (visibleItemOffset / itemHeightValue)
165
- val relativePosition = absoluteOffset / viewportHeight
166
-
167
- val distance = abs(2 - relativePosition)
168
-
157
+ // Rows away from the centre slot (two below the first visible row); RN fades 1 → 0.8 → 0.4.
158
+ val distance = abs(
159
+ index - listState.firstVisibleItemIndex -
160
+ listState.firstVisibleItemScrollOffset / itemHeight.toPx() - 2
161
+ )
169
162
  alpha = when {
170
- distance <= 1 -> 1f
171
- distance <= 2 -> 0.8f
172
- distance <= 3 -> 0.4f
163
+ distance <= 1f -> 1f - 0.2f * distance
164
+ distance <= 2f -> 0.8f - 0.4f * (distance - 1f)
173
165
  else -> 0.4f
174
166
  }
175
167
  },
@@ -2,6 +2,7 @@ package vn.momo.kits.const
2
2
 
3
3
  import androidx.compose.runtime.Composable
4
4
  import androidx.compose.ui.text.font.FontFamily
5
+ import androidx.compose.ui.text.font.FontWeight
5
6
 
6
7
  /**
7
8
  * Nạp font từ tài nguyên native của app host thay vì đóng gói bản copy riêng trong SDK.
@@ -13,4 +14,4 @@ import androidx.compose.ui.text.font.FontFamily
13
14
  * Trả về `null` nếu host không có font đó — caller tự fallback [FontFamily.Default].
14
15
  */
15
16
  @Composable
16
- internal expect fun hostFontFamily(fileName: String): FontFamily?
17
+ internal expect fun hostFontFamily(fileName: String, weight: FontWeight): FontFamily?
@@ -8,6 +8,7 @@ import androidx.compose.ui.text.font.FontFamily
8
8
  import androidx.compose.ui.text.font.FontWeight
9
9
  import androidx.compose.ui.text.style.TextDecoration
10
10
  import androidx.compose.ui.unit.*
11
+ import vn.momo.kits.application.DesignSystemKits
11
12
  import vn.momo.kits.application.LocalContext
12
13
  import vn.momo.kits.platform.getScreenDimensions
13
14
  import kotlin.math.max
@@ -20,13 +21,13 @@ const val MAX_DEVICE_SCALE = 5
20
21
  @Composable
21
22
  fun scaleSize(size: Float): Float {
22
23
  val context = LocalContext.current
23
- // Host-pushed context wins; otherwise fall back to the app-wide KitFontScale holder.
24
- val useOsScale = context?.useOSScaleRate ?: KitFontScale.useOSScaleRate
24
+ val useOsScale = context?.useOSScaleRate ?: DesignSystemKits.getConfigs().useOSScaleRate
25
25
  if (!useOsScale) {
26
- return size * (context?.userScaleRate ?: KitFontScale.userScaleRate)
26
+ return size * (context?.userScaleRate ?: DesignSystemKits.getConfigs().userScaleRate)
27
27
  }
28
28
  val scaleSizeMaxRate: Float = MAX_FONT_SCALE
29
- val deviceWidth = getScreenDimensions().width
29
+ // Short side, not width: landscape must not inflate every font.
30
+ val deviceWidth = getScreenDimensions().let { minOf(it.width, it.height) }
30
31
  val deviceScale = deviceWidth / DEFAULT_SCREEN_SIZE
31
32
 
32
33
  val density = LocalDensity.current
@@ -83,12 +84,6 @@ fun scaleSize(textStyle: TextStyle): TextStyle {
83
84
  }
84
85
 
85
86
 
86
- /**
87
- * Tên file font trong tài nguyên native của app host (xem [hostFontFamily]).
88
- *
89
- * SDK không đóng gói font riêng nữa: app host đã bundle sẵn toàn bộ font từ `font-source`
90
- * cho cả React Native lẫn native, nên bản copy trong `composeResources` chỉ làm phình app.
91
- */
92
87
  private const val LEGACY_DEFAULT_FONT_FILE = "SFProText-Regular.ttf"
93
88
  private const val NEW_DEFAULT_FONT_FILE = "MomoTrustSans-Regular.ttf"
94
89
 
@@ -123,17 +118,27 @@ private val hostFontFiles: Map<String, String> = mapOf(
123
118
  private const val NEW_DEFAULT_FONT_FAMILY = "MomoTrustSans"
124
119
  private const val LEGACY_DEFAULT_FONT_FAMILY = "SFProText"
125
120
 
121
+ // Families that ship one file per weight; the others map every weight to a single file.
122
+ private val weightedFontFamilies = setOf(NEW_DEFAULT_FONT_FAMILY, LEGACY_DEFAULT_FONT_FAMILY)
123
+
126
124
  internal val defaultFontFamily: String
127
- get() = if (KitDesignSystem.isWhiteList) NEW_DEFAULT_FONT_FAMILY else LEGACY_DEFAULT_FONT_FAMILY
125
+ get() = if (DesignSystemKits.getConfigs().isWhiteList) NEW_DEFAULT_FONT_FAMILY else LEGACY_DEFAULT_FONT_FAMILY
128
126
 
129
127
  @Composable
130
128
  fun getFontFamily(fontFamily: String, fontWeight: FontWeight? = FontWeight.Normal): FontFamily {
131
- val isWhiteList = KitDesignSystem.isWhiteList
129
+ val isWhiteList = DesignSystemKits.getConfigs().isWhiteList
132
130
  val defaultFile = if (isWhiteList) NEW_DEFAULT_FONT_FILE else LEGACY_DEFAULT_FONT_FILE
133
131
  val key = "$fontFamily-${fontWeight?.weight}"
134
- val fileName = hostFontFiles[key] ?: defaultFile
135
- return hostFontFamily(fileName)
136
- ?: hostFontFamily(defaultFile)
132
+ val mappedFile = hostFontFiles[key]
133
+ val fileName = mappedFile ?: defaultFile
134
+ // Declaring the file's real weight keeps Compose from synthesizing bold on top of a bold file.
135
+ val fileWeight = if (mappedFile != null && fontFamily in weightedFontFamilies) {
136
+ fontWeight ?: FontWeight.Normal
137
+ } else {
138
+ FontWeight.Normal
139
+ }
140
+ return hostFontFamily(fileName, fileWeight)
141
+ ?: hostFontFamily(defaultFile, FontWeight.Normal)
137
142
  ?: FontFamily.Default
138
143
  }
139
144
 
@@ -9,7 +9,6 @@ import androidx.compose.ui.Modifier
9
9
  import androidx.compose.ui.platform.LocalDensity
10
10
  import androidx.compose.ui.unit.Dp
11
11
  import androidx.compose.ui.unit.dp
12
- import vn.momo.kits.platform.getScreenDimensions
13
12
  import kotlin.math.floor
14
13
 
15
14
  internal data class GridContext(
@@ -24,7 +23,7 @@ internal data class GridContext(
24
23
  internal val LocalGridContext = compositionLocalOf<GridContext?> { null }
25
24
 
26
25
  @Composable
27
- internal fun gridColumns(): Int = if (getScreenDimensions().width > 600) 24 else 12
26
+ internal fun gridColumns(): Int = if (getScreenLayout().contentWidth > 600.dp) 24 else 12
28
27
 
29
28
  @Composable
30
29
  internal fun gridContext(maxWidthPx: Int, columns: Int, gap: Dp): GridContext {