@momo-kits/native-kits 0.168.1-beta.1 → 0.168.1-beta.10-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 (161) hide show
  1. package/CLAUDE.md +7 -5
  2. package/build.gradle.kts +11 -0
  3. package/compose/build.gradle.kts +198 -0
  4. package/compose/build.gradle.kts.backup +198 -0
  5. package/compose/compose.podspec +47 -0
  6. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
  7. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
  8. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
  9. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
  10. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
  11. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
  22. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
  23. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
  24. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
  25. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
  26. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
  27. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
  28. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
  29. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
  30. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
  31. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
  32. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
  33. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
  34. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
  35. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
  36. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
  37. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
  38. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
  39. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
  40. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
  41. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
  42. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
  43. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
  44. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
  45. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
  46. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
  47. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +399 -0
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +228 -0
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +105 -0
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +399 -0
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
  121. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
  122. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
  123. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
  124. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
  125. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
  126. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
  127. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
  128. package/gradle/libs.versions.toml +69 -0
  129. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  130. package/gradle/wrapper/gradle-wrapper.properties +8 -0
  131. package/gradle.properties +26 -0
  132. package/gradlew +252 -0
  133. package/gradlew.bat +94 -0
  134. package/ios/Application/ApplicationEnvironment.swift +7 -6
  135. package/ios/Application/DesignSystemKits.swift +100 -0
  136. package/ios/Application/DesignSystemKitsPayload.swift +68 -0
  137. package/ios/Badge/BadgeRibbon.swift +8 -1
  138. package/ios/Extensions/Color++.swift +2 -4
  139. package/ios/Input/Input.swift +13 -3
  140. package/ios/Input/InputPhoneNumber.swift +1 -3
  141. package/ios/Input/InputSearch.swift +1 -2
  142. package/ios/Input/InputTextArea.swift +5 -3
  143. package/ios/InputMoney/InputMoney.swift +6 -7
  144. package/ios/Layout/ScreenLayout.swift +46 -11
  145. package/ios/Navigation/BottomTab/DuoBarCompression.swift +2 -3
  146. package/ios/Navigation/BottomTab/DuoTabBar.swift +94 -69
  147. package/ios/Navigation/Component/HeaderBackground.swift +22 -1
  148. package/ios/Navigation/StackScreen.swift +3 -7
  149. package/ios/Popup/PopupLayout.swift +0 -2
  150. package/ios/Stepper/Stepper.swift +1 -2
  151. package/ios/Template/TrustBanner/TrustBanner.swift +4 -2
  152. package/ios/Typography/FontScale.swift +14 -11
  153. package/ios/Typography/Text.swift +86 -12
  154. package/ios/Typography/Typography.swift +2 -2
  155. package/ios/native-kits.podspec +1 -1
  156. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  157. package/ios-demo/README.md +120 -0
  158. package/package.json +1 -1
  159. package/settings.gradle.kts +64 -0
  160. package/ios/Typography/FontScaleStore.swift +0 -58
  161. package/ios/Typography/KitDesignSystem.swift +0 -17
