@momo-kits/native-kits 0.300.3-debug → 0.300.4-debug
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -7
- package/README.md +2 -2
- package/compose/build.gradle.kts +10 -2
- package/compose/build.gradle.kts.backup +8 -0
- package/compose/compose.podspec +1 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -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 +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
- 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/Header.kt +122 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -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 +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +3 -14
- package/gradle/wrapper/gradle-wrapper.properties +0 -2
- package/gradle.properties +3 -5
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +8 -2
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +415 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +138 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +7 -3
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderJourney.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderJourney.kt`: a "start → end" title
|
|
6
|
+
// with an optional description below.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
public struct HeaderJourneyView: View {
|
|
12
|
+
private let data: HeaderJourneyData
|
|
13
|
+
private let tintColor: Color
|
|
14
|
+
|
|
15
|
+
public init(_ data: HeaderJourneyData, tintColor: Color) {
|
|
16
|
+
self.data = data
|
|
17
|
+
self.tintColor = tintColor
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Environment(\.screenLayout) private var layout
|
|
21
|
+
|
|
22
|
+
public var body: some View {
|
|
23
|
+
if data.isLoading {
|
|
24
|
+
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
25
|
+
titleSkeleton(height: 18)
|
|
26
|
+
titleSkeleton(height: 12)
|
|
27
|
+
}
|
|
28
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
29
|
+
} else {
|
|
30
|
+
let tint = parseHeaderTint(data.tintColor) ?? tintColor
|
|
31
|
+
let iconTint = parseHeaderTint(data.iconColor) ?? tint
|
|
32
|
+
SwiftUI.Button(action: { data.onPress?() }) {
|
|
33
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
34
|
+
HStack(spacing: 0) {
|
|
35
|
+
MomoText(data.start, typography: .actionXsBold, color: tint, maxLines: 1)
|
|
36
|
+
Spacer().frame(width: Spacing.S)
|
|
37
|
+
if data.icon.trimmingCharacters(in: .whitespaces).isEmpty {
|
|
38
|
+
Spacer().frame(width: 16)
|
|
39
|
+
} else {
|
|
40
|
+
Icon(source: data.icon, size: 16, color: iconTint)
|
|
41
|
+
}
|
|
42
|
+
Spacer().frame(width: Spacing.S)
|
|
43
|
+
if let end = data.end, !end.isEmpty {
|
|
44
|
+
MomoText(end, typography: .actionXsBold, color: tint, maxLines: 1)
|
|
45
|
+
}
|
|
46
|
+
Spacer().frame(width: Spacing.XS)
|
|
47
|
+
Icon(source: "16_arrow_chevron_down_small", size: 16, color: tint)
|
|
48
|
+
}
|
|
49
|
+
if let description = data.description, !description.isEmpty {
|
|
50
|
+
MomoText(description, typography: .descriptionXsRegular, color: tint, maxLines: 1)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
.frame(maxWidth: headerTitleMaxWidth(layout), alignment: .leading)
|
|
54
|
+
}
|
|
55
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
56
|
+
.disabled(data.onPress == nil)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
func titleSkeleton(height: CGFloat) -> some View {
|
|
62
|
+
Skeleton()
|
|
63
|
+
.frame(width: 120, height: height)
|
|
64
|
+
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// CSS hex (`RGB`, `RRGGBB`, `RRGGBBAA`) as RN passes header tints; nil when unset or invalid.
|
|
68
|
+
func parseHeaderTint(_ value: String?) -> Color? {
|
|
69
|
+
guard var digits = value?.trimmingCharacters(in: .whitespaces), !digits.isEmpty else { return nil }
|
|
70
|
+
if digits.hasPrefix("#") { digits.removeFirst() }
|
|
71
|
+
guard [3, 6, 8].contains(digits.count), digits.allSatisfy(\.isHexDigit) else { return nil }
|
|
72
|
+
return Color(hex: digits)
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderLocation.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderLocation.kt`: a description above a
|
|
6
|
+
// tappable location with a chevron.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
public struct HeaderLocationView: View {
|
|
12
|
+
private let data: HeaderLocationData
|
|
13
|
+
private let tintColor: Color
|
|
14
|
+
|
|
15
|
+
public init(_ data: HeaderLocationData, tintColor: Color) {
|
|
16
|
+
self.data = data
|
|
17
|
+
self.tintColor = tintColor
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Environment(\.screenLayout) private var layout
|
|
21
|
+
|
|
22
|
+
public var body: some View {
|
|
23
|
+
if data.isLoading {
|
|
24
|
+
VStack(alignment: .leading, spacing: Spacing.XXS) {
|
|
25
|
+
titleSkeleton(height: 12)
|
|
26
|
+
titleSkeleton(height: 18)
|
|
27
|
+
}
|
|
28
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
29
|
+
} else {
|
|
30
|
+
let tint = parseHeaderTint(data.tintColor) ?? tintColor
|
|
31
|
+
SwiftUI.Button(action: { data.onPress?() }) {
|
|
32
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
33
|
+
if let description = data.description, !description.isEmpty {
|
|
34
|
+
MomoText(description, typography: .descriptionXsRegular, color: tint, maxLines: 1)
|
|
35
|
+
}
|
|
36
|
+
HStack(spacing: 0) {
|
|
37
|
+
MomoText(data.location, typography: .actionXsBold, color: tint, maxLines: 1)
|
|
38
|
+
Spacer().frame(width: Spacing.XS)
|
|
39
|
+
Icon(source: "16_arrow_chevron_down_small", size: 16, color: tint)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
.frame(maxWidth: headerTitleMaxWidth(layout), alignment: .leading)
|
|
43
|
+
}
|
|
44
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
45
|
+
.disabled(data.onPress == nil)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderRight.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderRight.kt`: the mini-app toolkit
|
|
6
|
+
// (shortcut / more / single tool, help center and close) in the bar or the iPhone Duo side bar.
|
|
7
|
+
//
|
|
8
|
+
|
|
9
|
+
import SwiftUI
|
|
10
|
+
|
|
11
|
+
/// Mirrors Compose's `HeaderRight.Toolkit`.
|
|
12
|
+
public struct HeaderRightData {
|
|
13
|
+
var useShortcut: Bool
|
|
14
|
+
var useMore: Bool
|
|
15
|
+
var tools: [ToolGroup]
|
|
16
|
+
var toolCallback: ((String) -> Void)?
|
|
17
|
+
var useSystemTools: Bool
|
|
18
|
+
/// Close dismisses only this mini app instead of returning home.
|
|
19
|
+
var useCloseIcon: Bool
|
|
20
|
+
|
|
21
|
+
public init(
|
|
22
|
+
useShortcut: Bool = false,
|
|
23
|
+
useMore: Bool = false,
|
|
24
|
+
tools: [ToolGroup] = [],
|
|
25
|
+
toolCallback: ((String) -> Void)? = nil,
|
|
26
|
+
useSystemTools: Bool = true,
|
|
27
|
+
useCloseIcon: Bool = false
|
|
28
|
+
) {
|
|
29
|
+
self.useShortcut = useShortcut
|
|
30
|
+
self.useMore = useMore
|
|
31
|
+
self.tools = tools
|
|
32
|
+
self.toolCallback = toolCallback
|
|
33
|
+
self.useSystemTools = useSystemTools
|
|
34
|
+
self.useCloseIcon = useCloseIcon
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var closeIcon: String { useCloseIcon ? "16_navigation_close_circle" : "16_basic_home" }
|
|
38
|
+
|
|
39
|
+
var showBadge: Bool { tools.contains { group in group.items.contains { $0.showBadge } } }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public struct Tool {
|
|
43
|
+
var key: String
|
|
44
|
+
var icon: String
|
|
45
|
+
var showBadge: Bool
|
|
46
|
+
var name: [String: String]
|
|
47
|
+
var showRightIcon: Bool
|
|
48
|
+
|
|
49
|
+
public init(
|
|
50
|
+
key: String,
|
|
51
|
+
icon: String,
|
|
52
|
+
name: [String: String] = [:],
|
|
53
|
+
showBadge: Bool = false,
|
|
54
|
+
showRightIcon: Bool = true
|
|
55
|
+
) {
|
|
56
|
+
self.key = key
|
|
57
|
+
self.icon = icon
|
|
58
|
+
self.showBadge = showBadge
|
|
59
|
+
self.name = name
|
|
60
|
+
self.showRightIcon = showRightIcon
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func toMap() -> [String: Any] {
|
|
64
|
+
[
|
|
65
|
+
"key": key,
|
|
66
|
+
"icon": icon,
|
|
67
|
+
"showBadge": showBadge,
|
|
68
|
+
"name": name,
|
|
69
|
+
"showRightIcon": showRightIcon
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public struct ToolGroup {
|
|
75
|
+
var title: [String: String]
|
|
76
|
+
var items: [Tool]
|
|
77
|
+
|
|
78
|
+
public init(title: [String: String] = [:], items: [Tool]) {
|
|
79
|
+
self.title = title
|
|
80
|
+
self.items = items
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
func toMap() -> [String: Any] {
|
|
84
|
+
[
|
|
85
|
+
"title": title,
|
|
86
|
+
"items": items.map { $0.toMap() }
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private let helpCenterAppId = "vn.momo.helpcenter"
|
|
92
|
+
|
|
93
|
+
/// The Help Center mini app hides its own help button; without a context there is nothing to open.
|
|
94
|
+
private func usesHelpCenter(_ context: MiniAppContext?) -> Bool {
|
|
95
|
+
guard let context else { return false }
|
|
96
|
+
return context.appId != helpCenterAppId
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private func showsShortcut(_ data: HeaderRightData, _ context: MiniAppContext?) -> Bool {
|
|
100
|
+
data.useShortcut && !(data.useMore && context == nil)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Height of the vertical toolkit: the optional shortcut, then the help/home capsule.
|
|
104
|
+
func toolkitColumnHeight(_ data: HeaderRightData, context: MiniAppContext?) -> CGFloat {
|
|
105
|
+
let shortcut = showsShortcut(data, context) ? DuoMetrics.controlSize + Spacing.M : 0
|
|
106
|
+
return shortcut + (usesHelpCenter(context) ? 2 * DuoMetrics.itemPitch : DuoMetrics.controlSize)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// A header right that renders the kit toolkit, so the Duo side bar can take it over.
|
|
110
|
+
protocol ToolkitHeaderRightView {
|
|
111
|
+
var headerRight: HeaderRightData? { get }
|
|
112
|
+
var tintColor: Color? { get }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Toolkit data when `headerRight` renders the kit toolkit; nil for a custom header right.
|
|
116
|
+
func toolkitHeaderRight(_ headerRight: (() -> any View)?) -> (data: HeaderRightData?, tintColor: Color?)? {
|
|
117
|
+
guard let toolkit = headerRight?() as? ToolkitHeaderRightView else { return nil }
|
|
118
|
+
return (toolkit.headerRight, toolkit.tintColor)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// MARK: - Public views
|
|
122
|
+
|
|
123
|
+
/// The kit toolkit. Works under `NavigationContainer` (keyed environment) and under hosts that
|
|
124
|
+
/// inject `ApplicationEnvironment` with `.environmentObject`.
|
|
125
|
+
public struct HeaderRight: View, ToolkitHeaderRightView {
|
|
126
|
+
var headerRight: HeaderRightData?
|
|
127
|
+
var tintColor: Color?
|
|
128
|
+
|
|
129
|
+
public init(headerRight: HeaderRightData? = nil, tintColor: Color? = nil) {
|
|
130
|
+
self.headerRight = headerRight
|
|
131
|
+
self.tintColor = tintColor
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public var body: some View {
|
|
135
|
+
Toolkit(headerRight: headerRight ?? HeaderRightData(), tintColor: tintColor)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MARK: - Toolkit
|
|
140
|
+
|
|
141
|
+
private struct NavigationButtonConfig {
|
|
142
|
+
let icon: String
|
|
143
|
+
let onPress: () -> Void
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// `sideBarColumn`: the iPhone Duo side bar; `sideBarRow`: the Duo header row on the status bar, same 48pt controls.
|
|
147
|
+
enum ToolkitLayout {
|
|
148
|
+
case bar, sideBarColumn, sideBarRow
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
struct Toolkit: View {
|
|
152
|
+
let headerRight: HeaderRightData
|
|
153
|
+
var tintColor: Color?
|
|
154
|
+
var layout: ToolkitLayout = .bar
|
|
155
|
+
|
|
156
|
+
@State private var isFavorite = false
|
|
157
|
+
@State private var isLoading = false
|
|
158
|
+
@OptionalEnvironmentObject private var injectedEnvironment: ApplicationEnvironment?
|
|
159
|
+
@Environment(\.applicationEnvironment) private var keyedEnvironment
|
|
160
|
+
@Environment(\.headerColor) private var barColor
|
|
161
|
+
|
|
162
|
+
private var environment: ApplicationEnvironment { injectedEnvironment ?? keyedEnvironment }
|
|
163
|
+
private var context: MiniAppContext? { environment.applicationContext }
|
|
164
|
+
|
|
165
|
+
/// The bar's colors when inside a header, otherwise derived from the tint like a plain header.
|
|
166
|
+
private var headerColor: HeaderColor {
|
|
167
|
+
barColor ?? getHeaderColor(isAnimated: false, isSurface: true, opacity: 1, tintColor: tintColor)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func apiValue(_ value: Any?) -> Any { value ?? NSNull() }
|
|
171
|
+
|
|
172
|
+
private var contextMap: [String: Any] {
|
|
173
|
+
[
|
|
174
|
+
"appId": apiValue(context?.appId),
|
|
175
|
+
"code": apiValue(context?.appCode),
|
|
176
|
+
"name": apiValue(context?.appName),
|
|
177
|
+
"icon": apiValue(context?.appIcon),
|
|
178
|
+
"description": apiValue(context?.description),
|
|
179
|
+
"support": apiValue(context?.support),
|
|
180
|
+
"toolkitConfig": apiValue(context?.toolkitConfig),
|
|
181
|
+
"providerId": apiValue(context?.providerId),
|
|
182
|
+
"permissions": apiValue(context?.permissions)
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private func parseResponse(_ response: String) -> Any? {
|
|
187
|
+
guard let data = response.data(using: .utf8),
|
|
188
|
+
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
189
|
+
return nil
|
|
190
|
+
}
|
|
191
|
+
return json["response"]
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private typealias MaxApiRequest = (MaxApi, [String: Any?], @escaping ([String: Any?]?) -> Void) -> Void
|
|
195
|
+
|
|
196
|
+
/// Through `composeApi` while the host still passes one, else `maxApi` (Compose `IMaxApi`).
|
|
197
|
+
/// Hosts may answer off the main thread, so the response is handed back on it.
|
|
198
|
+
private func request(
|
|
199
|
+
_ funcName: String,
|
|
200
|
+
_ params: [String: Any],
|
|
201
|
+
maxApi maxApiRequest: MaxApiRequest,
|
|
202
|
+
onResponse: @escaping (Any?) -> Void = { _ in }
|
|
203
|
+
) {
|
|
204
|
+
if let composeApi = environment.composeApi {
|
|
205
|
+
_ = composeApi.request(funcName: funcName, params: params) { response in
|
|
206
|
+
let value = parseResponse(response)
|
|
207
|
+
DispatchQueue.main.async { onResponse(value) }
|
|
208
|
+
}
|
|
209
|
+
} else if let maxApi = environment.maxApi {
|
|
210
|
+
maxApiRequest(maxApi, params.mapValues { $0 as Any? }) { data in
|
|
211
|
+
let value = data?["response"] ?? nil
|
|
212
|
+
DispatchQueue.main.async { onResponse(value) }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// MARK: Actions
|
|
218
|
+
|
|
219
|
+
private func loadFavoriteState() {
|
|
220
|
+
request(
|
|
221
|
+
"isFavoriteApp",
|
|
222
|
+
["code": apiValue(context?.appCode)],
|
|
223
|
+
maxApi: { $0.isFavoriteApp($1, callback: $2) }
|
|
224
|
+
) { response in
|
|
225
|
+
isFavorite = response as? Bool == true
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
private func onPressShortcut() {
|
|
230
|
+
request(
|
|
231
|
+
"onToolAction",
|
|
232
|
+
["item": ["key": "onFavorite"], "context": contextMap],
|
|
233
|
+
maxApi: { $0.onToolAction($1, callback: $2) }
|
|
234
|
+
) { response in
|
|
235
|
+
if let result = response as? [String: Any], result["success"] as? Bool == true {
|
|
236
|
+
isFavorite.toggle()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private func onPressMore() {
|
|
242
|
+
request(
|
|
243
|
+
"showTools",
|
|
244
|
+
[
|
|
245
|
+
"useSystemTools": headerRight.useSystemTools,
|
|
246
|
+
"tools": headerRight.tools.map { $0.toMap() },
|
|
247
|
+
"context": contextMap
|
|
248
|
+
],
|
|
249
|
+
maxApi: { $0.showTools($1, callback: $2) }
|
|
250
|
+
) { response in
|
|
251
|
+
if let tool = response as? String {
|
|
252
|
+
headerRight.toolCallback?(tool)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private func onPressHelpCenter() {
|
|
258
|
+
guard context != nil else { return }
|
|
259
|
+
request(
|
|
260
|
+
"showHelpCenter",
|
|
261
|
+
[
|
|
262
|
+
"appId": apiValue(context?.appId),
|
|
263
|
+
"code": apiValue(context?.appCode),
|
|
264
|
+
"name": apiValue(context?.appName),
|
|
265
|
+
"icon": apiValue(context?.appIcon),
|
|
266
|
+
"description": apiValue(context?.description)
|
|
267
|
+
],
|
|
268
|
+
maxApi: { $0.showHelpCenter($1, callback: $2) }
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
private func onPressClose() {
|
|
273
|
+
if headerRight.useCloseIcon {
|
|
274
|
+
environment.requestDismiss()
|
|
275
|
+
} else if let composeApi = environment.composeApi {
|
|
276
|
+
_ = composeApi.request(funcName: "dismissAll", params: nil) { _ in }
|
|
277
|
+
} else {
|
|
278
|
+
environment.maxApi?.dismissAll(completion: nil)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private var navigationButtonConfig: NavigationButtonConfig {
|
|
283
|
+
let totalTools = headerRight.tools.reduce(0) { $0 + $1.items.count }
|
|
284
|
+
if totalTools > 1 || headerRight.useMore {
|
|
285
|
+
return NavigationButtonConfig(icon: "navigation_more_icon", onPress: onPressMore)
|
|
286
|
+
}
|
|
287
|
+
if totalTools == 1, let tool = headerRight.tools.first?.items.first {
|
|
288
|
+
return NavigationButtonConfig(icon: tool.icon) { headerRight.toolCallback?(tool.key) }
|
|
289
|
+
}
|
|
290
|
+
return NavigationButtonConfig(icon: isFavorite ? "pin_star_checked" : "pin_star", onPress: onPressShortcut)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// MARK: Body
|
|
294
|
+
|
|
295
|
+
var body: some View {
|
|
296
|
+
Group {
|
|
297
|
+
switch layout {
|
|
298
|
+
case .bar:
|
|
299
|
+
horizontalBody
|
|
300
|
+
case .sideBarColumn:
|
|
301
|
+
VStack(spacing: Spacing.M) { sideBarControls(horizontal: false) }
|
|
302
|
+
case .sideBarRow:
|
|
303
|
+
HStack(spacing: Spacing.M) { sideBarControls(horizontal: true) }
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
.onAppear(perform: loadFavoriteState)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@ViewBuilder
|
|
310
|
+
private func sideBarControls(horizontal: Bool) -> some View {
|
|
311
|
+
let config = navigationButtonConfig
|
|
312
|
+
let useHelpCenter = usesHelpCenter(context)
|
|
313
|
+
if showsShortcut(headerRight, context) {
|
|
314
|
+
SideBarButton(
|
|
315
|
+
icon: config.icon,
|
|
316
|
+
headerColor: headerColor,
|
|
317
|
+
showBadge: headerRight.showBadge,
|
|
318
|
+
disabled: isLoading,
|
|
319
|
+
action: config.onPress
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
SideBarGroup(headerColor: headerColor, itemCount: useHelpCenter ? 2 : 1, horizontal: horizontal) {
|
|
323
|
+
if useHelpCenter {
|
|
324
|
+
SideBarGroupItem(
|
|
325
|
+
icon: "help_center",
|
|
326
|
+
headerColor: headerColor,
|
|
327
|
+
accessibilityId: "btn_navigation_help_center",
|
|
328
|
+
action: onPressHelpCenter
|
|
329
|
+
)
|
|
330
|
+
}
|
|
331
|
+
SideBarGroupItem(
|
|
332
|
+
icon: headerRight.closeIcon,
|
|
333
|
+
headerColor: headerColor,
|
|
334
|
+
accessibilityId: "btn_navigation_close",
|
|
335
|
+
action: onPressClose
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private var horizontalBody: some View {
|
|
341
|
+
let config = navigationButtonConfig
|
|
342
|
+
return HStack(alignment: .center, spacing: 0) {
|
|
343
|
+
if showsShortcut(headerRight, context) {
|
|
344
|
+
NavigationButton(
|
|
345
|
+
disabled: isLoading,
|
|
346
|
+
icon: config.icon,
|
|
347
|
+
showBadge: headerRight.showBadge,
|
|
348
|
+
headerColor: headerColor,
|
|
349
|
+
onClick: config.onPress
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
HStack(alignment: .center, spacing: 0) {
|
|
354
|
+
if usesHelpCenter(context) {
|
|
355
|
+
toolButton(icon: "help_center", accessibilityId: "btn_navigation_help_center", action: onPressHelpCenter)
|
|
356
|
+
Rectangle()
|
|
357
|
+
.fill(tintColor ?? Colors.black20)
|
|
358
|
+
.frame(width: 0.5, height: 12)
|
|
359
|
+
}
|
|
360
|
+
toolButton(icon: headerRight.closeIcon, accessibilityId: "btn_navigation_close", action: onPressClose)
|
|
361
|
+
}
|
|
362
|
+
.frame(width: 65, height: 28)
|
|
363
|
+
.background(headerColor.backgroundButton)
|
|
364
|
+
.cornerRadius(14)
|
|
365
|
+
.overlay(RoundedRectangle(cornerRadius: 14).stroke(headerColor.borderColor, lineWidth: 0.2))
|
|
366
|
+
.padding(.leading, Spacing.S)
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private func toolButton(icon: String, accessibilityId: String, action: @escaping () -> Void) -> some View {
|
|
371
|
+
SwiftUI.Button(action: action) {
|
|
372
|
+
Icon(source: icon, size: 20, color: headerColor.tintIconColor)
|
|
373
|
+
.padding(4)
|
|
374
|
+
}
|
|
375
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
376
|
+
.accessibilityIdentifier(accessibilityId)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// Mirrors Compose's `NavigationButton`: the 28pt circular shortcut / more button.
|
|
381
|
+
public struct NavigationButton: View {
|
|
382
|
+
var disabled: Bool
|
|
383
|
+
var icon: String
|
|
384
|
+
var showBadge: Bool
|
|
385
|
+
var headerColor: HeaderColor?
|
|
386
|
+
var onClick: () -> Void
|
|
387
|
+
|
|
388
|
+
@Environment(\.headerColor) private var barColor
|
|
389
|
+
|
|
390
|
+
/// Colors follow the enclosing header, so it fits a custom header right next to the toolkit.
|
|
391
|
+
public init(icon: String, showBadge: Bool = false, disabled: Bool = false, onClick: @escaping () -> Void) {
|
|
392
|
+
self.init(disabled: disabled, icon: icon, showBadge: showBadge, headerColor: nil, onClick: onClick)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
init(disabled: Bool, icon: String, showBadge: Bool, headerColor: HeaderColor?, onClick: @escaping () -> Void) {
|
|
396
|
+
self.disabled = disabled
|
|
397
|
+
self.icon = icon
|
|
398
|
+
self.showBadge = showBadge
|
|
399
|
+
self.headerColor = headerColor
|
|
400
|
+
self.onClick = onClick
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private var colors: HeaderColor {
|
|
404
|
+
headerColor ?? barColor ?? getHeaderColor(isAnimated: false, isSurface: true, opacity: 1, tintColor: nil)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
public var body: some View {
|
|
408
|
+
SwiftUI.Button(action: onClick) {
|
|
409
|
+
ZStack {
|
|
410
|
+
Circle()
|
|
411
|
+
.fill(colors.backgroundButton)
|
|
412
|
+
.overlay(Circle().stroke(colors.borderColor, lineWidth: 0.2))
|
|
413
|
+
Icon(source: icon, size: 20, color: colors.tintIconColor)
|
|
414
|
+
}
|
|
415
|
+
.frame(width: 28, height: 28)
|
|
416
|
+
.overlay(alignment: .topTrailing) {
|
|
417
|
+
if showBadge {
|
|
418
|
+
BadgeDot(size: .small).offset(x: -Spacing.XXS, y: -Spacing.XXS)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
.buttonStyle(ActiveOpacityButtonStyle())
|
|
423
|
+
.disabled(disabled)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HeaderTitle.swift
|
|
3
|
+
// MoMoUIKits
|
|
4
|
+
//
|
|
5
|
+
// SwiftUI mirror of Compose's `navigation/component/HeaderTitle.kt`: the plain header title.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
/// `actionSBold` at 15/22 in the header family, single line.
|
|
11
|
+
struct HeaderTitleText: View {
|
|
12
|
+
let title: String
|
|
13
|
+
let color: Color
|
|
14
|
+
var centered: Bool = false
|
|
15
|
+
|
|
16
|
+
init(_ title: String, color: Color, centered: Bool = false) {
|
|
17
|
+
self.title = title
|
|
18
|
+
self.color = color
|
|
19
|
+
self.centered = centered
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var body: some View {
|
|
23
|
+
MomoText(
|
|
24
|
+
title,
|
|
25
|
+
typography: .actionSBold,
|
|
26
|
+
color: color,
|
|
27
|
+
textAlign: centered ? .center : .leading,
|
|
28
|
+
maxLines: 1,
|
|
29
|
+
truncationMode: .tail,
|
|
30
|
+
accessibilityId: "title_navigation_header",
|
|
31
|
+
fontFamily: headerFontFamily
|
|
32
|
+
)
|
|
33
|
+
.fontSize(15, lineHeight: 22)
|
|
34
|
+
.frame(maxWidth: centered ? .infinity : nil, alignment: centered ? .center : .leading)
|
|
35
|
+
}
|
|
36
|
+
}
|