@momo-kits/native-kits 0.163.1-beta.8-debug → 0.163.1-beta.9-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 (220) hide show
  1. package/CLAUDE.md +1 -1
  2. package/compose/build.gradle.kts +1 -1
  3. package/compose/compose.podspec +1 -1
  4. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  5. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +30 -3
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +5 -0
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +1 -1
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +3 -0
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +4 -0
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +22 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +8 -2
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +63 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +32 -19
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +49 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +1 -1
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +1 -1
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +3 -1
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +93 -75
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +26 -7
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +3 -1
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +23 -2
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +11 -0
  25. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  26. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +26 -3
  27. package/gradle.properties +1 -1
  28. package/ios/Application/ApplicationEnvironment.swift +15 -2
  29. package/ios/Application/Components.swift +50 -44
  30. package/ios/Application/FloatingButton.swift +10 -9
  31. package/ios/Application/HeaderRight.swift +79 -45
  32. package/ios/Application/Localize.swift +277 -0
  33. package/ios/Application/MaxApi.swift +18 -0
  34. package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -0
  35. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
  36. package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
  37. package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
  38. package/ios/Application/Navigation/HeaderTitle.swift +57 -0
  39. package/ios/Application/Navigation/HeaderUser.swift +209 -0
  40. package/ios/Application/Navigation/NavigationContainer.swift +200 -0
  41. package/ios/Application/Navigation/NavigationOptions.swift +109 -0
  42. package/ios/Application/Navigation/Navigator.swift +385 -0
  43. package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
  44. package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
  45. package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
  46. package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
  47. package/ios/Application/Navigation/component/Header.swift +178 -0
  48. package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
  49. package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
  50. package/ios/Application/Screen.swift +2 -1
  51. package/ios/Application/StackScreen.swift +501 -0
  52. package/ios/Avatar/Avatar.swift +193 -0
  53. package/ios/Badge/Badge.swift +11 -14
  54. package/ios/Badge/BadgeRibbon.swift +92 -21
  55. package/ios/BaselineView/BaselineView.swift +163 -0
  56. package/ios/Button/Button.swift +124 -29
  57. package/ios/Carousel/Carousel.swift +196 -0
  58. package/ios/Checkbox/Checkbox.swift +23 -28
  59. package/ios/Chip/Chip.swift +56 -33
  60. package/ios/Collapse/Collapse.swift +192 -0
  61. package/ios/Colors+Radius+Spacing/Colors.swift +20 -17
  62. package/ios/Colors+Radius+Spacing/Spacing.swift +3 -1
  63. package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
  64. package/ios/DateTimePicker/DateTimePicker.swift +212 -0
  65. package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
  66. package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
  67. package/ios/DateTimePicker/WheelPicker.swift +165 -0
  68. package/ios/Divider/Divider.swift +16 -0
  69. package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
  70. package/ios/Extensions/Color++.swift +32 -0
  71. package/ios/Icon/Icon.swift +10 -1
  72. package/ios/IconButton/IconButton.swift +140 -0
  73. package/ios/Image/Image.swift +16 -3
  74. package/ios/Information/Information.swift +3 -1
  75. package/ios/Input/ErrorView.swift +3 -1
  76. package/ios/Input/Input.swift +72 -33
  77. package/ios/Input/InputPhoneNumber.swift +33 -21
  78. package/ios/Input/InputSearch.swift +107 -48
  79. package/ios/Input/InputTextArea.swift +134 -64
  80. package/ios/InputDropDown/InputDropDown.swift +201 -0
  81. package/ios/InputMoney/InputMoney.swift +347 -0
  82. package/ios/InputOTP/InputOTP.swift +210 -0
  83. package/ios/Loader/Loader.swift +113 -0
  84. package/ios/MoMoUIKits.podspec +5 -0
  85. package/ios/Pagination/PaginationDot.swift +61 -0
  86. package/ios/Pagination/PaginationNumber.swift +35 -0
  87. package/ios/Pagination/PaginationScroll.swift +101 -0
  88. package/ios/Pagination/PaginationWhiteDot.swift +37 -0
  89. package/ios/Popup/PopupDisplay.swift +11 -20
  90. package/ios/Popup/PopupInput.swift +2 -8
  91. package/ios/Popup/PopupNotify.swift +218 -0
  92. package/ios/Popup/PopupPromotion.swift +16 -8
  93. package/ios/ProgressInfo/ProgressInfo.swift +296 -0
  94. package/ios/Radio/Radio.swift +69 -0
  95. package/ios/Rating/Rating.swift +82 -0
  96. package/ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json +11 -0
  97. package/ios/Resources/MoMoUIKitsColors.xcassets/Background.colorset/Contents.json +38 -0
  98. package/ios/Resources/MoMoUIKitsColors.xcassets/Border.colorset/Contents.json +38 -0
  99. package/ios/Resources/MoMoUIKitsColors.xcassets/Card.colorset/Contents.json +38 -0
  100. package/ios/Resources/MoMoUIKitsColors.xcassets/Contents.json +6 -0
  101. package/ios/Resources/MoMoUIKitsColors.xcassets/Error.colorset/Contents.json +38 -0
  102. package/ios/Resources/MoMoUIKitsColors.xcassets/Primary.colorset/Contents.json +38 -0
  103. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryDark.colorset/Contents.json +38 -0
  104. package/ios/Resources/MoMoUIKitsColors.xcassets/PrimaryLight.colorset/Contents.json +38 -0
  105. package/ios/Resources/MoMoUIKitsColors.xcassets/Secondary.colorset/Contents.json +38 -0
  106. package/ios/Resources/MoMoUIKitsColors.xcassets/Success.colorset/Contents.json +38 -0
  107. package/ios/Resources/MoMoUIKitsColors.xcassets/Text.colorset/Contents.json +38 -0
  108. package/ios/Resources/MoMoUIKitsColors.xcassets/TextSecondary.colorset/Contents.json +38 -0
  109. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
  110. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
  111. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
  112. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
  113. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
  114. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
  115. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
  116. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
  117. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
  118. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
  119. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
  120. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
  121. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
  122. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
  123. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
  124. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
  125. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
  126. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
  127. package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
  128. package/ios/Resources/MoMoUIKitsColors.xcassets/Warning.colorset/Contents.json +38 -0
  129. package/ios/Skeleton/Skeleton.swift +99 -0
  130. package/ios/Slider/Slider.swift +237 -0
  131. package/ios/Stepper/Stepper.swift +220 -0
  132. package/ios/Steps/Steps.swift +467 -0
  133. package/ios/SuggestAction/SuggestAction.swift +99 -0
  134. package/ios/Swipeable/SwipeCell.swift +187 -0
  135. package/ios/Swipeable/SwipeCellModel.swift +21 -0
  136. package/ios/Switch/Switch.swift +53 -28
  137. package/ios/TabView/TabView.swift +534 -0
  138. package/ios/Tag/Tag.swift +98 -0
  139. package/ios/Template/TrustBanner/TrustBanner.swift +6 -7
  140. package/ios/Title/Title.swift +269 -0
  141. package/ios/Tooltip/Tooltip.swift +485 -0
  142. package/ios/Typography/FontScaleStore.swift +19 -0
  143. package/ios/Typography/Text.swift +79 -5
  144. package/ios/Typography/Typography.swift +3 -0
  145. package/ios/Uploader/Uploader.swift +174 -0
  146. package/ios/native-kits.podspec +61 -8
  147. package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
  148. package/ios-demo/README.md +120 -0
  149. package/ios-demo/Resources/dot_loading.json +558 -0
  150. package/ios-demo/Resources/icon.json +4052 -0
  151. package/ios-demo/Resources/lottie_circle_loader.json +1 -0
  152. package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
  153. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
  154. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
  155. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
  156. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
  157. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
  158. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
  159. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
  160. package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
  161. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
  162. package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
  163. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
  164. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
  165. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -0
  166. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
  167. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
  168. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +100 -0
  169. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
  170. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
  171. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
  172. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
  173. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
  174. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
  175. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
  176. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
  177. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +279 -0
  178. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
  179. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
  180. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
  181. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
  182. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
  183. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
  184. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
  185. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
  188. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
  189. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +188 -0
  190. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +229 -0
  191. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
  192. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
  193. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
  194. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
  195. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
  196. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
  197. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
  198. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
  199. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
  200. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
  201. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
  202. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
  203. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
  204. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
  205. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
  206. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
  207. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
  208. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
  209. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
  210. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
  211. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
  212. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
  213. package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +51 -0
  214. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
  215. package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
  216. package/local.properties +2 -2
  217. package/package.json +1 -1
  218. package/publish.sh +53 -0
  219. package/scripts/gen-ios-color.mjs +136 -0
  220. package/.claude/settings.local.json +0 -62