@@ -0,0 +1,100 @@
1
+ // File: ios/Application/DesignSystemKits.swift
2
+ // Created At: 2026-10-02 14:17:29 +07:00
3
+ // Created By: AI
4
+ // AI Agent: Claude Code
5
+ // Model: claude-opus-5-5
6
+
7
+ // AI-GENERATED START: app-wide design-system holder mirroring Compose's DesignSystemKits
8
+ import SwiftUI
9
+
10
+ /// Host-owned design-system inputs; mirrors Compose's `DesignSystemKitsConfigs` field for field.
11
+ public struct DesignSystemKitsConfigs: Equatable {
12
+ public var userScaleRate: CGFloat
13
+ public var useOSScaleRate: Bool
14
+ public var isWhiteList: Bool
15
+ public var designSystemConfig: KitConfig?
16
+
17
+ public init(
18
+ userScaleRate: CGFloat = 1,
19
+ useOSScaleRate: Bool = true,
20
+ isWhiteList: Bool = false,
21
+ designSystemConfig: KitConfig? = nil
22
+ ) {
23
+ self.userScaleRate = userScaleRate
24
+ self.useOSScaleRate = useOSScaleRate
25
+ self.isWhiteList = isWhiteList
26
+ self.designSystemConfig = designSystemConfig
27
+ }
28
+ }
29
+
30
+ /// App-wide holder for the SwiftUI kit, mirroring Compose's `DesignSystemKits`. The kit is sandboxed from the
31
+ /// host, so the host pushes `setConfigs(_:)` at startup and on every change; the OS Dynamic Type factor is
32
+ /// tracked here. Views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
33
+ /// applies it). Swift reserves `init`, so there is no `init(source:)` twin: the host observes and pushes.
34
+ public final class DesignSystemKits: ObservableObject {
35
+ public static let shared = DesignSystemKits()
36
+
37
+ @Published private var configs = DesignSystemKitsConfigs()
38
+ @Published public private(set) var osFontScale: CGFloat = DesignSystemKits.currentOsFontScale()
39
+
40
+ private init() {
41
+ NotificationCenter.default.addObserver(
42
+ self,
43
+ selector: #selector(contentSizeCategoryDidChange),
44
+ name: UIContentSizeCategory.didChangeNotification,
45
+ object: nil
46
+ )
47
+ }
48
+
49
+ public func setConfigs(_ configs: DesignSystemKitsConfigs) {
50
+ onMain {
51
+ // Re-assigning an unchanged value would still publish and invalidate every kit subtree.
52
+ guard self.configs != configs else { return }
53
+ self.configs = configs
54
+ }
55
+ }
56
+
57
+ public func getConfigs() -> DesignSystemKitsConfigs {
58
+ configs
59
+ }
60
+
61
+ public var fontScale: FontScale {
62
+ FontScale(
63
+ userScaleRate: configs.userScaleRate,
64
+ useOSScaleRate: configs.useOSScaleRate,
65
+ osFontScale: osFontScale
66
+ )
67
+ }
68
+
69
+ @objc private func contentSizeCategoryDidChange() {
70
+ let next = Self.currentOsFontScale()
71
+ guard next != osFontScale else { return }
72
+ osFontScale = next
73
+ }
74
+
75
+ /// Same factor Compose's iOS `ProvideOsFontScale` derives from
76
+ /// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
77
+ static func currentOsFontScale() -> CGFloat {
78
+ let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
79
+ return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
80
+ }
81
+
82
+ /// Synchronous when already on main — an async hop would defer the change by a frame.
83
+ private func onMain(_ work: @escaping () -> Void) {
84
+ if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
85
+ }
86
+ }
87
+
88
+ private struct DesignSystemKitsConfigsKey: EnvironmentKey {
89
+ /// Computed, never stored: an un-injected subtree still reads the live holder, just not reactively.
90
+ static var defaultValue: DesignSystemKitsConfigs { DesignSystemKits.shared.getConfigs() }
91
+ }
92
+
93
+ public extension EnvironmentValues {
94
+ /// Injected by `.momoFontScale()`; never set it directly (same trap as `\.momoFontScale`).
95
+ var designSystemKitsConfigs: DesignSystemKitsConfigs {
96
+ get { self[DesignSystemKitsConfigsKey.self] }
97
+ set { self[DesignSystemKitsConfigsKey.self] = newValue }
98
+ }
99
+ }
100
+ // AI-GENERATED END: app-wide design-system holder mirroring Compose's DesignSystemKits
@@ -0,0 +1,68 @@
1
+ // File: ios/Application/DesignSystemKitsPayload.swift
2
+ // Created At: 2026-10-02 15:40:00 +07:00
3
+ // Created By: AI
4
+ // AI Agent: Claude Code
5
+ // Model: claude-opus-5-5
6
+
7
+ // AI-GENERATED START: JSON entry point for hosts that can't share Swift types with the kit
8
+ import Foundation
9
+
10
+ public extension DesignSystemKits {
11
+ /// Applies configs pushed as JSON by a host that can't share Swift types with the kit (e.g. the KMP bridge).
12
+ /// Invalid JSON is ignored and the current configs are kept.
13
+ func setConfigs(json: String) {
14
+ guard let data = json.data(using: .utf8),
15
+ let payload = try? JSONDecoder().decode(DesignSystemKitsPayload.self, from: data) else { return }
16
+ setConfigs(payload.configs)
17
+ }
18
+ }
19
+
20
+ // Wire format; field names match the host's serializer (momo-app HostDesignSystemKits / DesignSystemData.kt).
21
+ private struct DesignSystemKitsPayload: Decodable {
22
+ let userScaleRate: CGFloat
23
+ let useOSScaleRate: Bool
24
+ let isWhiteList: Bool
25
+ let designSystemConfig: KitConfigPayload?
26
+
27
+ var configs: DesignSystemKitsConfigs {
28
+ DesignSystemKitsConfigs(
29
+ userScaleRate: userScaleRate,
30
+ useOSScaleRate: useOSScaleRate,
31
+ isWhiteList: isWhiteList,
32
+ designSystemConfig: designSystemConfig?.kitConfig
33
+ )
34
+ }
35
+ }
36
+
37
+ private struct KitConfigPayload: Decodable {
38
+ let trustBanner: TrustBannerPayload?
39
+ let headerBar: String?
40
+ let headerGradient: String?
41
+
42
+ var kitConfig: KitConfig {
43
+ KitConfig(trustBanner: trustBanner?.trustBannerData, headerBar: headerBar, headerGradient: headerGradient)
44
+ }
45
+ }
46
+
47
+ private struct TrustBannerPayload: Decodable {
48
+ let content: [String: String]
49
+ let subContent: [String: String]
50
+ let pciImage: String?
51
+ let sslImage: String?
52
+ let urlConfig: String?
53
+ let icons: [String]?
54
+ let momoImage: String?
55
+
56
+ var trustBannerData: TrustBannerData {
57
+ TrustBannerData(
58
+ content: content,
59
+ subContent: subContent,
60
+ pciImage: pciImage,
61
+ momoImage: momoImage,
62
+ sslImage: sslImage,
63
+ icons: icons,
64
+ urlConfig: urlConfig ?? ""
65
+ )
66
+ }
67
+ }
68
+ // AI-GENERATED END: JSON entry point for hosts that can't share Swift types with the kit
@@ -203,7 +203,9 @@ public struct BadgeRibbon: View {
203
203
  /// rotation) — the label gets the same scale applied a second time so it stays right-reading
204
204
  /// while only the ribbon shape flips.
205
205
  public struct RoundedBadgeRibbon: View {
206
+ @Environment(\.appTheme) private var theme
206
207
  @Environment(\.momoFontScale) private var fontScale
208
+ @Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
207
209
  let text: String
208
210
  let position: RibbonPosition
209
211
 
@@ -223,7 +225,12 @@ public struct RoundedBadgeRibbon: View {
223
225
 
224
226
  private var measuredTextSize: CGSize {
225
227
  let fontSize = fontScale(TypographyStyle.labelXsMedium.fontSize)
226
- let font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
228
+ let font = momoUIFont(
229
+ size: fontSize,
230
+ weight: .medium,
231
+ family: theme.font,
232
+ isWhiteList: designSystemKitsConfigs.isWhiteList
233
+ )
227
234
  return text.size(withAttributes: [.font: font])
228
235
  }
229
236
 
@@ -4,16 +4,14 @@ import Foundation
4
4
  private final class MoMoUIKitsBundleToken {}
5
5
 
6
6
  enum MoMoUIKitsResources {
7
+
7
8
  static let bundle: Bundle = {
8
9
  let bundleName = "MoMoUIKitsResources"
9
10
  let tokenBundle = Bundle(for: MoMoUIKitsBundleToken.self)
10
- let frameworkBundle = Bundle(identifier: "org.cocoapods.MoMoUIKits")
11
11
  let candidates = [
12
12
  Bundle.main.resourceURL,
13
13
  tokenBundle.resourceURL,
14
- tokenBundle.bundleURL,
15
- frameworkBundle?.resourceURL,
16
- frameworkBundle?.bundleURL
14
+ tokenBundle.bundleURL
17
15
  ]
18
16
 
19
17
  for candidate in candidates {
@@ -153,7 +153,7 @@ public struct Input: View {
153
153
  handleFocusChange(focused)
154
154
  })
155
155
  .keyboardType(keyboardType)
156
- .font(fontScale.font(size: 14, weight: fontWeight == .bold ? .bold : .regular))
156
+ .kitFont(fontWeight == .bold ? .actionSBold : .bodyDefaultRegular)
157
157
  .ignoreBoldTextSetting()
158
158
  .foregroundColor(getTextColor())
159
159
  .disabled(disabled || readOnly)
@@ -345,7 +345,12 @@ private struct SecureInputField: UIViewRepresentable {
345
345
  textField.spellCheckingType = .no
346
346
  textField.delegate = context.coordinator
347
347
  textField.keyboardType = keyboardType
348
- textField.font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight)
348
+ textField.font = momoUIFont(
349
+ size: fontSize,
350
+ weight: fontWeight,
351
+ family: context.environment.appTheme.font,
352
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
353
+ )
349
354
  textField.textColor = textColor
350
355
  textField.isEnabled = !isDisabled
351
356
  textField.setContentHuggingPriority(.defaultLow, for: .horizontal)
@@ -370,7 +375,12 @@ private struct SecureInputField: UIViewRepresentable {
370
375
  if textField.text != text {
371
376
  textField.text = text
372
377
  }
373
- let font = UIFont.systemFont(ofSize: fontSize, weight: fontWeight)
378
+ let font = momoUIFont(
379
+ size: fontSize,
380
+ weight: fontWeight,
381
+ family: context.environment.appTheme.font,
382
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
383
+ )
374
384
  if textField.font != font {
375
385
  textField.font = font
376
386
  }
@@ -104,9 +104,7 @@ public struct InputPhoneNumber: View {
104
104
  handleFocusChange(focused)
105
105
  })
106
106
  .keyboardType(.numberPad)
107
- .font(size == .small
108
- ? fontScale.font(size: 14, weight: .semibold)
109
- : fontScale.font(size: 18, weight: .bold))
107
+ .kitFont(size == .small ? .headerSSemibold : .headerMBold)
110
108
  .ignoreBoldTextSetting()
111
109
  .foregroundColor(theme.colors.text.default)
112
110
  .applyCursorColor(theme.colors.primary)
@@ -120,7 +120,6 @@ public struct InputSearchProps {
120
120
 
121
121
  public struct InputSearch: View {
122
122
  @Environment(\.appTheme) private var theme
123
- @Environment(\.momoFontScale) private var fontScale
124
123
  @Binding var text: String
125
124
  var buttonText: String = ""
126
125
  var showButtonText: Bool = false
@@ -263,7 +262,7 @@ public struct InputSearch: View {
263
262
  }, onCommit: onSubmit)
264
263
  .disabled(disabled)
265
264
  .foregroundColor(disabled ? theme.colors.text.disable : theme.colors.text.default)
266
- .font(.system(size: fontScale(14), weight: fontWeight))
265
+ .kitFont(size: 14, weight: fontWeight)
267
266
  .ignoreBoldTextSetting()
268
267
  .keyboardType(keyboardType)
269
268
  .applyCursorColor(theme.colors.primary)
@@ -41,9 +41,11 @@ struct UITextViewWrapper: UIViewRepresentable {
41
41
  uiView.tintColor = UIColor(cursorColor)
42
42
  // Read through `context.environment` rather than the global `scaleSize`: that is what makes
43
43
  // SwiftUI re-run updateUIView when the font scale changes.
44
- uiView.font = UIFont.systemFont(
45
- ofSize: context.environment.momoFontScale(16),
46
- weight: fontWeight == .bold ? .bold : .regular
44
+ uiView.font = momoUIFont(
45
+ size: context.environment.momoFontScale(16),
46
+ weight: fontWeight == .bold ? .bold : .regular,
47
+ family: context.environment.appTheme.font,
48
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
47
49
  )
48
50
  uiView.isEditable = !disabled
49
51
  if uiView.keyboardType != keyboardType {
@@ -356,14 +356,13 @@ private struct MoneyTextField: UIViewRepresentable {
356
356
  return numeric.isEmpty ? "" : formatNumberToMoney(numeric)
357
357
  }
358
358
 
359
- // Theme family as Compose `getFontFamily`: Momo Trust Sans only for whitelisted hosts, else system.
360
359
  private func font(_ context: Context) -> UIFont {
361
- let size = context.environment.momoFontScale(20)
362
- if KitDesignSystem.shared.isWhiteList, fontFamily == "MomoTrustSans",
363
- let font = UIFont(name: fontWeight == .bold ? "MomoTrustSans-Bold" : "MomoTrustSans-Regular", size: size) {
364
- return font
365
- }
366
- return .systemFont(ofSize: size, weight: fontWeight)
360
+ momoUIFont(
361
+ size: context.environment.momoFontScale(20),
362
+ weight: fontWeight,
363
+ family: fontFamily,
364
+ isWhiteList: context.environment.designSystemKitsConfigs.isWhiteList
365
+ )
367
366
  }
368
367
 
369
368
  final class Coordinator: NSObject, UITextFieldDelegate {
@@ -32,6 +32,8 @@ public struct ScreenLayout: Equatable {
32
32
  public let width: CGFloat
33
33
  public let height: CGFloat
34
34
  public let isIPhoneDuo: Bool
35
+ /// The device has a hinge, folded or not, whatever the window size; drives popups, modal and bottom sheet.
36
+ public let isDuoDevice: Bool
35
37
  public let insets: EdgeInsets
36
38
  public let insetLeft: CGFloat
37
39
  public let insetRight: CGFloat
@@ -68,8 +70,11 @@ enum DuoMetrics {
68
70
  // (x 550–643 of 669, clock digits centered on y 47); its reserved region is taller than the glyphs.
69
71
  static let innerStatusBarCenterY: CGFloat = 47
70
72
  static let innerStatusBarWidth: CGFloat = 120
71
- // Widest short side of any single-display iPhone.
72
- static let iPhoneMaxShortSide: CGFloat = 440
73
+ // Host app's iOS 26 tab bar on the inner display in portrait: a 62pt glass pill sized to its tabs,
74
+ // QR as a 62pt platter beside it, ≥ 20pt from the sides, 13pt into the home indicator inset.
75
+ static let floatingTabBarHeight: CGFloat = 62
76
+ static let floatingTabBarMargin: CGFloat = 20
77
+ static let floatingTabBarInsetOverlap: CGFloat = 13
73
78
  }
74
79
 
75
80
  enum HeaderMetrics {
@@ -104,15 +109,17 @@ extension ScreenLayout {
104
109
  size: CGSize,
105
110
  insets: EdgeInsets,
106
111
  reservedRegions: [CGRect] = ReservedRegions.current,
107
- isPhone: Bool = isPhoneIdiom
112
+ isPhone: Bool = isPhoneIdiom,
113
+ isFoldable: Bool = FoldableDevice.isFoldable(measuredIn: keyWindow)
108
114
  ) -> ScreenLayout {
109
115
  let width = size.width
110
116
  let height = size.height
117
+ let isDuoDevice = isPhone && isFoldable
111
118
  var (headerHeight, headerStatusBarHeight) = headerGeometry(topInset: insets.top, isPhone: isPhone)
112
119
  let isDuo = isPhone && isIPhoneDuoWindow(width: width, height: height, insets: insets)
113
120
  guard isDuo else {
114
121
  return ScreenLayout(
115
- width: width, height: height, isIPhoneDuo: false, insets: insets,
122
+ width: width, height: height, isIPhoneDuo: false, isDuoDevice: isDuoDevice, insets: insets,
116
123
  insetLeft: 0, insetRight: 0, contentWidth: width,
117
124
  headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: nil,
118
125
  statusBarClearance: nil
@@ -172,7 +179,7 @@ extension ScreenLayout {
172
179
  }
173
180
 
174
181
  return ScreenLayout(
175
- width: width, height: height, isIPhoneDuo: true, insets: insets,
182
+ width: width, height: height, isIPhoneDuo: true, isDuoDevice: isDuoDevice, insets: insets,
176
183
  insetLeft: insets.leading, insetRight: insets.trailing,
177
184
  contentWidth: width - insets.leading - insets.trailing,
178
185
  headerHeight: headerHeight, headerStatusBarHeight: headerStatusBarHeight, sideBar: sideBar,
@@ -251,13 +258,40 @@ extension ScreenLayout {
251
258
  }
252
259
  }
253
260
 
254
- /// Port of RN `device.ts` `isIPhoneDuo()`: UIScreen stays on the outer display, so this
255
- /// still matches when Split View narrows the window. Used by popups and overlays.
261
+ /// Snapshot for call sites without `\.screenLayout`; not reactive. `windowSize` is ignored,
262
+ /// kept for host call sites.
256
263
  public func isIPhoneDuoDevice(windowSize: CGSize? = nil) -> Bool {
257
- guard isPhoneIdiom else { return false }
258
- let screen = UIScreen.main.bounds.size
259
- let window = windowSize ?? keyWindow?.bounds.size ?? screen
260
- return max(min(screen.width, screen.height), min(window.width, window.height)) > DuoMetrics.iPhoneMaxShortSide
264
+ isPhoneIdiom && FoldableDevice.isFoldable(measuredIn: keyWindow)
265
+ }
266
+
267
+ /// Apple's foldable check (as the host's `SideTabRailView.isFoldableDevice`): only a device with a
268
+ /// hinge reports a `.division` region, inactive while flat but still there. Screen size cannot tell
269
+ /// it apart from a large iPhone in landscape or a narrowed window.
270
+ enum FoldableDevice {
271
+ // a hinge never goes away, so a positive answer is kept
272
+ private static var isKnownFoldable = false
273
+
274
+ static func isFoldable(measuredIn view: UIView?) -> Bool {
275
+ if isKnownFoldable { return true }
276
+ // compiled out on SDKs older than iOS 27.1 (UIKit 9127.0.85.28)
277
+ #if canImport(UIKit, _version: 9127.0.85.28)
278
+ guard #available(iOS 27.1, *), let view, view.window != nil || view is UIWindow else { return false }
279
+ isKnownFoldable = !view.reservedRegions(kind: .division, options: .includeInactive).isEmpty
280
+ #endif
281
+ return isKnownFoldable
282
+ }
283
+
284
+ /// Catches the hinge when the regions were read too early (before the window had them).
285
+ static func observe(in view: UIView) {
286
+ #if canImport(UIKit, _version: 9127.0.85.28)
287
+ guard #available(iOS 27.1, *) else { return }
288
+ view.addInteraction(UIHingeInteraction { _, update in
289
+ guard update.hinge != nil, !isKnownFoldable else { return }
290
+ isKnownFoldable = true
291
+ NotificationCenter.default.post(name: ReservedRegions.didChange, object: nil)
292
+ })
293
+ #endif
294
+ }
261
295
  }
262
296
 
263
297
  // MARK: - Reserved regions
@@ -282,6 +316,7 @@ private final class ReservedRegionsProbe: UIView {
282
316
  override init(frame: CGRect) {
283
317
  super.init(frame: frame)
284
318
  isUserInteractionEnabled = false
319
+ FoldableDevice.observe(in: self)
285
320
  }
286
321
 
287
322
  required init?(coder: NSCoder) { nil }
@@ -35,8 +35,6 @@ struct DuoBarCompression {
35
35
  static let defaultToolbarHeight = DuoMetrics.controlSize + Spacing.M + 2 * DuoMetrics.itemPitch
36
36
  /// Back, then the single button the toolbar folds into.
37
37
  private static let compressedToolbarHeight = 2 * DuoMetrics.controlSize + Spacing.M
38
- /// Back alone, while a rail opened from the compressed state takes the toolbar slot.
39
- private static let backOnlyHeight = DuoMetrics.controlSize
40
38
 
41
39
  /// - both fit: both stay whole
42
40
  /// - one fits whole beside the other compressed: one is compressed, tapping it swaps them
@@ -72,7 +70,8 @@ struct DuoBarCompression {
72
70
 
73
71
  let expandToolbar = { opened.wrappedValue = .toolbar }
74
72
  let expandTabBar = { opened.wrappedValue = .tabBar }
75
- if opened.wrappedValue == .tabBar && fits(backOnlyHeight, tabBar) {
73
+ // an opened rail always shows, even when only the group gap is short: it closes once a tab is picked
74
+ if opened.wrappedValue == .tabBar {
76
75
  return DuoBarCompression(
77
76
  toolbarCompressed: true,
78
77
  tabBarCompressed: false,