@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.
- package/CLAUDE.md +7 -5
- package/build.gradle.kts +11 -0
- package/compose/build.gradle.kts +198 -0
- package/compose/build.gradle.kts.backup +198 -0
- package/compose/compose.podspec +47 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +20 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +12 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +7 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +153 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +110 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +1071 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +269 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +81 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +32 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +175 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +357 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +115 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +89 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +128 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +209 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +542 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +49 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +231 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +189 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +478 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +114 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +235 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +223 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +265 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +185 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +343 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +56 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +84 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +40 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +361 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +308 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +59 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +21 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +90 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +330 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +220 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +473 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +122 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +560 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +125 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +207 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +501 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +176 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +182 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +193 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +29 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +236 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +180 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +325 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +16 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +194 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +287 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +54 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +50 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +41 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +52 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +66 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +11 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +51 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +58 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +137 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +205 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +399 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +732 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +228 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +217 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +169 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +455 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +105 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +106 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +399 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +39 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +363 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +133 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +127 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +12 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +91 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +9 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +74 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +1329 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +55 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +15 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +118 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +42 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +45 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +32 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +38 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +66 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +217 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +69 -0
- package/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/gradle.properties +26 -0
- package/gradlew +252 -0
- package/gradlew.bat +94 -0
- package/ios/Application/ApplicationEnvironment.swift +7 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Badge/BadgeRibbon.swift +8 -1
- package/ios/Extensions/Color++.swift +2 -4
- package/ios/Input/Input.swift +13 -3
- package/ios/Input/InputPhoneNumber.swift +1 -3
- package/ios/Input/InputSearch.swift +1 -2
- package/ios/Input/InputTextArea.swift +5 -3
- package/ios/InputMoney/InputMoney.swift +6 -7
- package/ios/Layout/ScreenLayout.swift +46 -11
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +2 -3
- package/ios/Navigation/BottomTab/DuoTabBar.swift +94 -69
- package/ios/Navigation/Component/HeaderBackground.swift +22 -1
- package/ios/Navigation/StackScreen.swift +3 -7
- package/ios/Popup/PopupLayout.swift +0 -2
- package/ios/Stepper/Stepper.swift +1 -2
- package/ios/Template/TrustBanner/TrustBanner.swift +4 -2
- package/ios/Typography/FontScale.swift +14 -11
- package/ios/Typography/Text.swift +86 -12
- package/ios/Typography/Typography.swift +2 -2
- package/ios/native-kits.podspec +1 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- package/ios-demo/README.md +120 -0
- package/package.json +1 -1
- package/settings.gradle.kts +64 -0
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// MoMoUIKits
|
|
4
4
|
//
|
|
5
5
|
// iPhone Duo tab bar, port of RN `BottomTab/DuoTabBar.tsx` (Figma Fold-Design-System MT1/MT2):
|
|
6
|
-
// a
|
|
6
|
+
// a rail in the side bar, or a floating bar when unfolded portrait.
|
|
7
7
|
//
|
|
8
8
|
|
|
9
9
|
import SwiftUI
|
|
@@ -16,12 +16,15 @@ private let railGap = Spacing.XXS
|
|
|
16
16
|
// selection bubble overflows the 48 rail cell by 5 above and below (Figma MT2)
|
|
17
17
|
private let railBubbleWidth: CGFloat = 44
|
|
18
18
|
private let railBubbleHeight: CGFloat = 58
|
|
19
|
-
let
|
|
19
|
+
private let floatingHeight = DuoMetrics.floatingTabBarHeight
|
|
20
20
|
private let floatingPadding = Spacing.XS
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
private let
|
|
21
|
+
// the system bar sizes its pill to the tabs instead of stretching it across the display
|
|
22
|
+
private let floatingItemWidth: CGFloat = 80
|
|
23
|
+
// while pressed the selection bubble grows into a lens past the pill's edges
|
|
24
|
+
private let lensScaleX: CGFloat = 1.2
|
|
25
|
+
private let lensScaleY: CGFloat = 1.3
|
|
26
|
+
// Figma Home-2026 tab bar "Selection": brand pink at 8%
|
|
27
|
+
private let selectionTintOpacity: Double = 0x14 / 255
|
|
25
28
|
private let tabSpring = Animation.interpolatingSpring(mass: 0.8, stiffness: 220, damping: 18)
|
|
26
29
|
|
|
27
30
|
/// Room tab screens leave for the Duo tab bar, provided by BottomTab to its tab StackScreens.
|
|
@@ -51,47 +54,34 @@ func duoRailHeight(itemCount: Int, hasQr: Bool, compressed: Bool = false) -> CGF
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
extension ScreenLayout {
|
|
57
|
+
/// Overlaps the home indicator inset like the system tab bar (21pt above the bottom with a 34pt inset).
|
|
58
|
+
var duoFloatingTabBarBottom: CGFloat {
|
|
59
|
+
max(insets.bottom - DuoMetrics.floatingTabBarInsetOverlap, Spacing.S)
|
|
60
|
+
}
|
|
61
|
+
|
|
54
62
|
func duoTabBarInsets(itemCount: Int, hasFloatingButton: Bool, tabBarCompressed: Bool = false) -> DuoTabBarInsets {
|
|
55
63
|
guard isIPhoneDuo else { return DuoTabBarInsets() }
|
|
56
64
|
guard let sideBar else {
|
|
57
|
-
|
|
65
|
+
// overlay is measured from the bottom safe area, the bar reaches into it
|
|
66
|
+
return DuoTabBarInsets(overlay: floatingHeight + duoFloatingTabBarBottom - insets.bottom)
|
|
58
67
|
}
|
|
59
68
|
let rail = duoRailHeight(itemCount: itemCount, hasQr: hasFloatingButton, compressed: tabBarCompressed)
|
|
60
69
|
return DuoTabBarInsets(sideBarReserve: sideBar.bottom + rail + DuoBarCompression.groupGap)
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
|
|
64
|
-
///
|
|
65
|
-
struct DuoBarMaterial<S:
|
|
73
|
+
/// RN `BarMaterial`: surface with a 1pt border.
|
|
74
|
+
struct DuoBarMaterial<S: InsettableShape>: ViewModifier {
|
|
66
75
|
let shape: S
|
|
67
|
-
var visible: Bool = true
|
|
68
|
-
|
|
69
|
-
func body(content: Content) -> some View {
|
|
70
|
-
content.background(material.allowsHitTesting(false))
|
|
71
|
-
}
|
|
72
76
|
|
|
73
|
-
@
|
|
74
|
-
private var material: some View {
|
|
75
|
-
#if compiler(>=6.2)
|
|
76
|
-
if #available(iOS 26.0, *) {
|
|
77
|
-
// glass materializes by toggling its effect, it does not fade with opacity
|
|
78
|
-
Color.clear
|
|
79
|
-
.glassEffect(visible ? .regular : .identity, in: shape)
|
|
80
|
-
.shadow(color: Color.black.opacity(visible ? 0.16 : 0), radius: 16, x: 0, y: 6)
|
|
81
|
-
.animation(.easeInOut(duration: 0.25), value: visible)
|
|
82
|
-
} else {
|
|
83
|
-
blur
|
|
84
|
-
}
|
|
85
|
-
#else
|
|
86
|
-
blur
|
|
87
|
-
#endif
|
|
88
|
-
}
|
|
77
|
+
@Environment(\.appTheme) private var theme
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
func body(content: Content) -> some View {
|
|
80
|
+
content.background(
|
|
81
|
+
shape.fill(theme.colors.background.surface)
|
|
82
|
+
.overlay(shape.strokeBorder(theme.colors.border.default, lineWidth: 1))
|
|
83
|
+
.allowsHitTesting(false)
|
|
84
|
+
)
|
|
95
85
|
}
|
|
96
86
|
}
|
|
97
87
|
|
|
@@ -126,7 +116,7 @@ private struct DuoRailTabBar: View {
|
|
|
126
116
|
let onTabSelected: (Int) -> Void
|
|
127
117
|
let compression: DuoBarCompression
|
|
128
118
|
|
|
129
|
-
@
|
|
119
|
+
@State private var pressedIndex: Int?
|
|
130
120
|
|
|
131
121
|
private var isRight: Bool { sideBar.edge == .right }
|
|
132
122
|
|
|
@@ -157,15 +147,15 @@ private struct DuoRailTabBar: View {
|
|
|
157
147
|
.ignoresSafeArea()
|
|
158
148
|
}
|
|
159
149
|
|
|
160
|
-
///
|
|
150
|
+
/// Column of icon-only tab items with the sliding selection bubble.
|
|
161
151
|
private var rail: some View {
|
|
162
152
|
ZStack(alignment: .top) {
|
|
163
|
-
|
|
164
|
-
.fill(theme.colors.background.selected)
|
|
153
|
+
DuoSelectionMaterial(lens: pressedIndex != nil)
|
|
165
154
|
.frame(width: railBubbleWidth, height: railBubbleHeight)
|
|
166
155
|
.offset(y: railPadding - (railBubbleHeight - itemSize) / 2
|
|
167
|
-
+ CGFloat(selectedIndex) * (itemSize + railGap))
|
|
156
|
+
+ CGFloat(pressedIndex ?? selectedIndex) * (itemSize + railGap))
|
|
168
157
|
.animation(tabSpring, value: selectedIndex)
|
|
158
|
+
.animation(tabSpring, value: pressedIndex)
|
|
169
159
|
|
|
170
160
|
VStack(spacing: railGap) {
|
|
171
161
|
ForEach(items.indices, id: \.self) { index in
|
|
@@ -177,19 +167,19 @@ private struct DuoRailTabBar: View {
|
|
|
177
167
|
.frame(width: itemSize, height: itemSize)
|
|
178
168
|
.contentShape(Rectangle())
|
|
179
169
|
}
|
|
180
|
-
.buttonStyle(
|
|
170
|
+
.buttonStyle(DuoLensPressStyle(index: index, pressedIndex: $pressedIndex))
|
|
181
171
|
.accessibilityLabel(items[index].label)
|
|
182
172
|
}
|
|
183
173
|
}
|
|
184
174
|
.padding(.vertical, railPadding)
|
|
185
175
|
}
|
|
186
176
|
.frame(width: itemSize)
|
|
187
|
-
.modifier(DuoBarMaterial(shape:
|
|
177
|
+
.modifier(DuoBarMaterial(shape: Capsule()))
|
|
188
178
|
.accessibilityIdentifier("bottom_tab_bar")
|
|
189
179
|
}
|
|
190
180
|
}
|
|
191
181
|
|
|
192
|
-
/// Floating labelled bar for the unfolded portrait display, QR in
|
|
182
|
+
/// Floating labelled bar for the unfolded portrait display, QR pinned in its own platter beside it.
|
|
193
183
|
private struct DuoFloatingTabBar: View {
|
|
194
184
|
let items: [BottomTabItem]
|
|
195
185
|
let floatingButton: BottomTabFloatingButton?
|
|
@@ -198,45 +188,48 @@ private struct DuoFloatingTabBar: View {
|
|
|
198
188
|
|
|
199
189
|
@Environment(\.screenLayout) private var layout
|
|
200
190
|
@Environment(\.appTheme) private var theme
|
|
191
|
+
@State private var pressedIndex: Int?
|
|
201
192
|
|
|
202
193
|
private var barWidth: CGFloat {
|
|
203
|
-
let
|
|
204
|
-
|
|
194
|
+
let margins = layout.insets.leading + layout.insets.trailing + 2 * DuoMetrics.floatingTabBarMargin
|
|
195
|
+
let qr = floatingButton == nil ? 0 : floatingHeight + Spacing.S
|
|
196
|
+
let preferred = CGFloat(items.count) * floatingItemWidth + 2 * floatingPadding
|
|
197
|
+
return max(min(preferred, layout.width - margins - qr), 0)
|
|
205
198
|
}
|
|
206
199
|
|
|
207
|
-
private var
|
|
208
|
-
private var middle: Int { items.count / 2 }
|
|
209
|
-
private var cellCount: Int { items.count + (useQr ? 1 : 0) }
|
|
210
|
-
private var cellWidth: CGFloat { (barWidth - floatingPadding * 2) / CGFloat(max(cellCount, 1)) }
|
|
211
|
-
private var visualIndex: Int { useQr && selectedIndex >= middle ? selectedIndex + 1 : selectedIndex }
|
|
200
|
+
private var cellWidth: CGFloat { (barWidth - floatingPadding * 2) / CGFloat(max(items.count, 1)) }
|
|
212
201
|
|
|
213
202
|
var body: some View {
|
|
203
|
+
HStack(spacing: Spacing.S) {
|
|
204
|
+
bar
|
|
205
|
+
if let floatingButton {
|
|
206
|
+
DuoQrButton(data: floatingButton, size: floatingHeight)
|
|
207
|
+
.shadow(color: Color.black.opacity(0.16), radius: 16, x: 0, y: 6)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
.padding(.bottom, layout.duoFloatingTabBarBottom)
|
|
211
|
+
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
|
|
212
|
+
.ignoresSafeArea()
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private var bar: some View {
|
|
214
216
|
ZStack(alignment: .leading) {
|
|
215
|
-
|
|
216
|
-
.fill(theme.colors.background.selected)
|
|
217
|
+
DuoSelectionMaterial(lens: pressedIndex != nil)
|
|
217
218
|
.frame(width: cellWidth)
|
|
218
|
-
.offset(x: CGFloat(
|
|
219
|
-
.animation(tabSpring, value:
|
|
219
|
+
.offset(x: CGFloat(pressedIndex ?? selectedIndex) * cellWidth)
|
|
220
|
+
.animation(tabSpring, value: selectedIndex)
|
|
221
|
+
.animation(tabSpring, value: pressedIndex)
|
|
220
222
|
|
|
221
223
|
HStack(spacing: 0) {
|
|
222
|
-
ForEach(
|
|
223
|
-
|
|
224
|
-
DuoQrButton(data: floatingButton, size: floatingQrSize)
|
|
225
|
-
.frame(width: cellWidth)
|
|
226
|
-
} else {
|
|
227
|
-
let index = useQr && cell > middle ? cell - 1 : cell
|
|
228
|
-
tabCell(index)
|
|
229
|
-
}
|
|
224
|
+
ForEach(items.indices, id: \.self) { index in
|
|
225
|
+
tabCell(index)
|
|
230
226
|
}
|
|
231
227
|
}
|
|
232
228
|
}
|
|
233
229
|
.padding(floatingPadding)
|
|
234
|
-
.frame(width: barWidth, height:
|
|
230
|
+
.frame(width: barWidth, height: floatingHeight)
|
|
235
231
|
.modifier(DuoBarMaterial(shape: Capsule()))
|
|
236
232
|
.accessibilityIdentifier("bottom_tab_bar")
|
|
237
|
-
.padding(.bottom, layout.insets.bottom + Spacing.M)
|
|
238
|
-
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
|
|
239
|
-
.ignoresSafeArea()
|
|
240
233
|
}
|
|
241
234
|
|
|
242
235
|
private func tabCell(_ index: Int) -> some View {
|
|
@@ -247,20 +240,52 @@ private struct DuoFloatingTabBar: View {
|
|
|
247
240
|
MomoText(
|
|
248
241
|
items[index].label,
|
|
249
242
|
typography: .labelXsMedium,
|
|
250
|
-
color: selected ? theme.colors.primary : theme.colors.text.
|
|
243
|
+
color: selected ? theme.colors.primary : theme.colors.text.hint,
|
|
251
244
|
textAlign: .center,
|
|
252
245
|
maxLines: 1
|
|
253
246
|
)
|
|
247
|
+
// like the system tab bar the label keeps its design size; a constant snapshot, nothing to observe
|
|
248
|
+
.environment(\.momoFontScale, FontScale(userScaleRate: 1, useOSScaleRate: false))
|
|
254
249
|
}
|
|
255
250
|
.frame(width: cellWidth)
|
|
256
251
|
.frame(maxHeight: .infinity)
|
|
257
252
|
.contentShape(Rectangle())
|
|
258
253
|
}
|
|
259
|
-
.buttonStyle(
|
|
254
|
+
.buttonStyle(DuoLensPressStyle(index: index, pressedIndex: $pressedIndex))
|
|
260
255
|
.accessibilityLabel(items[index].label)
|
|
261
256
|
}
|
|
262
257
|
}
|
|
263
258
|
|
|
259
|
+
/// Pink tinted capsule under the selected tab; while pressed it grows into a lens (80x54 to about 96x70).
|
|
260
|
+
private struct DuoSelectionMaterial: View {
|
|
261
|
+
let lens: Bool
|
|
262
|
+
|
|
263
|
+
@Environment(\.appTheme) private var theme
|
|
264
|
+
|
|
265
|
+
var body: some View {
|
|
266
|
+
Capsule()
|
|
267
|
+
.fill(theme.colors.primary.opacity(selectionTintOpacity))
|
|
268
|
+
.scaleEffect(x: lens ? lensScaleX : 1, y: lens ? lensScaleY : 1)
|
|
269
|
+
.allowsHitTesting(false)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Tracks the finger so the selection lens jumps to the pressed tab before it is selected.
|
|
274
|
+
private struct DuoLensPressStyle: ButtonStyle {
|
|
275
|
+
let index: Int
|
|
276
|
+
@Binding var pressedIndex: Int?
|
|
277
|
+
|
|
278
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
279
|
+
configuration.label.onChange(of: configuration.isPressed) { pressed in
|
|
280
|
+
if pressed {
|
|
281
|
+
pressedIndex = index
|
|
282
|
+
} else if pressedIndex == index {
|
|
283
|
+
pressedIndex = nil
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
264
289
|
private struct DuoTabIcon: View {
|
|
265
290
|
let item: BottomTabItem
|
|
266
291
|
let selected: Bool
|
|
@@ -274,7 +299,7 @@ private struct DuoTabIcon: View {
|
|
|
274
299
|
}
|
|
275
300
|
|
|
276
301
|
var body: some View {
|
|
277
|
-
Icon(source: item.icon, size: iconSize, color: selected ? theme.colors.primary : theme.colors.text.
|
|
302
|
+
Icon(source: item.icon, size: iconSize, color: selected ? theme.colors.primary : theme.colors.text.hint)
|
|
278
303
|
.overlay(alignment: .topTrailing) {
|
|
279
304
|
if let badgeLabel, !badgeLabel.isEmpty {
|
|
280
305
|
Badge(label: badgeLabel).offset(x: Spacing.S, y: -Spacing.XS)
|
|
@@ -90,6 +90,27 @@ private let duoGradientExtra: CGFloat = 52
|
|
|
90
90
|
// scroll distance over which the extended gradient collapses to the header
|
|
91
91
|
private let duoExtendedCollapseDistance: CGFloat = 100
|
|
92
92
|
|
|
93
|
+
// scroll distance over which the bar fades in
|
|
94
|
+
private let duoBarFadeDistance: CGFloat = 16
|
|
95
|
+
|
|
96
|
+
/// Port of RN `DuoGlassBackground`: translucent surface with a hairline, shown once the content
|
|
97
|
+
/// scrolls `fadeStart` under the header.
|
|
98
|
+
struct DuoHeaderBar: View {
|
|
99
|
+
let scrollOffset: CGFloat
|
|
100
|
+
var fadeStart: CGFloat = 0
|
|
101
|
+
|
|
102
|
+
@Environment(\.appTheme) private var theme
|
|
103
|
+
|
|
104
|
+
var body: some View {
|
|
105
|
+
theme.colors.background.surface.opacity(0.8)
|
|
106
|
+
.overlay(alignment: .bottom) {
|
|
107
|
+
theme.colors.border.default.frame(height: 0.5)
|
|
108
|
+
}
|
|
109
|
+
.opacity(min(max((scrollOffset - fadeStart) / duoBarFadeDistance, 0), 1))
|
|
110
|
+
.allowsHitTesting(false)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
93
114
|
/// Port of RN `DuoScrollGradient`: with the glass header the content scrolls under the bar, so the
|
|
94
115
|
/// header gradient is drawn under it. Compose draws this inside `HeaderBackground`.
|
|
95
116
|
struct DuoScrollGradient: View {
|
|
@@ -114,7 +135,7 @@ struct DuoScrollGradient: View {
|
|
|
114
135
|
let progress = min(scroll / duoExtendedCollapseDistance, 1)
|
|
115
136
|
gradient
|
|
116
137
|
.frame(height: headerHeight + (extended ? duoGradientExtra * (1 - progress) : 0))
|
|
117
|
-
.opacity(1 - min(scroll / duoGradientExtra, 1))
|
|
138
|
+
.opacity(Double(1 - min(scroll / duoGradientExtra, 1)))
|
|
118
139
|
}
|
|
119
140
|
}
|
|
120
141
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
|
@@ -403,11 +403,8 @@ public struct StackScreen<Content: View>: View {
|
|
|
403
403
|
.background(alignment: .top) {
|
|
404
404
|
Group {
|
|
405
405
|
if layout.sideBar != nil {
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
shape: Rectangle(),
|
|
409
|
-
visible: scrollOffset > heroGlassStart(animatedHeader)
|
|
410
|
-
))
|
|
406
|
+
// the bar shows once the banner has scrolled under the header
|
|
407
|
+
DuoHeaderBar(scrollOffset: scrollOffset, fadeStart: heroGlassStart(animatedHeader))
|
|
411
408
|
} else {
|
|
412
409
|
HeaderBarBackground(opacity: opacity)
|
|
413
410
|
}
|
|
@@ -457,8 +454,7 @@ public struct StackScreen<Content: View>: View {
|
|
|
457
454
|
|
|
458
455
|
header
|
|
459
456
|
.background(
|
|
460
|
-
|
|
461
|
-
.modifier(DuoBarMaterial(shape: Rectangle(), visible: scrollOffset > 0))
|
|
457
|
+
DuoHeaderBar(scrollOffset: scrollOffset)
|
|
462
458
|
.edgesIgnoringSafeArea([.top, .horizontal])
|
|
463
459
|
)
|
|
464
460
|
.offset(y: -headerHide)
|
|
@@ -5,8 +5,6 @@ import SwiftUI
|
|
|
5
5
|
private let duoPopupMaxWidth: CGFloat = 440
|
|
6
6
|
|
|
7
7
|
extension ScreenLayout {
|
|
8
|
-
var isDuoDevice: Bool { isIPhoneDuoDevice(windowSize: CGSize(width: width, height: height)) }
|
|
9
|
-
|
|
10
8
|
/// Card width cap (Compose `popupMaxWidth`); unbounded off iPhone Duo, where the popup's own padding sets the width.
|
|
11
9
|
var popupMaxWidth: CGFloat {
|
|
12
10
|
isDuoDevice ? duoPopupMaxWidth : .infinity
|
|
@@ -84,7 +84,6 @@ private struct StepperButton: View {
|
|
|
84
84
|
|
|
85
85
|
private struct StepperNumberView: View {
|
|
86
86
|
@Environment(\.appTheme) private var theme
|
|
87
|
-
@Environment(\.momoFontScale) private var fontScale
|
|
88
87
|
|
|
89
88
|
@Binding var text: String
|
|
90
89
|
var size: StepperSize
|
|
@@ -106,7 +105,7 @@ private struct StepperNumberView: View {
|
|
|
106
105
|
MomoText(text, typography: .descriptionDefaultRegular, color: textColor)
|
|
107
106
|
}
|
|
108
107
|
}
|
|
109
|
-
.
|
|
108
|
+
.kitFont(size: 12)
|
|
110
109
|
.ignoreBoldTextSetting()
|
|
111
110
|
.foregroundColor(textColor)
|
|
112
111
|
// Compose's min width includes the horizontal padding.
|
|
@@ -11,7 +11,7 @@ import SDWebImageSwiftUI
|
|
|
11
11
|
|
|
12
12
|
let jsonUrl = "https://static.momocdn.net/app/json/component-kits/design_system.json"
|
|
13
13
|
|
|
14
|
-
public struct TrustBannerData: Decodable {
|
|
14
|
+
public struct TrustBannerData: Decodable, Equatable {
|
|
15
15
|
public let content: [String: String]
|
|
16
16
|
public let subContent: [String: String]
|
|
17
17
|
public let pciImage: String?
|
|
@@ -85,6 +85,7 @@ let defaultBanner = TrustBannerData(
|
|
|
85
85
|
public struct TrustBanner: View {
|
|
86
86
|
@Environment(\.applicationEnvironment) var applicationEnvironment
|
|
87
87
|
@Environment(\.localize) private var localize
|
|
88
|
+
@Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
|
|
88
89
|
|
|
89
90
|
var onPress: ((String)->Void)?
|
|
90
91
|
private let serviceName: String
|
|
@@ -104,7 +105,8 @@ public struct TrustBanner: View {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
private var banner: TrustBannerData {
|
|
107
|
-
|
|
108
|
+
// Container config wins, else the host-pushed DesignSystemKits config (Compose parity).
|
|
109
|
+
(applicationEnvironment.config ?? designSystemKitsConfigs.designSystemConfig)?.trustBanner ?? defaultBanner
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
func onPressSecurity() {
|
|
@@ -52,7 +52,7 @@ public struct FontScale: Equatable {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/// A host-pushed mini-app context wins per field, matching Compose's
|
|
55
|
-
/// `context?.useOSScaleRate ?:
|
|
55
|
+
/// `context?.useOSScaleRate ?: DesignSystemKits.getConfigs().useOSScaleRate`.
|
|
56
56
|
public func overriding(userScaleRate: CGFloat?, useOSScaleRate: Bool?) -> FontScale {
|
|
57
57
|
guard userScaleRate != nil || useOSScaleRate != nil else { return self }
|
|
58
58
|
return FontScale(
|
|
@@ -66,7 +66,7 @@ public struct FontScale: Equatable {
|
|
|
66
66
|
private struct FontScaleKey: EnvironmentKey {
|
|
67
67
|
/// Computed, never stored: a bare `UIHostingController` root that never got `.momoFontScale()`
|
|
68
68
|
/// still reads the live store instead of a value frozen at first access.
|
|
69
|
-
static var defaultValue: FontScale {
|
|
69
|
+
static var defaultValue: FontScale { DesignSystemKits.shared.fontScale }
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
public extension EnvironmentValues {
|
|
@@ -77,9 +77,10 @@ public extension EnvironmentValues {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
public extension View {
|
|
80
|
-
/// Injects a Dynamic-Type-observing font scale
|
|
80
|
+
/// Injects a Dynamic-Type-observing font scale plus `\.designSystemKitsConfigs` (whitelist, remote config)
|
|
81
|
+
/// and re-renders this subtree on change.
|
|
81
82
|
/// Use at every `UIHostingController` root that is not inside a `NavigationContainer`.
|
|
82
|
-
func momoFontScale(_ store:
|
|
83
|
+
func momoFontScale(_ store: DesignSystemKits = .shared, context: MiniAppContext? = nil) -> some View {
|
|
83
84
|
modifier(MomoFontScaleModifier(store: store, context: context))
|
|
84
85
|
}
|
|
85
86
|
}
|
|
@@ -87,17 +88,19 @@ public extension View {
|
|
|
87
88
|
/// `@ObservedObject` is load-bearing, same as `MomoLocalizeModifier`: `@Environment(\.momoFontScale)`
|
|
88
89
|
/// only invalidates when the boxed snapshot changes, which requires *this* body to re-run on publish.
|
|
89
90
|
private struct MomoFontScaleModifier: ViewModifier {
|
|
90
|
-
@ObservedObject var store:
|
|
91
|
+
@ObservedObject var store: DesignSystemKits
|
|
91
92
|
let context: MiniAppContext?
|
|
92
93
|
|
|
93
94
|
func body(content: Content) -> some View {
|
|
94
|
-
content
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
content
|
|
96
|
+
.environment(
|
|
97
|
+
\.momoFontScale,
|
|
98
|
+
store.fontScale.overriding(
|
|
99
|
+
userScaleRate: context?.userScaleRate,
|
|
100
|
+
useOSScaleRate: context?.useOSScaleRate
|
|
101
|
+
)
|
|
99
102
|
)
|
|
100
|
-
|
|
103
|
+
.environment(\.designSystemKitsConfigs, store.getConfigs())
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
// AI-GENERATED END: reactive font-scale snapshot injected through the environment
|
|
@@ -4,7 +4,7 @@ import SwiftUI
|
|
|
4
4
|
/// free functions). Inside a SwiftUI view prefer `@Environment(\.momoFontScale)` so the view
|
|
5
5
|
/// re-renders when Dynamic Type or the host preference changes.
|
|
6
6
|
public func scaleSize(_ size: CGFloat) -> CGFloat {
|
|
7
|
-
|
|
7
|
+
DesignSystemKits.shared.fontScale(size)
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
@available(*, deprecated, message: "Max font scale is fixed at 1.5; this override is no longer applied. Drop the second argument.")
|
|
@@ -105,10 +105,10 @@ private enum MomoFontFace {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
/// Port of Compose `getFontFamily`; SF Pro Text is the iOS system font, so the legacy family is `.system`.
|
|
108
|
-
private func momoFontFace(family: String, weight: Font.Weight) -> MomoFontFace? {
|
|
108
|
+
private func momoFontFace(family: String, weight: Font.Weight, isWhiteList: Bool) -> MomoFontFace? {
|
|
109
109
|
let isNewFamily = family == "MomoTrustSans" || family == headerFontFamily
|
|
110
110
|
// Outside the whitelist the new DS families drop to the legacy font, as before DS-791.
|
|
111
|
-
if family == "SFProText" || (isNewFamily && !
|
|
111
|
+
if family == "SFProText" || (isNewFamily && !isWhiteList) { return .system }
|
|
112
112
|
// single-file families (Compose maps every weight to the same .otf)
|
|
113
113
|
if family == headerFontFamily || family == "MoMoSignature" { return .named(family) }
|
|
114
114
|
guard family == "MomoTrustSans" else { return nil }
|
|
@@ -127,8 +127,14 @@ private func momoFontFace(family: String, weight: Font.Weight) -> MomoFontFace?
|
|
|
127
127
|
/// Header title family (Compose `HEADER_FONT_FAMILY`), whitelist only like the default font.
|
|
128
128
|
let headerFontFamily = "MoMoTrustDisplay"
|
|
129
129
|
|
|
130
|
-
private func momoFont(
|
|
131
|
-
|
|
130
|
+
private func momoFont(
|
|
131
|
+
size: CGFloat,
|
|
132
|
+
weight: Font.Weight,
|
|
133
|
+
family: String,
|
|
134
|
+
fallbackFamily: String? = nil,
|
|
135
|
+
isWhiteList: Bool
|
|
136
|
+
) -> Font {
|
|
137
|
+
switch momoFontFace(family: family, weight: weight, isWhiteList: isWhiteList) {
|
|
132
138
|
case .system:
|
|
133
139
|
return .system(size: size, weight: weight)
|
|
134
140
|
case .named(let name) where UIFont(name: name, size: size) != nil:
|
|
@@ -136,15 +142,15 @@ private func momoFont(size: CGFloat, weight: Font.Weight, family: String, fallba
|
|
|
136
142
|
default:
|
|
137
143
|
// Compose falls back to the default font file when a family is missing
|
|
138
144
|
if let fallbackFamily, fallbackFamily != family {
|
|
139
|
-
return momoFont(size: size, weight: weight, family: fallbackFamily)
|
|
145
|
+
return momoFont(size: size, weight: weight, family: fallbackFamily, isWhiteList: isWhiteList)
|
|
140
146
|
}
|
|
141
147
|
return .system(size: size, weight: weight)
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
// lineHeight is identical across weights of a family, so the regular face is enough.
|
|
146
|
-
private func momoLineHeight(size: CGFloat, family: String, fallbackFamily: String? = nil) -> CGFloat {
|
|
147
|
-
switch momoFontFace(family: family, weight: .regular) {
|
|
152
|
+
private func momoLineHeight(size: CGFloat, family: String, fallbackFamily: String? = nil, isWhiteList: Bool) -> CGFloat {
|
|
153
|
+
switch momoFontFace(family: family, weight: .regular, isWhiteList: isWhiteList) {
|
|
148
154
|
case .system:
|
|
149
155
|
break
|
|
150
156
|
case .named(let name):
|
|
@@ -152,15 +158,71 @@ private func momoLineHeight(size: CGFloat, family: String, fallbackFamily: Strin
|
|
|
152
158
|
fallthrough
|
|
153
159
|
case nil:
|
|
154
160
|
if let fallbackFamily, fallbackFamily != family {
|
|
155
|
-
return momoLineHeight(size: size, family: fallbackFamily)
|
|
161
|
+
return momoLineHeight(size: size, family: fallbackFamily, isWhiteList: isWhiteList)
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
164
|
return UIFont.systemFont(ofSize: size).lineHeight
|
|
159
165
|
}
|
|
160
166
|
|
|
167
|
+
/// UIKit counterpart of `momoFont`, for text that is not a `MomoText` (UITextField/UITextView, text measuring).
|
|
168
|
+
func momoUIFont(size: CGFloat, weight: UIFont.Weight, family: String, isWhiteList: Bool) -> UIFont {
|
|
169
|
+
guard case .named(let name) = momoFontFace(family: family, weight: Font.Weight(weight), isWhiteList: isWhiteList),
|
|
170
|
+
let font = UIFont(name: name, size: size) else {
|
|
171
|
+
return .systemFont(ofSize: size, weight: weight)
|
|
172
|
+
}
|
|
173
|
+
return font
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private extension Font.Weight {
|
|
177
|
+
init(_ weight: UIFont.Weight) {
|
|
178
|
+
switch weight {
|
|
179
|
+
case .ultraLight: self = .ultraLight
|
|
180
|
+
case .thin: self = .thin
|
|
181
|
+
case .light: self = .light
|
|
182
|
+
case .medium: self = .medium
|
|
183
|
+
case .semibold: self = .semibold
|
|
184
|
+
case .bold: self = .bold
|
|
185
|
+
case .heavy: self = .heavy
|
|
186
|
+
case .black: self = .black
|
|
187
|
+
default: self = .regular
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Gives input text the same family as `MomoText` (Compose's `getFontFamily`), e.g. a TextField
|
|
193
|
+
/// next to a `MomoText` placeholder. `size` is unscaled; the environment font scale is applied here.
|
|
194
|
+
private struct KitFontModifier: ViewModifier {
|
|
195
|
+
@Environment(\.appTheme) private var theme
|
|
196
|
+
@Environment(\.momoFontScale) private var fontScale
|
|
197
|
+
@Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
|
|
198
|
+
let size: CGFloat
|
|
199
|
+
let weight: Font.Weight
|
|
200
|
+
|
|
201
|
+
func body(content: Content) -> some View {
|
|
202
|
+
content.font(momoFont(
|
|
203
|
+
size: fontScale(size),
|
|
204
|
+
weight: weight,
|
|
205
|
+
family: theme.font,
|
|
206
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
207
|
+
))
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
extension View {
|
|
212
|
+
func kitFont(_ typography: TypographyStyle) -> some View {
|
|
213
|
+
modifier(KitFontModifier(size: typography.fontSize, weight: typography.fontWeight))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func kitFont(size: CGFloat, weight: Font.Weight = .regular) -> some View {
|
|
217
|
+
modifier(KitFontModifier(size: size, weight: weight))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
161
221
|
public struct MomoText: View {
|
|
162
222
|
@Environment(\.appTheme) private var theme
|
|
163
223
|
@Environment(\.momoFontScale) private var fontScale
|
|
224
|
+
// From the environment, not DesignSystemKits.shared, so a whitelist flip re-renders the text.
|
|
225
|
+
@Environment(\.designSystemKitsConfigs) private var designSystemKitsConfigs
|
|
164
226
|
private let content: String
|
|
165
227
|
private let typography: TypographyStyle
|
|
166
228
|
private let color: Color?
|
|
@@ -236,7 +298,8 @@ public struct MomoText: View {
|
|
|
236
298
|
size: fontScale(fontSize),
|
|
237
299
|
weight: typography.fontWeight,
|
|
238
300
|
family: fontFamily ?? theme.font,
|
|
239
|
-
fallbackFamily: theme.font
|
|
301
|
+
fallbackFamily: theme.font,
|
|
302
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
240
303
|
)
|
|
241
304
|
}
|
|
242
305
|
|
|
@@ -244,7 +307,13 @@ public struct MomoText: View {
|
|
|
244
307
|
private var baseText: SwiftUI.Text {
|
|
245
308
|
let keywords = highlightText.filter { !$0.isEmpty }.sorted { $0.count > $1.count }
|
|
246
309
|
guard !keywords.isEmpty else { return SwiftUI.Text(content) }
|
|
247
|
-
let highlightFont = momoFont(
|
|
310
|
+
let highlightFont = momoFont(
|
|
311
|
+
size: fontScale(fontSize),
|
|
312
|
+
weight: TypographyStyle.headerXsSemibold.fontWeight,
|
|
313
|
+
family: fontFamily ?? theme.font,
|
|
314
|
+
fallbackFamily: theme.font,
|
|
315
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
316
|
+
)
|
|
248
317
|
func isWordChar(_ character: Character?) -> Bool {
|
|
249
318
|
guard let character else { return false }
|
|
250
319
|
return character.isLetter || character.isNumber || character == "_"
|
|
@@ -282,7 +351,12 @@ public struct MomoText: View {
|
|
|
282
351
|
|
|
283
352
|
// lineSpacing is added on top of the font's natural line height, not on top of fontSize.
|
|
284
353
|
let lineSpacing = max(0, fontScale(lineHeight)
|
|
285
|
-
- momoLineHeight(
|
|
354
|
+
- momoLineHeight(
|
|
355
|
+
size: fontScale(fontSize),
|
|
356
|
+
family: fontFamily ?? theme.font,
|
|
357
|
+
fallbackFamily: theme.font,
|
|
358
|
+
isWhiteList: designSystemKitsConfigs.isWhiteList
|
|
359
|
+
))
|
|
286
360
|
|
|
287
361
|
var text = baseText
|
|
288
362
|
.font(font)
|
|
@@ -10,12 +10,12 @@ public extension View {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
public extension Font {
|
|
13
|
-
/// Non-reactive: resolves against the current `
|
|
13
|
+
/// Non-reactive: resolves against the current `DesignSystemKits` font scale at each access. The
|
|
14
14
|
/// styles below are computed properties for that reason — as stored `static let`s they would
|
|
15
15
|
/// freeze the scale at first access and never pick up a Dynamic Type change.
|
|
16
16
|
/// Inside a view, `@Environment(\.momoFontScale)` + `fontScale.font(size:weight:)` also re-renders.
|
|
17
17
|
static func appFont(size: CGFloat) -> Font {
|
|
18
|
-
|
|
18
|
+
DesignSystemKits.shared.fontScale.font(size: size)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// New supported typography styles
|
package/ios/native-kits.podspec
CHANGED