@momo-kits/native-kits 0.0.1-beta.4 → 0.0.2-alpha.1
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 +99 -0
- package/PUBLISH_GUIDE.md +675 -0
- package/README.md +184 -5
- package/ios/Application/ApplicationEnvironment.swift +71 -0
- package/ios/Application/Components.swift +270 -0
- package/ios/Application/ComposeApi.swift +22 -0
- package/ios/Application/FloatingButton.swift +174 -0
- package/ios/Application/HeaderRight.swift +306 -0
- package/ios/Application/LanguageSource.swift +93 -0
- package/ios/Application/Localize.swift +378 -0
- package/ios/Application/LocalizeModifier.swift +30 -0
- package/ios/Application/MaxApi.swift +18 -0
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +104 -0
- package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +215 -0
- package/ios/Application/Navigation/BottomTab/CurvedContainer.swift +65 -0
- package/ios/Application/Navigation/HeaderBackProps.swift +66 -0
- package/ios/Application/Navigation/HeaderTitle.swift +57 -0
- package/ios/Application/Navigation/HeaderUser.swift +209 -0
- package/ios/Application/Navigation/NavigationContainer.swift +213 -0
- package/ios/Application/Navigation/NavigationOptions.swift +109 -0
- package/ios/Application/Navigation/Navigator.swift +453 -0
- package/ios/Application/Navigation/NavigatorManager.swift +22 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +256 -0
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +74 -0
- package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -0
- package/ios/Application/Navigation/Tracking/ScreenTracker.swift +117 -0
- package/ios/Application/Navigation/component/Header.swift +178 -0
- package/ios/Application/Navigation/component/HeaderBackground.swift +83 -0
- package/ios/Application/Navigation/component/HeaderColor.swift +65 -0
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +11 -0
- package/ios/Application/Navigation/component/NavigationFooter.swift +90 -0
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +196 -0
- package/ios/Application/Screen.swift +250 -0
- package/ios/Application/StackScreen.swift +492 -0
- package/ios/Avatar/Avatar.swift +193 -0
- package/ios/Badge/Badge.swift +84 -0
- package/ios/Badge/BadgeDot.swift +31 -0
- package/ios/Badge/BadgeRibbon.swift +319 -0
- package/ios/BaselineView/BaselineView.swift +163 -0
- package/ios/Button/Button.swift +232 -84
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +126 -0
- package/ios/Carousel/Carousel.swift +196 -0
- package/ios/Checkbox/Checkbox.swift +77 -0
- package/ios/Chip/Chip.swift +120 -0
- package/ios/Collapse/Collapse.swift +192 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Colors.swift +42 -37
- package/ios/Colors+Radius+Spacing/Radius.swift +22 -0
- package/ios/{Colors+Spacing → Colors+Radius+Spacing}/Spacing.swift +3 -1
- package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
- package/ios/DateTimePicker/DateTimePicker.swift +212 -0
- package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
- package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
- package/ios/DateTimePicker/WheelPicker.swift +165 -0
- package/ios/Divider/Divider.swift +16 -0
- package/ios/Extensions/ActiveOpacityButtonStyle.swift +14 -0
- package/ios/Extensions/Color++.swift +32 -0
- package/ios/Icon/Icon.swift +69 -0
- package/ios/IconButton/IconButton.swift +140 -0
- package/ios/Image/Image.swift +87 -0
- package/ios/Information/Information.swift +156 -0
- package/ios/Input/ErrorView.swift +58 -0
- package/ios/Input/Input.swift +461 -77
- package/ios/Input/InputPhoneNumber.swift +192 -0
- package/ios/Input/InputSearch.swift +301 -0
- package/ios/Input/InputTextArea.swift +318 -0
- package/ios/InputDropDown/InputDropDown.swift +201 -0
- package/ios/InputMoney/InputMoney.swift +405 -0
- package/ios/InputOTP/InputOTP.swift +211 -0
- package/ios/Layout/GridContext.swift +30 -0
- package/ios/Layout/Item.swift +42 -0
- package/ios/Layout/Section.swift +134 -0
- package/ios/Loader/Loader.swift +113 -0
- package/ios/Lottie/LottieView.swift +86 -0
- package/ios/MoMoUIKits.podspec +5 -0
- package/ios/OTPKeyboard/KeyboardButton.swift +13 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +1 -1
- package/ios/Pagination/PaginationDot.swift +61 -0
- package/ios/Pagination/PaginationNumber.swift +35 -0
- package/ios/Pagination/PaginationScroll.swift +101 -0
- package/ios/Pagination/PaginationWhiteDot.swift +37 -0
- package/ios/Popup/PopupDisplay.swift +279 -0
- package/ios/Popup/PopupInput.swift +92 -0
- package/ios/Popup/PopupNotify.swift +219 -0
- package/ios/Popup/PopupPromotion.swift +74 -0
- package/ios/PopupView/FullscreenPopup.swift +251 -0
- package/ios/{Popup → PopupView}/Modifiers.swift +6 -20
- package/ios/{Popup → PopupView}/PopupView.swift +38 -146
- package/ios/PopupView/Utils++.swift +281 -0
- package/ios/ProgressInfo/ProgressInfo.swift +297 -0
- package/ios/Radio/Radio.swift +69 -0
- package/ios/Rating/Rating.swift +83 -0
- package/{example/ios/Example/Preview Content/Preview Assets.xcassets/Contents.json → ios/Resources/MoMoUIKitsColors.xcassets/AccentColor.colorset/Contents.json} +5 -0
- package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Text.colorset/Contents.json +3 -3
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/Contents.json +9 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-pressed.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-selected.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-surface.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/background-tonal.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/border-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/gradient.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-container.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-primary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/success-secondary.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-default.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-disable.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-hint.colorset/Contents.json +38 -0
- package/ios/Resources/MoMoUIKitsColors.xcassets/ThemeColors/text-secondary.colorset/Contents.json +38 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +110 -0
- package/ios/Skeleton/Skeleton.swift +99 -0
- package/ios/Slider/Slider.swift +237 -0
- package/ios/StatusBarTap/StatusBarTap.h +13 -0
- package/ios/StatusBarTap/StatusBarTap.m +75 -0
- package/ios/Stepper/Stepper.swift +221 -0
- package/ios/Steps/Steps.swift +467 -0
- package/ios/SuggestAction/SuggestAction.swift +100 -0
- package/ios/Swipeable/SwipeCell.swift +226 -31
- package/ios/Swipeable/SwipeCellModel.swift +21 -0
- package/ios/Switch/Switch.swift +69 -0
- package/ios/TabView/TabView.swift +534 -0
- package/ios/Tag/Tag.swift +99 -0
- package/ios/Template/Logo/Logo.swift +75 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +123 -0
- package/ios/Title/Title.swift +270 -0
- package/ios/Tooltip/Tooltip.swift +486 -0
- package/ios/Typography/FontScale.swift +103 -0
- package/ios/Typography/FontScaleStore.swift +58 -0
- package/ios/Typography/KitDesignSystem.swift +17 -0
- package/ios/Typography/Text.swift +262 -0
- package/ios/Typography/Typography.swift +80 -23
- package/ios/Uploader/Uploader.swift +174 -0
- package/ios/native-kits.podspec +73 -13
- package/ios-demo/MoMoUIKitsDemo.podspec +20 -0
- package/ios-demo/Resources/dot_loading.json +558 -0
- package/ios-demo/Resources/icon.json +4052 -0
- package/ios-demo/Resources/lottie_circle_loader.json +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/DemoScaffold.swift +48 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsBadgeDemoView.swift +25 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsButtonDemoView.swift +26 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsCheckboxDemoView.swift +19 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsChipDemoView.swift +33 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsInputDemoView.swift +48 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsPopupDemoView.swift +21 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsRadioDemoView.swift +17 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/Demos/UIKitsTypographyDemoView.swift +28 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoContext.swift +31 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/MoMoUIKitsDemoFactory.swift +24 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +75 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BaselineView.swift +117 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BottomTabDemo.swift +89 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ButtonDemo.swift +108 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +96 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +60 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +102 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DemoScreen.swift +420 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +56 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +276 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconDemo.swift +81 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +65 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +96 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +117 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +88 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +120 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +85 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +73 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LocalizeDemo.swift +235 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +241 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +68 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PlaygroundData.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +95 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +67 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +101 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +64 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +68 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SectionDemo.swift +83 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +61 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +90 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +82 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +114 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +90 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +58 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +114 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +86 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +181 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +104 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +118 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +85 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +53 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
- package/package.json +6 -6
- package/publish.sh +554 -20
- package/scripts/gen-ios-color.mjs +136 -0
- package/CODE_OF_CONDUCT.md +0 -133
- package/CONTRIBUTING.md +0 -114
- package/LICENSE +0 -20
- package/android/build.gradle +0 -82
- package/android/gradle.properties +0 -5
- package/android/src/main/AndroidManifest.xml +0 -4
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsPackage.kt +0 -17
- package/android/src/main/java/com/momoplatform/nativekits/NativeKitsViewManager.kt +0 -20
- package/example/ios/Example/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -63
- package/example/ios/Example/ComponentDetail.swift +0 -35
- package/example/ios/Example/ComponentItem.swift +0 -24
- package/example/ios/Example/ComponentModel.swift +0 -33
- package/example/ios/Example/ContentView.swift +0 -53
- package/example/ios/Example/Example.entitlements +0 -10
- package/example/ios/Example/Foundation/ButtonDemo.swift +0 -80
- package/example/ios/Example/Foundation/Popup/ActionSheets.swift +0 -136
- package/example/ios/Example/Foundation/Popup/Floats.swift +0 -127
- package/example/ios/Example/Foundation/Popup/Popups.swift +0 -101
- package/example/ios/Example/Foundation/PopupDemo.swift +0 -366
- package/example/ios/Example/Foundation/TextInputDemo.swift +0 -31
- package/example/ios/Example/Foundation/TypographyDemo.swift +0 -53
- package/example/ios/Example/Foundation.swift +0 -26
- package/example/ios/Example/Info.plist +0 -18
- package/example/ios/Example/Library/SwipeableDemo.swift +0 -105
- package/example/ios/Example/Library.swift +0 -26
- package/example/ios/Example/Main.swift +0 -11
- package/example/ios/Example/Overview.swift +0 -20
- package/example/ios/Example/Resource/SFProText-Black.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Bold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Heavy.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Light.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Medium.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Regular.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Semibold.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Thin.ttf +0 -0
- package/example/ios/Example/Resource/SFProText-Ultralight.ttf +0 -0
- package/example/ios/Example/Resource/foundation.json +0 -33
- package/example/ios/Example/Resource/library.json +0 -8
- package/example/ios/Example/Utils.swift +0 -30
- package/example/ios/Example/ViewModifier/DarkViewModifier.swift +0 -22
- package/example/ios/Example.xcodeproj/project.pbxproj +0 -570
- package/example/ios/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcodeproj/project.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/contents.xcworkspacedata +0 -10
- package/example/ios/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/wem.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Podfile +0 -6
- package/example/ios/Podfile.lock +0 -16
- package/example/ios/Pods/Local Podspecs/MoMoUIKits.podspec.json +0 -29
- package/example/ios/Pods/Manifest.lock +0 -16
- package/example/ios/Pods/Pods.xcodeproj/project.pbxproj +0 -661
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/wem.xcuserdatad/xcschemes/xcschememanagement.plist +0 -25
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-prefix.pch +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.debug.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/MoMoUIKits/MoMoUIKits.release.xcconfig +0 -12
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.markdown +0 -3
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-acknowledgements.plist +0 -29
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-dummy.m +0 -5
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example-umbrella.h +0 -16
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.debug.xcconfig +0 -14
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.modulemap +0 -6
- package/example/ios/Pods/Target Support Files/Pods-Example/Pods-Example.release.xcconfig +0 -14
- package/ios/Colors+Spacing/Radius.swift +0 -11
- package/ios/Popup/Utils++.swift +0 -163
- package/ios/Theme.md +0 -18
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Background.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Border.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Card.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Error.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Primary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryDark.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/PrimaryLight.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Secondary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Success.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/TextSecondary.colorset/Contents.json +0 -0
- /package/{example/ios/Example/Assets.xcassets → ios/Resources/MoMoUIKitsColors.xcassets}/Warning.colorset/Contents.json +0 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// MARK: - Types
|
|
4
|
+
|
|
5
|
+
/// Tooltip placement relative to the anchor element.
|
|
6
|
+
public enum TooltipPlacement {
|
|
7
|
+
case top
|
|
8
|
+
case bottom
|
|
9
|
+
case left
|
|
10
|
+
case right
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// Cross-axis alignment of the tooltip relative to the anchor.
|
|
14
|
+
public enum TooltipAlign {
|
|
15
|
+
case start
|
|
16
|
+
case center
|
|
17
|
+
case end
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Describes a single action button displayed in the tooltip.
|
|
21
|
+
public struct TooltipButton {
|
|
22
|
+
public let title: String?
|
|
23
|
+
public let icon: String?
|
|
24
|
+
public let onPress: (() -> Void)?
|
|
25
|
+
|
|
26
|
+
public init(
|
|
27
|
+
title: String? = nil,
|
|
28
|
+
icon: String? = nil,
|
|
29
|
+
onPress: (() -> Void)? = nil
|
|
30
|
+
) {
|
|
31
|
+
self.title = title
|
|
32
|
+
self.icon = icon
|
|
33
|
+
self.onPress = onPress
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// State holder for controlling tooltip visibility imperatively.
|
|
38
|
+
/// Mirrors the Compose `TooltipState` / `rememberTooltipState()` pattern.
|
|
39
|
+
public final class TooltipState: ObservableObject {
|
|
40
|
+
@Published public private(set) var isVisible: Bool = false
|
|
41
|
+
|
|
42
|
+
public init() {}
|
|
43
|
+
|
|
44
|
+
/// Shows the tooltip.
|
|
45
|
+
public func show() {
|
|
46
|
+
isVisible = true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Hides the tooltip.
|
|
50
|
+
public func hide() {
|
|
51
|
+
isVisible = false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Toggles the tooltip visibility.
|
|
55
|
+
public func toggle() {
|
|
56
|
+
isVisible.toggle()
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// MARK: - Constants
|
|
61
|
+
|
|
62
|
+
private enum TooltipConst {
|
|
63
|
+
static let offset = Spacing.S // TOOLTIP_OFFSET
|
|
64
|
+
static let arrowSize: CGFloat = 6 // ARROW_SIZE
|
|
65
|
+
static let maxWidth: CGFloat = 300
|
|
66
|
+
static let cornerRadius = Radius.S
|
|
67
|
+
static let arrowCornerRadius: CGFloat = 2
|
|
68
|
+
static let arrowEdgeMargin = Spacing.M
|
|
69
|
+
static let arrowOverlap: CGFloat = 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// MARK: - Arrow Shape
|
|
73
|
+
|
|
74
|
+
/// Triangular arrow pointing away from the anchor in the given placement direction.
|
|
75
|
+
struct TooltipArrowShape: Shape {
|
|
76
|
+
let placement: TooltipPlacement
|
|
77
|
+
|
|
78
|
+
func path(in rect: CGRect) -> Path {
|
|
79
|
+
let w = rect.width
|
|
80
|
+
let h = rect.height
|
|
81
|
+
var path = Path()
|
|
82
|
+
switch placement {
|
|
83
|
+
case .top:
|
|
84
|
+
path.move(to: CGPoint(x: 0, y: 0))
|
|
85
|
+
path.addLine(to: CGPoint(x: w, y: 0))
|
|
86
|
+
path.addLine(to: CGPoint(x: w / 2, y: h))
|
|
87
|
+
case .bottom:
|
|
88
|
+
path.move(to: CGPoint(x: 0, y: h))
|
|
89
|
+
path.addLine(to: CGPoint(x: w, y: h))
|
|
90
|
+
path.addLine(to: CGPoint(x: w / 2, y: 0))
|
|
91
|
+
case .left:
|
|
92
|
+
path.move(to: CGPoint(x: 0, y: 0))
|
|
93
|
+
path.addLine(to: CGPoint(x: 0, y: h))
|
|
94
|
+
path.addLine(to: CGPoint(x: w, y: h / 2))
|
|
95
|
+
case .right:
|
|
96
|
+
path.move(to: CGPoint(x: w, y: 0))
|
|
97
|
+
path.addLine(to: CGPoint(x: w, y: h))
|
|
98
|
+
path.addLine(to: CGPoint(x: 0, y: h / 2))
|
|
99
|
+
}
|
|
100
|
+
path.closeSubpath()
|
|
101
|
+
return path
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// MARK: - Anchor bounds preference
|
|
106
|
+
|
|
107
|
+
private struct TooltipAnchorBoundsKey: PreferenceKey {
|
|
108
|
+
static var defaultValue: Anchor<CGRect>? = nil
|
|
109
|
+
static func reduce(value: inout Anchor<CGRect>?, nextValue: () -> Anchor<CGRect>?) {
|
|
110
|
+
value = nextValue() ?? value
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// MARK: - Tooltip
|
|
115
|
+
|
|
116
|
+
/// A tooltip component that wraps `content` (the anchor) and shows a positioned
|
|
117
|
+
/// tooltip popup with title, description, close button, and action buttons.
|
|
118
|
+
///
|
|
119
|
+
/// Port of the Compose / React Native `Tooltip` component.
|
|
120
|
+
public struct Tooltip<Content: View>: View {
|
|
121
|
+
@ObservedObject private var state: TooltipState
|
|
122
|
+
private let title: String?
|
|
123
|
+
private let description: String
|
|
124
|
+
private let buttons: [TooltipButton]
|
|
125
|
+
private let placement: TooltipPlacement
|
|
126
|
+
private let align: TooltipAlign
|
|
127
|
+
private let onVisibleChange: ((Bool) -> Void)?
|
|
128
|
+
private let onPressClose: (() -> Void)?
|
|
129
|
+
private let content: Content
|
|
130
|
+
|
|
131
|
+
public init(
|
|
132
|
+
state: TooltipState,
|
|
133
|
+
title: String? = nil,
|
|
134
|
+
description: String,
|
|
135
|
+
buttons: [TooltipButton] = [],
|
|
136
|
+
placement: TooltipPlacement = .top,
|
|
137
|
+
align: TooltipAlign = .center,
|
|
138
|
+
onVisibleChange: ((Bool) -> Void)? = nil,
|
|
139
|
+
onPressClose: (() -> Void)? = nil,
|
|
140
|
+
@ViewBuilder content: () -> Content
|
|
141
|
+
) {
|
|
142
|
+
self.state = state
|
|
143
|
+
self.title = title
|
|
144
|
+
self.description = description
|
|
145
|
+
self.buttons = buttons
|
|
146
|
+
self.placement = placement
|
|
147
|
+
self.align = align
|
|
148
|
+
self.onVisibleChange = onVisibleChange
|
|
149
|
+
self.onPressClose = onPressClose
|
|
150
|
+
self.content = content()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public var body: some View {
|
|
154
|
+
content
|
|
155
|
+
.anchorPreference(key: TooltipAnchorBoundsKey.self, value: .bounds) { $0 }
|
|
156
|
+
.overlayPreferenceValue(TooltipAnchorBoundsKey.self) { anchor in
|
|
157
|
+
GeometryReader { proxy in
|
|
158
|
+
if state.isVisible, let anchor = anchor {
|
|
159
|
+
let anchorRect = proxy[anchor]
|
|
160
|
+
TooltipPopupContent(
|
|
161
|
+
title: title,
|
|
162
|
+
description: description,
|
|
163
|
+
buttons: buttons,
|
|
164
|
+
placement: placement,
|
|
165
|
+
align: align,
|
|
166
|
+
onPressClose: onPressClose
|
|
167
|
+
)
|
|
168
|
+
.fixedSize()
|
|
169
|
+
.modifier(
|
|
170
|
+
TooltipPositionModifier(
|
|
171
|
+
anchorRect: anchorRect,
|
|
172
|
+
containerSize: proxy.size,
|
|
173
|
+
placement: placement,
|
|
174
|
+
align: align
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
.onChange(of: state.isVisible) { newValue in
|
|
181
|
+
onVisibleChange?(newValue)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// MARK: - Positioning
|
|
187
|
+
|
|
188
|
+
/// Positions the tooltip (already sized via `.fixedSize()`) relative to the anchor,
|
|
189
|
+
/// mirroring `TooltipPositionProvider.calculatePosition`.
|
|
190
|
+
private struct TooltipPositionModifier: ViewModifier {
|
|
191
|
+
let anchorRect: CGRect
|
|
192
|
+
let containerSize: CGSize
|
|
193
|
+
let placement: TooltipPlacement
|
|
194
|
+
let align: TooltipAlign
|
|
195
|
+
|
|
196
|
+
@State private var popupSize: CGSize = .zero
|
|
197
|
+
|
|
198
|
+
private var offsetPx: CGFloat {
|
|
199
|
+
TooltipConst.offset + TooltipConst.arrowSize
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func body(content: Content) -> some View {
|
|
203
|
+
content
|
|
204
|
+
.background(
|
|
205
|
+
GeometryReader { p in
|
|
206
|
+
Color.clear
|
|
207
|
+
.onAppear { popupSize = p.size }
|
|
208
|
+
.onChange(of: p.size) { popupSize = $0 }
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
.offset(x: originX, y: originY)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private var originX: CGFloat {
|
|
215
|
+
let popW = popupSize.width
|
|
216
|
+
switch placement {
|
|
217
|
+
case .top, .bottom:
|
|
218
|
+
return alignHorizontal(anchorRect.minX, anchorRect.width, popW)
|
|
219
|
+
case .left:
|
|
220
|
+
return anchorRect.minX - popW - offsetPx
|
|
221
|
+
case .right:
|
|
222
|
+
return anchorRect.maxX + offsetPx
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private var originY: CGFloat {
|
|
227
|
+
let popH = popupSize.height
|
|
228
|
+
switch placement {
|
|
229
|
+
case .top:
|
|
230
|
+
return anchorRect.minY - popH - offsetPx
|
|
231
|
+
case .bottom:
|
|
232
|
+
return anchorRect.maxY + offsetPx
|
|
233
|
+
case .left, .right:
|
|
234
|
+
return alignVertical(anchorRect.minY, anchorRect.height, popH)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private func alignHorizontal(_ anchorX: CGFloat, _ anchorW: CGFloat, _ popW: CGFloat) -> CGFloat {
|
|
239
|
+
switch align {
|
|
240
|
+
case .start: return anchorX
|
|
241
|
+
case .end: return anchorX + anchorW - popW
|
|
242
|
+
case .center: return anchorX + (anchorW - popW) / 2
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private func alignVertical(_ anchorY: CGFloat, _ anchorH: CGFloat, _ popH: CGFloat) -> CGFloat {
|
|
247
|
+
switch align {
|
|
248
|
+
case .start: return anchorY
|
|
249
|
+
case .end: return anchorY + anchorH - popH
|
|
250
|
+
case .center: return anchorY + (anchorH - popH) / 2
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// MARK: - Popup content
|
|
256
|
+
|
|
257
|
+
private struct TooltipPopupContent: View {
|
|
258
|
+
let title: String?
|
|
259
|
+
let description: String?
|
|
260
|
+
let buttons: [TooltipButton]
|
|
261
|
+
let placement: TooltipPlacement
|
|
262
|
+
let align: TooltipAlign
|
|
263
|
+
let onPressClose: (() -> Void)?
|
|
264
|
+
|
|
265
|
+
var body: some View {
|
|
266
|
+
ZStack {
|
|
267
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
268
|
+
HStack(alignment: .top, spacing: 0) {
|
|
269
|
+
VStack(alignment: .leading, spacing: 0) {
|
|
270
|
+
if let title = title, !title.isEmpty {
|
|
271
|
+
MomoText(title, typography: .headerSSemibold, color: Colors.black01)
|
|
272
|
+
.lineLimit(1)
|
|
273
|
+
.truncationMode(.tail)
|
|
274
|
+
.padding(.bottom, Spacing.XS)
|
|
275
|
+
}
|
|
276
|
+
if let description = description, !description.isEmpty {
|
|
277
|
+
MomoText(description, typography: .descriptionDefaultRegular, color: Colors.black01)
|
|
278
|
+
.lineLimit(2)
|
|
279
|
+
.truncationMode(.tail)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
.frame(maxWidth: .infinity, alignment: .leading)
|
|
283
|
+
|
|
284
|
+
if let onPressClose = onPressClose {
|
|
285
|
+
Spacer().frame(width: Spacing.M)
|
|
286
|
+
SwiftUI.Button(action: onPressClose) {
|
|
287
|
+
Icon(source: "navigation_close", size: 20, color: Colors.black01)
|
|
288
|
+
.frame(width: 20, height: 20)
|
|
289
|
+
}
|
|
290
|
+
.buttonStyle(.plain)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if !buttons.isEmpty {
|
|
295
|
+
Spacer().frame(height: Spacing.M)
|
|
296
|
+
TooltipButtons(buttons: buttons)
|
|
297
|
+
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
.padding(Spacing.M)
|
|
301
|
+
.frame(maxWidth: TooltipConst.maxWidth, alignment: .leading)
|
|
302
|
+
.background(Colors.black17)
|
|
303
|
+
.clipShape(RoundedRectangle(cornerRadius: TooltipConst.cornerRadius))
|
|
304
|
+
}
|
|
305
|
+
.overlay(arrow, alignment: arrowAlignment)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// MARK: Arrow
|
|
309
|
+
|
|
310
|
+
@ViewBuilder
|
|
311
|
+
private var arrow: some View {
|
|
312
|
+
let arrowWidth = TooltipConst.arrowSize * 2
|
|
313
|
+
let arrowHeight = TooltipConst.arrowSize
|
|
314
|
+
let isVertical = placement == .top || placement == .bottom
|
|
315
|
+
TooltipArrowShape(placement: placement)
|
|
316
|
+
.fill(Colors.black17)
|
|
317
|
+
.frame(
|
|
318
|
+
width: isVertical ? arrowWidth : arrowHeight,
|
|
319
|
+
height: isVertical ? arrowHeight : arrowWidth
|
|
320
|
+
)
|
|
321
|
+
.offset(x: arrowOffsetX, y: arrowOffsetY)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
private var arrowAlignment: Alignment {
|
|
325
|
+
switch placement {
|
|
326
|
+
case .top:
|
|
327
|
+
switch align {
|
|
328
|
+
case .start: return .bottomLeading
|
|
329
|
+
case .center: return .bottom
|
|
330
|
+
case .end: return .bottomTrailing
|
|
331
|
+
}
|
|
332
|
+
case .bottom:
|
|
333
|
+
switch align {
|
|
334
|
+
case .start: return .topLeading
|
|
335
|
+
case .center: return .top
|
|
336
|
+
case .end: return .topTrailing
|
|
337
|
+
}
|
|
338
|
+
case .left:
|
|
339
|
+
switch align {
|
|
340
|
+
case .start: return .topTrailing
|
|
341
|
+
case .center: return .trailing
|
|
342
|
+
case .end: return .bottomTrailing
|
|
343
|
+
}
|
|
344
|
+
case .right:
|
|
345
|
+
switch align {
|
|
346
|
+
case .start: return .topLeading
|
|
347
|
+
case .center: return .leading
|
|
348
|
+
case .end: return .bottomLeading
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
private var arrowOffsetX: CGFloat {
|
|
354
|
+
let arrowHeight = TooltipConst.arrowSize
|
|
355
|
+
switch placement {
|
|
356
|
+
case .left: return arrowHeight - TooltipConst.arrowOverlap
|
|
357
|
+
case .right: return -arrowHeight + TooltipConst.arrowOverlap
|
|
358
|
+
default:
|
|
359
|
+
switch align {
|
|
360
|
+
case .start: return TooltipConst.arrowEdgeMargin
|
|
361
|
+
case .end: return -TooltipConst.arrowEdgeMargin
|
|
362
|
+
case .center: return 0
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
private var arrowOffsetY: CGFloat {
|
|
368
|
+
let arrowHeight = TooltipConst.arrowSize
|
|
369
|
+
switch placement {
|
|
370
|
+
case .top: return arrowHeight - TooltipConst.arrowOverlap
|
|
371
|
+
case .bottom: return -arrowHeight + TooltipConst.arrowOverlap
|
|
372
|
+
default:
|
|
373
|
+
switch align {
|
|
374
|
+
case .start: return TooltipConst.arrowEdgeMargin
|
|
375
|
+
case .end: return -TooltipConst.arrowEdgeMargin
|
|
376
|
+
case .center: return 0
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// MARK: - Buttons row
|
|
383
|
+
|
|
384
|
+
private struct TooltipButtons: View {
|
|
385
|
+
let buttons: [TooltipButton]
|
|
386
|
+
|
|
387
|
+
var body: some View {
|
|
388
|
+
HStack(spacing: 0) {
|
|
389
|
+
content
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
@ViewBuilder
|
|
394
|
+
private var content: some View {
|
|
395
|
+
if buttons.count == 1 {
|
|
396
|
+
TooltipSingleButton(btn: buttons[0])
|
|
397
|
+
} else if buttons.count == 2 {
|
|
398
|
+
let primary = buttons[0]
|
|
399
|
+
let secondary = buttons[1]
|
|
400
|
+
let bothIcons = primary.icon != nil && secondary.icon != nil
|
|
401
|
+
if bothIcons {
|
|
402
|
+
TooltipIconButton(icon: secondary.icon ?? "", onPress: secondary.onPress ?? {})
|
|
403
|
+
Spacer().frame(width: Spacing.S)
|
|
404
|
+
TooltipIconButton(icon: primary.icon ?? "", onPress: primary.onPress ?? {})
|
|
405
|
+
} else {
|
|
406
|
+
TooltipSecondaryButton(title: secondary.title ?? "", onPress: secondary.onPress ?? {})
|
|
407
|
+
Spacer().frame(width: Spacing.S)
|
|
408
|
+
TooltipPrimaryButton(title: primary.title ?? "", onPress: primary.onPress ?? {})
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
ForEach(Array(buttons.enumerated()), id: \.offset) { index, btn in
|
|
412
|
+
if index > 0 {
|
|
413
|
+
Spacer().frame(width: Spacing.XXS)
|
|
414
|
+
}
|
|
415
|
+
TooltipSingleButton(btn: btn)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private func tooltipLimitWithEllipsis(_ value: String, max: Int = 16) -> String {
|
|
422
|
+
value.count > max ? String(value.prefix(max)) + "…" : value
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private struct TooltipSingleButton: View {
|
|
426
|
+
let btn: TooltipButton
|
|
427
|
+
|
|
428
|
+
var body: some View {
|
|
429
|
+
if let icon = btn.icon {
|
|
430
|
+
TooltipIconButton(icon: icon, onPress: btn.onPress ?? {})
|
|
431
|
+
} else {
|
|
432
|
+
TooltipPrimaryButton(title: btn.title ?? "", onPress: btn.onPress ?? {})
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private struct TooltipPrimaryButton: View {
|
|
438
|
+
let title: String
|
|
439
|
+
let onPress: () -> Void
|
|
440
|
+
|
|
441
|
+
var body: some View {
|
|
442
|
+
Button(
|
|
443
|
+
title: tooltipLimitWithEllipsis(title),
|
|
444
|
+
action: onPress,
|
|
445
|
+
type: .secondary,
|
|
446
|
+
size: .medium,
|
|
447
|
+
isFull: false
|
|
448
|
+
)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private struct TooltipSecondaryButton: View {
|
|
453
|
+
let title: String
|
|
454
|
+
let onPress: () -> Void
|
|
455
|
+
|
|
456
|
+
var body: some View {
|
|
457
|
+
SwiftUI.Button(action: onPress) {
|
|
458
|
+
MomoText(tooltipLimitWithEllipsis(title), typography: .actionSBold, color: Colors.black01)
|
|
459
|
+
.lineLimit(1)
|
|
460
|
+
.truncationMode(.tail)
|
|
461
|
+
.padding(.horizontal, Spacing.M)
|
|
462
|
+
.padding(.vertical, Spacing.S)
|
|
463
|
+
}
|
|
464
|
+
.buttonStyle(.plain)
|
|
465
|
+
// Mirrors Compose's `Modifier.weight(1f, fill = false)` on the secondary button:
|
|
466
|
+
// it yields space to the primary button first under width pressure.
|
|
467
|
+
.layoutPriority(-1)
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private struct TooltipIconButton: View {
|
|
472
|
+
@Environment(\.momoFontScale) private var fontScale
|
|
473
|
+
let icon: String
|
|
474
|
+
let onPress: () -> Void
|
|
475
|
+
|
|
476
|
+
var body: some View {
|
|
477
|
+
let iconButtonSize = fontScale(36)
|
|
478
|
+
SwiftUI.Button(action: onPress) {
|
|
479
|
+
Icon(source: icon, size: 24)
|
|
480
|
+
.frame(width: iconButtonSize, height: iconButtonSize)
|
|
481
|
+
.background(Colors.black01)
|
|
482
|
+
.clipShape(RoundedRectangle(cornerRadius: Radius.XL))
|
|
483
|
+
}
|
|
484
|
+
.buttonStyle(.plain)
|
|
485
|
+
}
|
|
486
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// File: ios/Typography/FontScale.swift
|
|
2
|
+
// Created At: 2026-08-06 10:00:00 +07:00
|
|
3
|
+
// Created By: AI
|
|
4
|
+
// AI Agent: Claude Code
|
|
5
|
+
// Model: claude-opus-5
|
|
6
|
+
|
|
7
|
+
// AI-GENERATED START: reactive font-scale snapshot injected through the environment
|
|
8
|
+
import SwiftUI
|
|
9
|
+
|
|
10
|
+
/// Immutable snapshot of the kit's font-scale inputs. The formula is a 1:1 port of Compose's
|
|
11
|
+
/// `scaleSize()` (`const/Typography.kt`): the device-width scale and the OS Dynamic Type scale are
|
|
12
|
+
/// computed independently and the larger one wins.
|
|
13
|
+
public struct FontScale: Equatable {
|
|
14
|
+
private static let defaultScreenSize: CGFloat = 375
|
|
15
|
+
private static let maxFontScale: CGFloat = 1.5
|
|
16
|
+
private static let maxDeviceScale: CGFloat = 5
|
|
17
|
+
|
|
18
|
+
public let userScaleRate: CGFloat
|
|
19
|
+
public let useOSScaleRate: Bool
|
|
20
|
+
/// OS Dynamic Type factor at the time the snapshot was taken.
|
|
21
|
+
public let osFontScale: CGFloat
|
|
22
|
+
|
|
23
|
+
public init(userScaleRate: CGFloat = 1, useOSScaleRate: Bool = true, osFontScale: CGFloat = 1) {
|
|
24
|
+
self.userScaleRate = userScaleRate
|
|
25
|
+
self.useOSScaleRate = useOSScaleRate
|
|
26
|
+
self.osFontScale = osFontScale
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// `callAsFunction` so call sites keep reading like Compose's `scaleSize(16)`.
|
|
30
|
+
public func callAsFunction(_ size: CGFloat) -> CGFloat {
|
|
31
|
+
guard useOSScaleRate else { return size * userScaleRate }
|
|
32
|
+
|
|
33
|
+
// Device width is read live rather than snapshotted, matching Compose's
|
|
34
|
+
// getScreenDimensions() read inside scaleSize(), so a rotation or split-view resize counts.
|
|
35
|
+
let deviceScale = UIScreen.main.bounds.width / Self.defaultScreenSize
|
|
36
|
+
|
|
37
|
+
var deviceScaled = size
|
|
38
|
+
var osScaled = size
|
|
39
|
+
if deviceScale > 1 {
|
|
40
|
+
deviceScaled = min(size * deviceScale, size + Self.maxDeviceScale)
|
|
41
|
+
}
|
|
42
|
+
if osFontScale > 1 {
|
|
43
|
+
osScaled = min(size * osFontScale, size * Self.maxFontScale)
|
|
44
|
+
}
|
|
45
|
+
return max(deviceScaled, osScaled)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// `.system(size:)` is a fixed-size font — SwiftUI does not Dynamic-Type it — so the scale has to
|
|
49
|
+
/// be baked into the point size here, and there is no double-scaling to undo.
|
|
50
|
+
public func font(size: CGFloat, weight: Font.Weight = .regular) -> Font {
|
|
51
|
+
.system(size: self(size), weight: weight)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// A host-pushed mini-app context wins per field, matching Compose's
|
|
55
|
+
/// `context?.useOSScaleRate ?: KitFontScale.useOSScaleRate`.
|
|
56
|
+
public func overriding(userScaleRate: CGFloat?, useOSScaleRate: Bool?) -> FontScale {
|
|
57
|
+
guard userScaleRate != nil || useOSScaleRate != nil else { return self }
|
|
58
|
+
return FontScale(
|
|
59
|
+
userScaleRate: userScaleRate ?? self.userScaleRate,
|
|
60
|
+
useOSScaleRate: useOSScaleRate ?? self.useOSScaleRate,
|
|
61
|
+
osFontScale: osFontScale
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private struct FontScaleKey: EnvironmentKey {
|
|
67
|
+
/// Computed, never stored: a bare `UIHostingController` root that never got `.momoFontScale()`
|
|
68
|
+
/// still reads the live store instead of a value frozen at first access.
|
|
69
|
+
static var defaultValue: FontScale { FontScaleStore.shared.snapshot }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public extension EnvironmentValues {
|
|
73
|
+
var momoFontScale: FontScale {
|
|
74
|
+
get { self[FontScaleKey.self] }
|
|
75
|
+
set { self[FontScaleKey.self] = newValue }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public extension View {
|
|
80
|
+
/// Injects a Dynamic-Type-observing font scale and re-renders this subtree on change.
|
|
81
|
+
/// Use at every `UIHostingController` root that is not inside a `NavigationContainer`.
|
|
82
|
+
func momoFontScale(_ store: FontScaleStore = .shared, context: MiniAppContext? = nil) -> some View {
|
|
83
|
+
modifier(MomoFontScaleModifier(store: store, context: context))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// `@ObservedObject` is load-bearing, same as `MomoLocalizeModifier`: `@Environment(\.momoFontScale)`
|
|
88
|
+
/// only invalidates when the boxed snapshot changes, which requires *this* body to re-run on publish.
|
|
89
|
+
private struct MomoFontScaleModifier: ViewModifier {
|
|
90
|
+
@ObservedObject var store: FontScaleStore
|
|
91
|
+
let context: MiniAppContext?
|
|
92
|
+
|
|
93
|
+
func body(content: Content) -> some View {
|
|
94
|
+
content.environment(
|
|
95
|
+
\.momoFontScale,
|
|
96
|
+
store.snapshot.overriding(
|
|
97
|
+
userScaleRate: context?.userScaleRate,
|
|
98
|
+
useOSScaleRate: context?.useOSScaleRate
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// AI-GENERATED END: reactive font-scale snapshot injected through the environment
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
/// App-wide font-scale inputs for the SwiftUI kit, and the single place they publish from.
|
|
4
|
+
/// The host writes `update(...)` at startup (from the cache-backed `FontScaleProvider`) and again
|
|
5
|
+
/// whenever the in-app font-size preference changes; the OS Dynamic Type factor is tracked here.
|
|
6
|
+
/// Mirrors Compose's `KitFontScale` + `ProvideOsFontScale` pair, including its live behaviour —
|
|
7
|
+
/// views re-render on change as long as the subtree got `.momoFontScale()` (`NavigationContainer`
|
|
8
|
+
/// applies it). When `useOSScaleRate` is false the kit applies `userScaleRate` instead of the OS factor.
|
|
9
|
+
public final class FontScaleStore: ObservableObject {
|
|
10
|
+
public static let shared = FontScaleStore()
|
|
11
|
+
|
|
12
|
+
@Published public private(set) var userScaleRate: CGFloat = 1
|
|
13
|
+
@Published public private(set) var useOSScaleRate: Bool = true
|
|
14
|
+
@Published public private(set) var osFontScale: CGFloat = FontScaleStore.currentOsFontScale()
|
|
15
|
+
|
|
16
|
+
private init() {
|
|
17
|
+
NotificationCenter.default.addObserver(
|
|
18
|
+
self,
|
|
19
|
+
selector: #selector(contentSizeCategoryDidChange),
|
|
20
|
+
name: UIContentSizeCategory.didChangeNotification,
|
|
21
|
+
object: nil
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public var snapshot: FontScale {
|
|
26
|
+
FontScale(
|
|
27
|
+
userScaleRate: userScaleRate,
|
|
28
|
+
useOSScaleRate: useOSScaleRate,
|
|
29
|
+
osFontScale: osFontScale
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public func update(userScaleRate: CGFloat, useOSScaleRate: Bool) {
|
|
34
|
+
onMain {
|
|
35
|
+
// Re-assigning an unchanged value would still publish and invalidate every kit subtree.
|
|
36
|
+
if self.userScaleRate != userScaleRate { self.userScaleRate = userScaleRate }
|
|
37
|
+
if self.useOSScaleRate != useOSScaleRate { self.useOSScaleRate = useOSScaleRate }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@objc private func contentSizeCategoryDidChange() {
|
|
42
|
+
let next = Self.currentOsFontScale()
|
|
43
|
+
guard next != osFontScale else { return }
|
|
44
|
+
osFontScale = next
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/// Same factor Compose's iOS `ProvideOsFontScale` derives from
|
|
48
|
+
/// `UIFont.preferredFont(forTextStyle: .body).pointSize / 17`, so both iOS surfaces scale alike.
|
|
49
|
+
static func currentOsFontScale() -> CGFloat {
|
|
50
|
+
let base = UIFont.systemFont(ofSize: UIFont.labelFontSize)
|
|
51
|
+
return UIFontMetrics.default.scaledFont(for: base).pointSize / base.pointSize
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Synchronous when already on main — an async hop would defer the change by a frame.
|
|
55
|
+
private func onMain(_ work: @escaping () -> Void) {
|
|
56
|
+
if Thread.isMainThread { work() } else { DispatchQueue.main.async(execute: work) }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Design-system rollout flag for the SwiftUI kit, mirroring Compose's `KitDesignSystem`.
|
|
4
|
+
/// The kit is sandboxed and can't read the host config, so the host writes `update(_:)` once at
|
|
5
|
+
/// startup from `DesignSystemConfig`. Startup-only and non-reactive, like `FontScaleStore` was
|
|
6
|
+
/// before it gained live updates: there is no SwiftUI navigation container to re-render from.
|
|
7
|
+
public final class KitDesignSystem {
|
|
8
|
+
public static let shared = KitDesignSystem()
|
|
9
|
+
|
|
10
|
+
public private(set) var isWhiteList: Bool = false
|
|
11
|
+
|
|
12
|
+
private init() {}
|
|
13
|
+
|
|
14
|
+
public func update(isWhiteList: Bool) {
|
|
15
|
+
self.isWhiteList = isWhiteList
|
|
16
|
+
}
|
|
17
|
+
}
|