@@ -1,6 +1,7 @@
1
1
  import SwiftUI
2
2
 
3
3
  public struct Badge: View {
4
+ @Environment(\.appTheme) private var theme
4
5
  let label: String
5
6
  let backgroundColor: Color?
6
7
 
@@ -38,24 +39,21 @@ public struct Badge: View {
38
39
  }
39
40
 
40
41
  private var badgeColor: Color {
41
- // If a custom background color is provided, use it
42
- if let bgColor = backgroundColor {
42
+ // Default colors based on label type
43
+ let defaultColor: Color = isNumber(label)
44
+ ? theme.colors.error.primary
45
+ : theme.colors.warning.primary
46
+
47
+ // Mirror Compose: only apply backgroundColor when it is one of the
48
+ // allowed primaryColors; otherwise fall back to the label-based default.
49
+ if let bgColor = backgroundColor, primaryColors.contains(bgColor) {
43
50
  return bgColor
44
51
  }
45
-
46
- // Otherwise, use default colors based on label type
47
- if isNumber(label) {
48
- return Colors.red03 // error.primary
49
- } else {
50
- return Colors.orange03 // warning.primary
51
- }
52
+ return defaultColor
52
53
  }
53
54
 
54
55
  public var body: some View {
55
- Text(formatTitle(label))
56
- .font(.system(size: scaleSize(10), weight: .bold))
57
- .ignoreBoldTextSetting()
58
- .foregroundColor(Colors.black01)
56
+ MomoText(formatTitle(label), typography: .actionXxsBold, color: Colors.black01)
59
57
  .padding(.horizontal, Spacing.XS)
60
58
  .frame(minWidth: scaleSize(16), minHeight: scaleSize(16))
61
59
  .background(badgeColor)
@@ -83,4 +81,3 @@ struct Badge_Previews: PreviewProvider {
83
81
  }
84
82
  }
85
83
  #endif
86
-
@@ -77,10 +77,11 @@ public enum RibbonPosition {
77
77
  }
78
78
 
79
79
  public struct BadgeRibbon: View {
80
+ @Environment(\.appTheme) private var theme
80
81
  let position: RibbonPosition
81
82
  let label: String
82
83
  let isRound: Bool
83
- let backgroundColor: Color
84
+ let backgroundColor: Color?
84
85
 
85
86
  public init(
86
87
  position: RibbonPosition = .topRight,
@@ -91,7 +92,7 @@ public struct BadgeRibbon: View {
91
92
  self.position = position
92
93
  self.label = label
93
94
  self.isRound = isRound
94
- self.backgroundColor = backgroundColor ?? Colors.orange03
95
+ self.backgroundColor = backgroundColor
95
96
  }
96
97
 
97
98
  private var rotation: Angle {
@@ -117,6 +118,8 @@ public struct BadgeRibbon: View {
117
118
  }
118
119
 
119
120
  public var body: some View {
121
+ let bgColor = backgroundColor ?? theme.colors.warning.primary
122
+
120
123
  HStack(alignment: verticalAlignment, spacing: 0) {
121
124
  if useUpTail {
122
125
  upTail
@@ -125,22 +128,18 @@ public struct BadgeRibbon: View {
125
128
  }
126
129
 
127
130
  if isRound {
128
- roundContent
131
+ roundContent(backgroundColor: bgColor)
129
132
  } else {
130
- skewContent
133
+ skewContent(backgroundColor: bgColor)
131
134
  }
132
135
  }
133
136
  .frame(height: RibbonConstants.ribbonHeight)
134
137
  .rotationEffect(rotation)
135
138
  }
136
139
 
137
- private var roundContent: some View {
140
+ private func roundContent(backgroundColor: Color) -> some View {
138
141
  HStack(spacing: 0) {
139
- Text(label)
140
- .font(.system(size: scaleSize(12), weight: .medium))
141
- .ignoreBoldTextSetting()
142
- .foregroundColor(Colors.black01)
143
- .lineLimit(1)
142
+ MomoText(label, typography: .labelXsMedium, color: Colors.black01, maxLines: 1)
144
143
  .rotationEffect(rotation)
145
144
  }
146
145
  .frame(height: RibbonConstants.roundHeight)
@@ -156,13 +155,9 @@ public struct BadgeRibbon: View {
156
155
  )
157
156
  }
158
157
 
159
- private var skewContent: some View {
158
+ private func skewContent(backgroundColor: Color) -> some View {
160
159
  HStack(spacing: 0) {
161
- Text(label)
162
- .font(.system(size: scaleSize(12), weight: .medium))
163
- .ignoreBoldTextSetting()
164
- .foregroundColor(Colors.black01)
165
- .lineLimit(1)
160
+ MomoText(label, typography: .labelXsMedium, color: Colors.black01, maxLines: 1)
166
161
  .rotationEffect(rotation)
167
162
  .frame(height: RibbonConstants.skewBodyHeight)
168
163
  .padding(.horizontal, 8)
@@ -179,7 +174,6 @@ public struct BadgeRibbon: View {
179
174
  )
180
175
  .frame(width: RibbonConstants.headTailWidth, height: RibbonConstants.headTailHeight)
181
176
  .rotationEffect(.degrees(180))
182
- .offset(x: 1)
183
177
  .clipped()
184
178
  }
185
179
 
@@ -189,7 +183,6 @@ public struct BadgeRibbon: View {
189
183
  placeholder: AnyView(Color.clear)
190
184
  )
191
185
  .frame(width: RibbonConstants.headTailWidth, height: RibbonConstants.headTailHeight)
192
- .offset(x: 1)
193
186
  .clipped()
194
187
  }
195
188
 
@@ -199,17 +192,96 @@ public struct BadgeRibbon: View {
199
192
  placeholder: AnyView(Color.clear)
200
193
  )
201
194
  .frame(width: RibbonConstants.skewTailWidth, height: RibbonConstants.skewTailHeight)
202
- .offset(x: -2)
203
195
  .clipped()
204
196
  }
205
197
  }
206
198
 
199
+ /// Ported from Compose's `RoundedBadgeRibbon` (BadgeRibbon.kt:164-326): a canvas-drawn
200
+ /// ribbon shape sized to its text, mirrored per corner via scaleX/scaleY (never an actual
201
+ /// rotation) — the label gets the same scale applied a second time so it stays right-reading
202
+ /// while only the ribbon shape flips.
203
+ public struct RoundedBadgeRibbon: View {
204
+ let text: String
205
+ let position: RibbonPosition
206
+
207
+ public init(text: String = "Label", position: RibbonPosition = .topLeft) {
208
+ self.text = text
209
+ self.position = position
210
+ }
211
+
212
+ private var scale: (x: CGFloat, y: CGFloat) {
213
+ switch position {
214
+ case .topLeft: return (-1, 1)
215
+ case .topRight: return (1, 1)
216
+ case .bottomRight: return (1, -1)
217
+ case .bottomLeft: return (-1, -1)
218
+ }
219
+ }
220
+
221
+ private var measuredTextSize: CGSize {
222
+ let fontSize = scaleSize(TypographyStyle.labelXsMedium.fontSize)
223
+ let font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
224
+ return text.size(withAttributes: [.font: font])
225
+ }
226
+
227
+ public var body: some View {
228
+ let textSize = measuredTextSize
229
+ let minRibbonHeight = max(textSize.height * 1.2, 16)
230
+ let padding = minRibbonHeight / 4
231
+ let badgeHeight = minRibbonHeight + padding
232
+ let badgeWidth = max(textSize.width + padding * 2, 28)
233
+
234
+ ZStack {
235
+ Canvas { context, size in
236
+ let ribbonHeight = size.height - padding
237
+ let cornerRadius = (badgeHeight - padding) / 2
238
+ let mainColor = Colors.orange03
239
+ let tailColor = Colors.red01
240
+
241
+ let headWidth: CGFloat = 4
242
+ // Kotlin computes `leftSectionWidth` + `middleSectionWidth` here, but those
243
+ // cancel algebraically to `width - headWidth` — kept as the direct result.
244
+ let rightX = size.width - headWidth
245
+
246
+ let leftMiddlePath = RoundedCorners(topLeft: cornerRadius, topRight: 0, bottomLeft: cornerRadius, bottomRight: 0)
247
+ .path(in: CGRect(x: 0, y: 0, width: rightX + 1, height: ribbonHeight))
248
+ context.fill(leftMiddlePath, with: .color(mainColor))
249
+
250
+ let headPath = RoundedCorners(topLeft: 0, topRight: cornerRadius, bottomLeft: 0, bottomRight: 0)
251
+ .path(in: CGRect(x: rightX, y: 0, width: headWidth, height: ribbonHeight))
252
+ context.fill(headPath, with: .color(mainColor))
253
+
254
+ let halfHeadWidth = headWidth / 2
255
+ let tailStartX = rightX + headWidth - halfHeadWidth
256
+ let tailY = ribbonHeight
257
+ let tailHeight = padding
258
+
259
+ context.fill(
260
+ Path(CGRect(x: tailStartX, y: tailY, width: halfHeadWidth, height: halfHeadWidth)),
261
+ with: .color(mainColor)
262
+ )
263
+
264
+ let tailRightPath = RoundedCorners(topLeft: 0, topRight: tailHeight, bottomLeft: 0, bottomRight: tailHeight)
265
+ .path(in: CGRect(x: tailStartX, y: tailY, width: halfHeadWidth, height: tailHeight))
266
+ context.fill(tailRightPath, with: .color(tailColor))
267
+ }
268
+
269
+ MomoText(text, typography: .labelXsMedium, color: Colors.black01, maxLines: 1)
270
+ .padding(.bottom, padding)
271
+ .padding(.horizontal, padding / 2)
272
+ .scaleEffect(x: scale.x, y: scale.y)
273
+ }
274
+ .frame(width: badgeWidth, height: badgeHeight)
275
+ .scaleEffect(x: scale.x, y: scale.y)
276
+ }
277
+ }
278
+
207
279
  // MARK: - Constants
208
280
  private struct RibbonConstants {
209
281
  static let ribbonHeight: CGFloat = 20
210
282
  static let roundHeight: CGFloat = 16
211
283
  static let skewBodyHeight: CGFloat = 16
212
- static let roundRightRadius: CGFloat = 8
284
+ static let roundRightRadius: CGFloat = 12
213
285
  static let roundPaddingEnd: CGFloat = 6
214
286
  static let skewTailWidth: CGFloat = 8
215
287
  static let skewTailHeight: CGFloat = 16
@@ -241,4 +313,3 @@ struct BadgeRibbon_Previews: PreviewProvider {
241
313
  }
242
314
  }
243
315
  #endif
244
-
@@ -0,0 +1,163 @@
1
+ import SwiftUI
2
+
3
+ public struct BaselineView: View {
4
+ private let enabled: Bool
5
+
6
+ public init(enabled: Bool = true) {
7
+ self.enabled = enabled
8
+ }
9
+
10
+ public var body: some View {
11
+ if enabled {
12
+ GeometryReader { geometry in
13
+ let topInset = geometry.safeAreaInsets.top
14
+ let bottomInset = min(geometry.safeAreaInsets.bottom, 21)
15
+
16
+ ZStack(alignment: .topLeading) {
17
+ dangerZones(topInset: topInset, bottomInset: bottomInset)
18
+ guideLines(
19
+ topInset: topInset,
20
+ bottomInset: bottomInset,
21
+ width: geometry.size.width
22
+ )
23
+ }
24
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
25
+ .ignoresSafeArea()
26
+ .allowsHitTesting(false)
27
+ .accessibilityHidden(true)
28
+ }
29
+ }
30
+ }
31
+
32
+ private func dangerZones(topInset: CGFloat, bottomInset: CGFloat) -> some View {
33
+ let safeBackground = Color.red.opacity(0.15)
34
+
35
+ return ZStack(alignment: .topLeading) {
36
+ safeBackground
37
+ .frame(maxWidth: .infinity)
38
+ .frame(height: topInset)
39
+
40
+ HStack(spacing: 0) {
41
+ safeBackground
42
+ .frame(width: 12)
43
+ Spacer(minLength: 0)
44
+ safeBackground
45
+ .frame(width: 12)
46
+ }
47
+ .padding(.top, topInset)
48
+ .padding(.bottom, bottomInset)
49
+
50
+ VStack(spacing: 0) {
51
+ Spacer(minLength: 0)
52
+ safeBackground
53
+ .frame(maxWidth: .infinity)
54
+ .frame(height: bottomInset)
55
+ }
56
+ }
57
+ }
58
+
59
+ private func guideLines(topInset: CGFloat, bottomInset: CGFloat, width: CGFloat) -> some View {
60
+ ZStack(alignment: .topLeading) {
61
+ baselineLine(isDotted: false, color: Color(hex: 0xE400FF))
62
+ .offset(y: topInset)
63
+
64
+ baselineLine(color: Color(hex: 0xE400FF))
65
+ .offset(y: topInset + 52)
66
+
67
+ baselineLine(color: Color(hex: 0xE400FF))
68
+ .offset(y: topInset + 104)
69
+
70
+ bottomLine(bottomInset: bottomInset + 64, color: Color(hex: 0xE400FF))
71
+
72
+ bottomLine(bottomInset: bottomInset, isDotted: false, color: Color(hex: 0xE400FF))
73
+
74
+ baselineLine(orientation: .vertical, isDotted: false, color: Color(hex: 0xE400FF))
75
+ .offset(x: 12)
76
+
77
+ baselineLine(orientation: .vertical, isDotted: false, color: Color(hex: 0xE400FF))
78
+ .offset(x: width - 12)
79
+
80
+ baselineLine(color: Color(hex: 0xFF7A00))
81
+ .offset(y: topInset + 26)
82
+
83
+ bottomLine(bottomInset: bottomInset + 56, color: Color(hex: 0xFFCC00))
84
+
85
+ bottomLine(bottomInset: bottomInset + 8, color: Color(hex: 0xFFCC00))
86
+
87
+ baselineLine(orientation: .vertical, color: Color(hex: 0x00C520))
88
+ .frame(height: 52)
89
+ .offset(x: 40, y: topInset)
90
+
91
+ baselineLine(orientation: .vertical, color: Color(hex: 0x00C520))
92
+ .frame(height: 52)
93
+ .offset(x: 48, y: topInset)
94
+ }
95
+ }
96
+
97
+ private func bottomLine(
98
+ bottomInset: CGFloat,
99
+ isDotted: Bool = true,
100
+ color: Color
101
+ ) -> some View {
102
+ VStack(spacing: 0) {
103
+ Spacer(minLength: 0)
104
+ baselineLine(isDotted: isDotted, color: color)
105
+ .padding(.bottom, bottomInset)
106
+ }
107
+ }
108
+
109
+ private func baselineLine(
110
+ orientation: BaselineOrientation = .horizontal,
111
+ isDotted: Bool = true,
112
+ color: Color
113
+ ) -> some View {
114
+ BaselineLineShape(orientation: orientation)
115
+ .stroke(
116
+ color,
117
+ style: StrokeStyle(
118
+ lineWidth: 1,
119
+ lineCap: .round,
120
+ dash: isDotted ? [8, 8] : []
121
+ )
122
+ )
123
+ .frame(
124
+ maxWidth: orientation == .horizontal ? .infinity : 1,
125
+ maxHeight: orientation == .vertical ? .infinity : 1
126
+ )
127
+ }
128
+ }
129
+
130
+ public enum BaselineOrientation: Sendable {
131
+ case horizontal
132
+ case vertical
133
+ }
134
+
135
+ private struct BaselineLineShape: Shape {
136
+ let orientation: BaselineOrientation
137
+
138
+ func path(in rect: CGRect) -> Path {
139
+ var path = Path()
140
+
141
+ switch orientation {
142
+ case .horizontal:
143
+ path.move(to: CGPoint(x: rect.minX, y: rect.midY))
144
+ path.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
145
+ case .vertical:
146
+ path.move(to: CGPoint(x: rect.midX, y: rect.minY))
147
+ path.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
148
+ }
149
+
150
+ return path
151
+ }
152
+ }
153
+
154
+ #if DEBUG
155
+ struct BaselineView_Previews: PreviewProvider {
156
+ static var previews: some View {
157
+ ZStack {
158
+ Colors.black01
159
+ BaselineView()
160
+ }
161
+ }
162
+ }
163
+ #endif
@@ -31,33 +31,45 @@ public struct ButtonStyleData {
31
31
 
32
32
  public extension ButtonType {
33
33
  func backgroundColor(loading: Bool) -> Color {
34
+ backgroundColor(loading: loading, theme: defaultTheme)
35
+ }
36
+
37
+ func backgroundColor(loading: Bool, theme: Theme) -> Color {
34
38
  switch self {
35
- case .disabled: return Colors.black05.opacity(loading ? 0.75 : 1)
36
- case .primary: return Colors.primary.opacity(loading ? 0.75 : 1)
37
- case .secondary: return Colors.card.opacity(loading ? 0.75 : 1)
38
- case .outline: return Colors.card.opacity(loading ? 0.75 : 1)
39
- case .tonal: return Colors.primaryLight.opacity(loading ? 0.75 : 1)
40
- case .danger: return Colors.error.opacity(loading ? 0.75 : 1)
39
+ case .disabled: return theme.colors.background.disable.opacity(loading ? 0.75 : 1)
40
+ case .primary: return theme.colors.primary.opacity(loading ? 0.75 : 1)
41
+ case .secondary: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
42
+ case .outline: return theme.colors.background.surface.opacity(loading ? 0.75 : 1)
43
+ case .tonal: return theme.colors.background.tonal.opacity(loading ? 0.75 : 1)
44
+ case .danger: return theme.colors.error.primary.opacity(loading ? 0.75 : 1)
41
45
  case .text: return .clear
42
46
  }
43
47
  }
44
48
 
45
49
  func contentColor(loading: Bool) -> Color {
50
+ contentColor(loading: loading, theme: defaultTheme)
51
+ }
52
+
53
+ func contentColor(loading: Bool, theme: Theme) -> Color {
46
54
  switch self {
47
- case .disabled: return Colors.black06.opacity(loading ? 0.75 : 1)
55
+ case .disabled: return theme.colors.text.disable.opacity(loading ? 0.75 : 1)
48
56
  case .primary: return Colors.black01
49
- case .secondary: return Colors.black17
50
- case .outline: return Colors.primary
51
- case .tonal: return Colors.primary
57
+ case .secondary: return theme.colors.text.default
58
+ case .outline: return theme.colors.primary
59
+ case .tonal: return theme.colors.primary
52
60
  case .danger: return Colors.black01
53
- case .text: return Colors.primary
61
+ case .text: return theme.colors.primary
54
62
  }
55
63
  }
56
64
 
57
65
  var borderColor: Color? {
66
+ borderColor(theme: defaultTheme)
67
+ }
68
+
69
+ func borderColor(theme: Theme) -> Color? {
58
70
  switch self {
59
- case .outline: return Colors.primary
60
- case .secondary: return Colors.black04
71
+ case .outline: return theme.colors.primary
72
+ case .secondary: return theme.colors.border.default
61
73
  default: return nil
62
74
  }
63
75
  }
@@ -111,12 +123,12 @@ extension ButtonSize {
111
123
  case .large: return .actionDefaultBold
112
124
  case .medium: return .actionSBold
113
125
  case .small: return .actionXsBold
114
- default : return .actionDefaultBold
115
126
  }
116
127
  }
117
128
  }
118
129
 
119
130
  public struct Button: View {
131
+ @Environment(\.appTheme) private var theme
120
132
  var title: String
121
133
  var action: () -> Void
122
134
  var type: ButtonType
@@ -125,6 +137,7 @@ public struct Button: View {
125
137
  var iconRight: AnyView?
126
138
  var isFull: Bool
127
139
  var loading: Bool
140
+ var useTintColor: Bool
128
141
 
129
142
  public init(
130
143
  title: String = "",
@@ -134,7 +147,8 @@ public struct Button: View {
134
147
  iconLeft: AnyView? = nil,
135
148
  iconRight: AnyView? = nil,
136
149
  isFull: Bool = true,
137
- loading: Bool = false
150
+ loading: Bool = false,
151
+ useTintColor: Bool = true
138
152
  ) {
139
153
  self.title = title
140
154
  self.action = action
@@ -144,6 +158,36 @@ public struct Button: View {
144
158
  self.iconRight = iconRight
145
159
  self.isFull = isFull
146
160
  self.loading = loading
161
+ self.useTintColor = useTintColor
162
+ }
163
+
164
+ /// Mirrors Kotlin's `Button(iconLeft: String, iconRight: String, ...)`, which resolves
165
+ /// icons by design-system source name through the shared `Icon` component rather than
166
+ /// a caller-supplied view. `iconLeft` is required here (unlike Kotlin's `= ""` default)
167
+ /// solely to keep this initializer unambiguous alongside the `AnyView`-based one above —
168
+ /// pass `""` explicitly for an icon-right-only button.
169
+ public init(
170
+ title: String = "Button",
171
+ action: @escaping () -> Void,
172
+ type: ButtonType = .primary,
173
+ size: ButtonSize = .large,
174
+ iconLeft: String,
175
+ iconRight: String = "",
176
+ isFull: Bool = true,
177
+ loading: Bool = false,
178
+ useTintColor: Bool = true
179
+ ) {
180
+ self.init(
181
+ title: title,
182
+ action: action,
183
+ type: type,
184
+ size: size,
185
+ iconLeft: iconLeft.isEmpty ? nil : AnyView(Icon(source: iconLeft)),
186
+ iconRight: iconRight.isEmpty ? nil : AnyView(Icon(source: iconRight)),
187
+ isFull: isFull,
188
+ loading: loading,
189
+ useTintColor: useTintColor
190
+ )
147
191
  }
148
192
 
149
193
  private func shouldLoadingOnLeft(_ left: AnyView?, _ right: AnyView?) -> Bool {
@@ -160,14 +204,15 @@ public struct Button: View {
160
204
 
161
205
  public var body: some View {
162
206
  let loadingOnLeft = shouldLoadingOnLeft(iconLeft, iconRight)
207
+ let isEnabled = type != .disabled && !loading
163
208
 
164
- let bg = type.backgroundColor(loading: loading)
165
- let fg = type.contentColor(loading: loading)
166
- let border = type.borderColor
209
+ let bg = type.backgroundColor(loading: loading, theme: theme)
210
+ let fg = type.contentColor(loading: loading, theme: theme)
211
+ let border = type.borderColor(theme: theme)
167
212
  let borderWidth = type.borderWidth
168
213
 
169
214
  SwiftUI.Button(action: {
170
- if !loading, type != .disabled {
215
+ if isEnabled {
171
216
  action()
172
217
  }
173
218
  }) {
@@ -178,7 +223,7 @@ public struct Button: View {
178
223
  .colorMultiply(fg)
179
224
 
180
225
  } else if let iconLeft = iconLeft {
181
- iconLeft.frame(width: size.iconSize, height: size.iconSize)
226
+ tintedIcon(iconLeft, color: fg)
182
227
  }
183
228
 
184
229
  MomoText(title, typography: size.typographyStyle, color: fg)
@@ -191,20 +236,70 @@ public struct Button: View {
191
236
  .colorMultiply(fg)
192
237
 
193
238
  } else if let iconRight = iconRight {
194
- iconRight.frame(width: size.iconSize, height: size.iconSize)
239
+ tintedIcon(iconRight, color: fg)
195
240
  }
196
241
  }
197
242
  .frame(maxWidth: isFull ? .infinity : nil)
198
243
  .padding(.horizontal, size.padding)
199
244
  .frame(height: size.height)
200
- .background(bg)
201
- .overlay(
202
- RoundedRectangle(cornerRadius: size.radius)
203
- .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
204
- )
205
- .clipShape(RoundedRectangle(cornerRadius: size.radius))
206
- .opacity(loading ? 0.75 : 1)
207
245
  }
208
- .disabled(type == .disabled || loading)
246
+ .buttonStyle(ButtonPressFeedbackStyle(
247
+ isEnabled: isEnabled,
248
+ background: bg,
249
+ border: border,
250
+ borderWidth: borderWidth,
251
+ radius: size.radius,
252
+ height: size.height
253
+ ))
254
+ .disabled(!isEnabled)
255
+ }
256
+
257
+ @ViewBuilder
258
+ private func tintedIcon(_ icon: AnyView, color: Color) -> some View {
259
+ if useTintColor {
260
+ icon
261
+ .frame(width: size.iconSize, height: size.iconSize)
262
+ .colorMultiply(color)
263
+ } else {
264
+ icon
265
+ .frame(width: size.iconSize, height: size.iconSize)
266
+ }
267
+ }
268
+ }
269
+
270
+ /// Mirrors Compose's press feedback: the background shrinks via an animated
271
+ /// horizontal inset while the content fades, rather than a plain opacity dim on
272
+ /// the whole button (matching `animatedPadding`/`alpha` in Button.kt).
273
+ private struct ButtonPressFeedbackStyle: ButtonStyle {
274
+ let isEnabled: Bool
275
+ let background: Color
276
+ let border: Color?
277
+ let borderWidth: CGFloat
278
+ let radius: CGFloat
279
+ let height: CGFloat
280
+
281
+ func makeBody(configuration: Configuration) -> some View {
282
+ let pressed = configuration.isPressed && isEnabled
283
+
284
+ ZStack {
285
+ RoundedRectangle(cornerRadius: radius)
286
+ .fill(background)
287
+ .overlay(
288
+ RoundedRectangle(cornerRadius: radius)
289
+ .strokeBorder(border ?? .clear, lineWidth: border != nil ? borderWidth : 0)
290
+ )
291
+ .padding(.horizontal, pressed ? 2 : 0)
292
+
293
+ configuration.label
294
+ .opacity(pressed ? 0.5 : 1)
295
+ }
296
+ // Pin the whole button to its intended height explicitly: the bare
297
+ // RoundedRectangle background has no intrinsic size of its own, so left
298
+ // unconstrained in a ZStack inside a non-height-constraining container
299
+ // (e.g. a footer VStack) it can report an oversized ideal height and
300
+ // stretch the button — and its container — far taller than intended.
301
+ .frame(height: height)
302
+ .clipShape(RoundedRectangle(cornerRadius: radius))
303
+ .animation(.easeInOut(duration: 0.1), value: pressed)
209
304
  }
210
305
  }