@momo-kits/native-kits 0.162.2-beta.1 → 0.162.2-sp.1-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/settings.local.json +55 -0
- package/build.gradle.kts +11 -0
- package/compose/build.gradle.kts +186 -0
- package/compose/build.gradle.kts.backup +186 -0
- package/compose/compose.podspec +47 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +6 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +123 -0
- package/compose/src/commonMain/composeResources/font/momosignature.otf +0 -0
- package/compose/src/commonMain/composeResources/font/momotrustdisplay.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_black.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_bold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_heavy.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_light.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_medium.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_regular.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_semibold.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_thin.ttf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.otf +0 -0
- package/compose/src/commonMain/composeResources/font/sfprotext_ultralight.ttf +0 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +57 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +109 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +201 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +222 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +48 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +305 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +33 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +921 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +121 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +403 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +69 -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 +85 -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 +340 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +198 -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 +123 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +94 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +136 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +543 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +23 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +76 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +148 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +116 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +452 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +172 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +255 -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 +233 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +259 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +241 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +364 -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 +92 -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 +352 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +103 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +70 -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 +17 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +96 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +92 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +135 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +214 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +590 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +177 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +205 -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 +239 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +191 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +306 -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 +188 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +285 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +35 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +2 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +57 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +68 -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 +14 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +50 -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 +253 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +133 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +104 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +145 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +345 -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 +556 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +161 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +243 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +86 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +187 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +315 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +80 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +34 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +370 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +167 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +72 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +66 -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 +70 -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 +105 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +33 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +186 -0
- package/gradle/libs.versions.toml +67 -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/Components.swift +34 -43
- package/ios/Application/FloatingButton.swift +10 -8
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +102 -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 +151 -0
- package/ios/Application/Navigation/NavigationOptions.swift +109 -0
- package/ios/Application/Navigation/Navigator.swift +360 -0
- package/ios/Application/Navigation/Overplay/BottomSheet.swift +176 -0
- package/ios/Application/Navigation/Overplay/ModalScreen.swift +68 -0
- package/ios/Application/Navigation/Overplay/SnackBar.swift +144 -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/StackScreen.swift +449 -0
- package/ios/Avatar/Avatar.swift +193 -0
- package/ios/Badge/Badge.swift +11 -13
- package/ios/Badge/BadgeRibbon.swift +12 -19
- package/ios/BaselineView/BaselineView.swift +163 -0
- package/ios/Button/Button.swift +62 -23
- package/ios/Carousel/Carousel.swift +181 -0
- package/ios/Checkbox/Checkbox.swift +14 -12
- package/ios/Chip/Chip.swift +23 -9
- package/ios/Collapse/Collapse.swift +182 -0
- package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
- package/ios/Colors+Radius+Spacing/Theme.swift +295 -0
- package/ios/DateTimePicker/DateTimePicker.swift +210 -0
- package/ios/DateTimePicker/DateTimePickerTypes.swift +55 -0
- package/ios/DateTimePicker/DateTimePickerUtils.swift +167 -0
- package/ios/DateTimePicker/WheelPicker.swift +161 -0
- package/ios/Divider/Divider.swift +16 -0
- package/ios/Icon/Icon.swift +6 -1
- package/ios/IconButton/IconButton.swift +112 -0
- package/ios/Information/Information.swift +151 -0
- package/ios/Input/Input.swift +42 -25
- package/ios/Input/InputPhoneNumber.swift +21 -19
- package/ios/Input/InputSearch.swift +101 -45
- package/ios/Input/InputTextArea.swift +67 -40
- package/ios/InputDropDown/InputDropDown.swift +198 -0
- package/ios/InputMoney/InputMoney.swift +335 -0
- package/ios/InputOTP/InputOTP.swift +210 -0
- package/ios/Loader/Loader.swift +113 -0
- package/ios/MoMoUIKits.podspec +5 -0
- package/ios/OTPKeyboard/KeyboardButton.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 +10 -12
- package/ios/Popup/PopupInput.swift +2 -6
- package/ios/Popup/PopupNotify.swift +219 -0
- package/ios/ProgressInfo/ProgressInfo.swift +294 -0
- package/ios/Radio/Radio.swift +70 -0
- package/ios/Rating/Rating.swift +82 -0
- package/ios/Skeleton/Skeleton.swift +99 -0
- package/ios/Slider/Slider.swift +237 -0
- package/ios/Stepper/Stepper.swift +220 -0
- package/ios/Steps/Steps.swift +451 -0
- package/ios/SuggestAction/SuggestAction.swift +97 -0
- package/ios/Swipeable/SwipeCell.swift +185 -0
- package/ios/Swipeable/SwipeCellModel.swift +21 -0
- package/ios/TabView/TabView.swift +530 -0
- package/ios/Tag/Tag.swift +98 -0
- package/ios/Template/TrustBanner/TrustBanner.swift +2 -0
- package/ios/Title/Title.swift +269 -0
- package/ios/Tooltip/Tooltip.swift +482 -0
- package/ios/Typography/Text.swift +86 -24
- package/ios/Typography/Typography.swift +31 -6
- package/ios/Uploader/Uploader.swift +183 -0
- package/ios/native-kits.podspec +58 -8
- package/ios-demo/MoMoUIKitsDemo.podspec +18 -0
- package/ios-demo/README.md +120 -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 +72 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AnimatedHeaderDemo.swift +110 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/AvatarDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/BadgeDemo.swift +269 -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 +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CarouselDemo.swift +126 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CheckboxDemo.swift +63 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ChipDemo.swift +20 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/CollapseDemo.swift +93 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DateTimePickerDemo.swift +124 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DividerDemo.swift +14 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/HeaderUserDemo.swift +49 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +261 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IconButtonDemo.swift +58 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ImageDemo.swift +78 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InformationDemo.swift +138 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDemo.swift +111 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputDropDownDemo.swift +136 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputMoneyDemo.swift +107 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputOTPDemo.swift +73 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputPhoneNumberDemo.swift +100 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputSearchDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/InputTextAreaDemo.swift +57 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/LoaderDemo.swift +70 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +227 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PaginationDemo.swift +72 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +175 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +121 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +76 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ProgressInfoDemo.swift +89 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RadioDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/RatingDemo.swift +56 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SkeletonDemo.swift +32 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SliderDemo.swift +150 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SnackBarDemo.swift +138 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepperDemo.swift +103 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/StepsDemo.swift +94 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SuggestActionDemo.swift +79 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwipeDemo.swift +152 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/SwitchDemo.swift +33 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TabViewDemo.swift +190 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TagDemo.swift +34 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ThemeDemo.swift +182 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TitleDemo.swift +109 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TooltipDemo.swift +187 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/TypographyDemo.swift +87 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/UploaderDemo.swift +131 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SwiftUIDemoBrowserView.swift +50 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoModels.swift +15 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/UIKitsDemoRegistry.swift +73 -0
- package/local.properties +8 -0
- package/package.json +1 -1
- package/publish.sh +50 -0
- package/scripts/gen-ios-color.mjs +136 -0
- package/settings.gradle.kts +64 -0
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Example.xcscheme +0 -32
- package/example/ios/Example.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/WorkspaceSettings.xcsettings +0 -16
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +0 -6
- package/example/ios/Example.xcworkspace/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -5
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/MoMoUIKits.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/Pods-Example.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImage.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SDWebImageSwiftUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/SkeletonUI.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios-LottiePrivacyInfo.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/lottie-ios.xcscheme +0 -58
- package/example/ios/Pods/Pods.xcodeproj/xcuserdata/sonnguyen.xcuserdatad/xcschemes/xcschememanagement.plist +0 -60
- package/ios/Typography/MomoFontScale.swift +0 -36
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import SwiftUI
|
|
3
|
+
|
|
4
|
+
/// Native SwiftUI port of the Compose `Skeleton` component.
|
|
5
|
+
///
|
|
6
|
+
/// Fills the available space with a base placeholder color and overlays a
|
|
7
|
+
/// horizontal shimmer gradient that animates left-to-right on an infinite loop.
|
|
8
|
+
///
|
|
9
|
+
/// Mirrors the Compose surface: `Skeleton()` takes no required arguments and
|
|
10
|
+
/// fills its container. The shimmer animation parameters match the Compose
|
|
11
|
+
/// defaults (`durationMillis = 1000`, shimmer band = 60% of container width).
|
|
12
|
+
public struct Skeleton: View {
|
|
13
|
+
// Matches Compose `DEFAULT_DURATION_MILLIS = 1000`.
|
|
14
|
+
private let durationMillis: Int
|
|
15
|
+
// Matches Compose `angleOfAxisY = 0f` (horizontal gradient).
|
|
16
|
+
private let angleOfAxisY: CGFloat
|
|
17
|
+
|
|
18
|
+
public init(
|
|
19
|
+
durationMillis: Int = 1000,
|
|
20
|
+
angleOfAxisY: CGFloat = 0
|
|
21
|
+
) {
|
|
22
|
+
self.durationMillis = durationMillis
|
|
23
|
+
self.angleOfAxisY = angleOfAxisY
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public var body: some View {
|
|
27
|
+
GeometryReader { proxy in
|
|
28
|
+
ShimmerLoading(
|
|
29
|
+
size: proxy.size,
|
|
30
|
+
durationMillis: durationMillis,
|
|
31
|
+
angleOfAxisY: angleOfAxisY
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
// Compose base background (`Colors.black_05`).
|
|
35
|
+
.background(Colors.black05)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Internal shimmer overlay. Reproduces Compose's `Modifier.shimmerLoadingAnimation`:
|
|
40
|
+
/// a linear gradient band (60% of the container width) translated across the
|
|
41
|
+
/// surface on an infinite, restarting, linearly-eased loop.
|
|
42
|
+
private struct ShimmerLoading: View {
|
|
43
|
+
let size: CGSize
|
|
44
|
+
let durationMillis: Int
|
|
45
|
+
let angleOfAxisY: CGFloat
|
|
46
|
+
|
|
47
|
+
@State private var phase: CGFloat = 0
|
|
48
|
+
|
|
49
|
+
// Matches Compose `SHADOW_BRUSH_PERCENTAGE = 0.6f`.
|
|
50
|
+
private static let shadowBrushPercentage: CGFloat = 0.6
|
|
51
|
+
|
|
52
|
+
// Pre-computed shimmer colors (Colors.black_05, black_03, black_05).
|
|
53
|
+
private static let shimmerColors: [Color] = [
|
|
54
|
+
Colors.black05,
|
|
55
|
+
Colors.black03,
|
|
56
|
+
Colors.black05,
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
private var widthOfShadowBrush: CGFloat {
|
|
60
|
+
(size.width * Self.shadowBrushPercentage).rounded(.towardZero)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Compose animates from 0 to (durationMillis + widthOfShadowBrush). Since the
|
|
64
|
+
// band is anchored at the animated value, the meaningful travel range spans
|
|
65
|
+
// from off-screen-left to off-screen-right: -widthOfShadowBrush ... maxWidth.
|
|
66
|
+
private var animationTarget: CGFloat {
|
|
67
|
+
size.width + widthOfShadowBrush
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var body: some View {
|
|
71
|
+
let startX = phase - widthOfShadowBrush
|
|
72
|
+
let endX = phase
|
|
73
|
+
|
|
74
|
+
Rectangle()
|
|
75
|
+
.fill(
|
|
76
|
+
LinearGradient(
|
|
77
|
+
gradient: Gradient(colors: Self.shimmerColors),
|
|
78
|
+
startPoint: .init(
|
|
79
|
+
x: size.width > 0 ? startX / size.width : 0,
|
|
80
|
+
y: 0
|
|
81
|
+
),
|
|
82
|
+
endPoint: .init(
|
|
83
|
+
x: size.width > 0 ? endX / size.width : 0,
|
|
84
|
+
y: size.height > 0 ? angleOfAxisY / size.height : 0
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
.onAppear {
|
|
89
|
+
guard size.width > 0 else { return }
|
|
90
|
+
phase = 0
|
|
91
|
+
withAnimation(
|
|
92
|
+
.linear(duration: Double(durationMillis) / 1000.0)
|
|
93
|
+
.repeatForever(autoreverses: false)
|
|
94
|
+
) {
|
|
95
|
+
phase = animationTarget
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// ── Design constants ─────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
private let THUMB_SIZE: CGFloat = 20
|
|
6
|
+
private let THUMB_BORDER: CGFloat = 3
|
|
7
|
+
private let TRACK_HEIGHT: CGFloat = 4
|
|
8
|
+
private let LABEL_GAP: CGFloat = 4 // space between label and thumb top (Spacing.XS)
|
|
9
|
+
|
|
10
|
+
// ── Pure helpers (mirrors Compose / RN helpers) ──────────────────────────────
|
|
11
|
+
|
|
12
|
+
private func clampF(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
|
|
13
|
+
Swift.min(Swift.max(value, minV), maxV)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Converts a raw touch position into a snapped slider value.
|
|
17
|
+
/// Direct port of Compose getValueForPosition().
|
|
18
|
+
private func getValueForPosition(
|
|
19
|
+
positionInView: CGFloat,
|
|
20
|
+
containerWidth: CGFloat,
|
|
21
|
+
thumbWidth: CGFloat,
|
|
22
|
+
minV: CGFloat,
|
|
23
|
+
maxV: CGFloat,
|
|
24
|
+
step: CGFloat
|
|
25
|
+
) -> CGFloat {
|
|
26
|
+
let availableSpace = containerWidth - thumbWidth
|
|
27
|
+
if availableSpace <= 0 { return minV }
|
|
28
|
+
let relStepUnit = step / (maxV - minV)
|
|
29
|
+
var relPosition = (positionInView - thumbWidth / 2) / availableSpace
|
|
30
|
+
let relOffset = relPosition.truncatingRemainder(dividingBy: relStepUnit)
|
|
31
|
+
relPosition -= relOffset
|
|
32
|
+
if relStepUnit != 0, relOffset / relStepUnit >= 0.5 { relPosition += relStepUnit }
|
|
33
|
+
let steps = (relPosition / relStepUnit).rounded()
|
|
34
|
+
return clampF(minV + steps * step, minV, maxV)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Direct port of Compose isLowCloser().
|
|
38
|
+
private func isLowCloser(_ downX: CGFloat, _ lowPosition: CGFloat, _ highPosition: CGFloat) -> Bool {
|
|
39
|
+
if lowPosition == highPosition { return downX < lowPosition }
|
|
40
|
+
return abs(downX - lowPosition) < abs(downX - highPosition)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func valueToFraction(_ value: CGFloat, _ minV: CGFloat, _ maxV: CGFloat) -> CGFloat {
|
|
44
|
+
if maxV == minV { return 0 }
|
|
45
|
+
return clampF((value - minV) / (maxV - minV), 0, 1)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private func formatLabel(_ value: CGFloat) -> String {
|
|
49
|
+
if value == value.rounded() {
|
|
50
|
+
return String(Int(value))
|
|
51
|
+
}
|
|
52
|
+
return String(format: "%.1f", value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/// Range slider (or single-thumb when `disableRange` = true), ported from Compose.
|
|
58
|
+
///
|
|
59
|
+
/// SwiftUI ships a built-in `Slider`; this custom type is named `MomoSlider`
|
|
60
|
+
/// (convention, like `MomoText`) to avoid module-wide shadowing.
|
|
61
|
+
public struct MomoSlider: View {
|
|
62
|
+
@Environment(\.appTheme) private var theme
|
|
63
|
+
|
|
64
|
+
private let minV: CGFloat
|
|
65
|
+
private let maxV: CGFloat
|
|
66
|
+
private let step: CGFloat
|
|
67
|
+
private let initLow: CGFloat
|
|
68
|
+
private let initHigh: CGFloat
|
|
69
|
+
private let minRange: CGFloat
|
|
70
|
+
private let disableRange: Bool
|
|
71
|
+
private let floatingLabel: Bool
|
|
72
|
+
private let disabled: Bool
|
|
73
|
+
private let onValueChanged: (CGFloat, CGFloat, Bool) -> Void
|
|
74
|
+
private let onSliderTouchStart: (CGFloat, CGFloat) -> Void
|
|
75
|
+
private let onSliderTouchEnd: (CGFloat, CGFloat) -> Void
|
|
76
|
+
|
|
77
|
+
@State private var lowValue: CGFloat = 0
|
|
78
|
+
@State private var highValue: CGFloat = 0
|
|
79
|
+
@State private var draggingLow: Bool = true
|
|
80
|
+
@State private var isDragging: Bool = false
|
|
81
|
+
@State private var didInit: Bool = false
|
|
82
|
+
|
|
83
|
+
public init(
|
|
84
|
+
min: CGFloat,
|
|
85
|
+
max: CGFloat,
|
|
86
|
+
step: CGFloat,
|
|
87
|
+
low: CGFloat? = nil,
|
|
88
|
+
high: CGFloat? = nil,
|
|
89
|
+
minRange: CGFloat = 0,
|
|
90
|
+
disableRange: Bool = false,
|
|
91
|
+
floatingLabel: Bool = false,
|
|
92
|
+
disabled: Bool = false,
|
|
93
|
+
onValueChanged: @escaping (CGFloat, CGFloat, Bool) -> Void = { _, _, _ in },
|
|
94
|
+
onSliderTouchStart: @escaping (CGFloat, CGFloat) -> Void = { _, _ in },
|
|
95
|
+
onSliderTouchEnd: @escaping (CGFloat, CGFloat) -> Void = { _, _ in }
|
|
96
|
+
) {
|
|
97
|
+
self.minV = min
|
|
98
|
+
self.maxV = max
|
|
99
|
+
self.step = step
|
|
100
|
+
self.minRange = minRange
|
|
101
|
+
self.disableRange = disableRange
|
|
102
|
+
self.floatingLabel = floatingLabel
|
|
103
|
+
self.disabled = disabled
|
|
104
|
+
self.onValueChanged = onValueChanged
|
|
105
|
+
self.onSliderTouchStart = onSliderTouchStart
|
|
106
|
+
self.onSliderTouchEnd = onSliderTouchEnd
|
|
107
|
+
|
|
108
|
+
self.initLow = clampF(low ?? min, min, max)
|
|
109
|
+
self.initHigh = disableRange ? max : clampF(high ?? max, min, max)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private var thumbColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
|
|
113
|
+
private var trackActiveColor: Color { disabled ? theme.colors.text.disable : theme.colors.primary }
|
|
114
|
+
private var trackInactiveColor: Color { theme.colors.background.default }
|
|
115
|
+
|
|
116
|
+
public var body: some View {
|
|
117
|
+
GeometryReader { geo in
|
|
118
|
+
let trackWidth = geo.size.width
|
|
119
|
+
let availableWidth = max(trackWidth - THUMB_SIZE, 0)
|
|
120
|
+
let topPad: CGFloat = floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0
|
|
121
|
+
|
|
122
|
+
let lowFraction = valueToFraction(lowValue, minV, maxV)
|
|
123
|
+
let highFraction = valueToFraction(highValue, minV, maxV)
|
|
124
|
+
|
|
125
|
+
let activeStartFraction = disableRange ? 0 : lowFraction
|
|
126
|
+
let activeEndFraction = disableRange ? lowFraction : highFraction
|
|
127
|
+
let activeStartX = THUMB_SIZE / 2 + availableWidth * activeStartFraction
|
|
128
|
+
let activeWidth = availableWidth * (activeEndFraction - activeStartFraction)
|
|
129
|
+
|
|
130
|
+
ZStack(alignment: .topLeading) {
|
|
131
|
+
// ── Floating label ────────────────────────────────────────────
|
|
132
|
+
if floatingLabel && isDragging {
|
|
133
|
+
let activeFraction = draggingLow ? lowFraction : highFraction
|
|
134
|
+
let activeValue = draggingLow ? lowValue : highValue
|
|
135
|
+
MomoText(formatLabel(activeValue), typography: .descriptionXsRegular, color: theme.colors.text.default)
|
|
136
|
+
.padding(.horizontal, Spacing.XS)
|
|
137
|
+
.padding(.vertical, Spacing.XXS)
|
|
138
|
+
.background(Colors.black01)
|
|
139
|
+
.clipShape(RoundedRectangle(cornerRadius: Radius.XS))
|
|
140
|
+
.offset(x: availableWidth * activeFraction, y: 0)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Inactive track (full width) ───────────────────────────────
|
|
144
|
+
Capsule()
|
|
145
|
+
.fill(trackInactiveColor)
|
|
146
|
+
.frame(width: max(trackWidth - THUMB_SIZE, 0), height: TRACK_HEIGHT)
|
|
147
|
+
.offset(x: THUMB_SIZE / 2, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
|
|
148
|
+
|
|
149
|
+
// ── Active track (selected range / single value) ──────────────
|
|
150
|
+
if activeWidth > 0 {
|
|
151
|
+
Capsule()
|
|
152
|
+
.fill(trackActiveColor)
|
|
153
|
+
.frame(width: activeWidth, height: TRACK_HEIGHT)
|
|
154
|
+
.offset(x: activeStartX, y: topPad + (THUMB_SIZE - TRACK_HEIGHT) / 2)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Low thumb ─────────────────────────────────────────────────
|
|
158
|
+
thumb
|
|
159
|
+
.offset(x: availableWidth * lowFraction, y: topPad)
|
|
160
|
+
|
|
161
|
+
// ── High thumb (range mode only) ──────────────────────────────
|
|
162
|
+
if !disableRange {
|
|
163
|
+
thumb
|
|
164
|
+
.offset(x: availableWidth * highFraction, y: topPad)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
.frame(width: trackWidth, height: topPad + THUMB_SIZE, alignment: .topLeading)
|
|
168
|
+
.contentShape(Rectangle())
|
|
169
|
+
.gesture(dragGesture(trackWidth: trackWidth))
|
|
170
|
+
}
|
|
171
|
+
.frame(height: (floatingLabel ? (THUMB_SIZE + LABEL_GAP) : 0) + THUMB_SIZE)
|
|
172
|
+
.onAppear {
|
|
173
|
+
if !didInit {
|
|
174
|
+
lowValue = initLow
|
|
175
|
+
highValue = initHigh
|
|
176
|
+
didInit = true
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private var thumb: some View {
|
|
182
|
+
Circle()
|
|
183
|
+
.fill(thumbColor)
|
|
184
|
+
.frame(width: THUMB_SIZE, height: THUMB_SIZE)
|
|
185
|
+
.overlay(
|
|
186
|
+
Circle().strokeBorder(theme.colors.background.surface, lineWidth: THUMB_BORDER)
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func dragGesture(trackWidth: CGFloat) -> some Gesture {
|
|
191
|
+
DragGesture(minimumDistance: 0)
|
|
192
|
+
.onChanged { gesture in
|
|
193
|
+
if disabled { return }
|
|
194
|
+
let touchX = gesture.location.x
|
|
195
|
+
|
|
196
|
+
if !isDragging {
|
|
197
|
+
// Finger down: pick the closer thumb
|
|
198
|
+
let lowCenterX = THUMB_SIZE / 2 +
|
|
199
|
+
valueToFraction(lowValue, minV, maxV) * (trackWidth - THUMB_SIZE)
|
|
200
|
+
let highCenterX = THUMB_SIZE / 2 +
|
|
201
|
+
valueToFraction(highValue, minV, maxV) * (trackWidth - THUMB_SIZE)
|
|
202
|
+
draggingLow = disableRange || isLowCloser(touchX, lowCenterX, highCenterX)
|
|
203
|
+
isDragging = true
|
|
204
|
+
onSliderTouchStart(lowValue, highValue)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
applyDrag(positionX: touchX, trackWidthPx: trackWidth)
|
|
208
|
+
}
|
|
209
|
+
.onEnded { _ in
|
|
210
|
+
if disabled { return }
|
|
211
|
+
isDragging = false
|
|
212
|
+
onSliderTouchEnd(lowValue, highValue)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private func applyDrag(positionX: CGFloat, trackWidthPx: CGFloat) {
|
|
217
|
+
let snapped = getValueForPosition(
|
|
218
|
+
positionInView: positionX,
|
|
219
|
+
containerWidth: trackWidthPx,
|
|
220
|
+
thumbWidth: THUMB_SIZE,
|
|
221
|
+
minV: minV, maxV: maxV, step: step
|
|
222
|
+
)
|
|
223
|
+
if draggingLow {
|
|
224
|
+
let newLow = clampF(snapped, minV, highValue - minRange)
|
|
225
|
+
if newLow != lowValue {
|
|
226
|
+
lowValue = newLow
|
|
227
|
+
onValueChanged(newLow, highValue, true)
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
let newHigh = clampF(snapped, lowValue + minRange, maxV)
|
|
231
|
+
if newHigh != highValue {
|
|
232
|
+
highValue = newHigh
|
|
233
|
+
onValueChanged(lowValue, newHigh, true)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import SwiftUI
|
|
2
|
+
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Size variant
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
|
|
7
|
+
public enum StepperSize {
|
|
8
|
+
case large
|
|
9
|
+
case small
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private struct StepperSizeSpec {
|
|
13
|
+
let buttonSize: CGFloat
|
|
14
|
+
let iconSize: CGFloat
|
|
15
|
+
let numberMinWidth: CGFloat
|
|
16
|
+
let numberHeight: CGFloat
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private func stepperSizeSpec(_ size: StepperSize) -> StepperSizeSpec {
|
|
20
|
+
switch size {
|
|
21
|
+
case .large:
|
|
22
|
+
return StepperSizeSpec(buttonSize: 36, iconSize: 22, numberMinWidth: 32, numberHeight: 28)
|
|
23
|
+
case .small:
|
|
24
|
+
return StepperSizeSpec(buttonSize: 28, iconSize: 18, numberMinWidth: 28, numberHeight: 24)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Disabled spec — mirrors Compose `disabled: Any` (Bool or per-part array)
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
public struct StepperDisabled {
|
|
33
|
+
let minus: Bool
|
|
34
|
+
let plus: Bool
|
|
35
|
+
|
|
36
|
+
fileprivate var number: Bool { minus && plus }
|
|
37
|
+
|
|
38
|
+
public init(minus: Bool, plus: Bool) {
|
|
39
|
+
self.minus = minus
|
|
40
|
+
self.plus = plus
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// All parts share the same disabled state.
|
|
44
|
+
public init(_ all: Bool) {
|
|
45
|
+
self.minus = all
|
|
46
|
+
self.plus = all
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
extension StepperDisabled: ExpressibleByBooleanLiteral {
|
|
51
|
+
public init(booleanLiteral value: Bool) {
|
|
52
|
+
self.init(value)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Private sub-views
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
private struct StepperButton: View {
|
|
61
|
+
@Environment(\.appTheme) private var theme
|
|
62
|
+
|
|
63
|
+
var sign: String
|
|
64
|
+
var size: StepperSize
|
|
65
|
+
var disabled: Bool
|
|
66
|
+
var onPress: () -> Void
|
|
67
|
+
|
|
68
|
+
var body: some View {
|
|
69
|
+
let specs = stepperSizeSpec(size)
|
|
70
|
+
let iconColor: Color = disabled ? theme.colors.text.disable : theme.colors.primary
|
|
71
|
+
let iconName = sign == "-" ? "24_navigation_minus_circle" : "24_navigation_plus_circle"
|
|
72
|
+
|
|
73
|
+
SwiftUI.Button(action: { if !disabled { onPress() } }) {
|
|
74
|
+
ZStack {
|
|
75
|
+
Circle().fill(theme.colors.background.disable)
|
|
76
|
+
Icon(source: iconName, size: specs.iconSize, color: iconColor)
|
|
77
|
+
}
|
|
78
|
+
.frame(width: specs.buttonSize, height: specs.buttonSize)
|
|
79
|
+
}
|
|
80
|
+
.buttonStyle(.plain)
|
|
81
|
+
.disabled(disabled)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private struct StepperNumberView: View {
|
|
86
|
+
@Environment(\.appTheme) private var theme
|
|
87
|
+
|
|
88
|
+
@Binding var text: String
|
|
89
|
+
var size: StepperSize
|
|
90
|
+
var disabled: Bool
|
|
91
|
+
var editable: Bool
|
|
92
|
+
|
|
93
|
+
var body: some View {
|
|
94
|
+
let specs = stepperSizeSpec(size)
|
|
95
|
+
let textColor: Color = disabled ? theme.colors.text.disable : theme.colors.text.default
|
|
96
|
+
let borderColor: Color = disabled ? theme.colors.border.disable : theme.colors.border.default
|
|
97
|
+
|
|
98
|
+
Group {
|
|
99
|
+
if editable {
|
|
100
|
+
TextField("", text: $text)
|
|
101
|
+
.keyboardType(.numberPad)
|
|
102
|
+
.multilineTextAlignment(.center)
|
|
103
|
+
.disabled(disabled)
|
|
104
|
+
} else {
|
|
105
|
+
MomoText(text, typography: .descriptionDefaultRegular, color: textColor)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
.font(.system(size: scaleSize(12)))
|
|
109
|
+
.ignoreBoldTextSetting()
|
|
110
|
+
.foregroundColor(textColor)
|
|
111
|
+
.frame(minWidth: specs.numberMinWidth)
|
|
112
|
+
.frame(height: specs.numberHeight)
|
|
113
|
+
.padding(.horizontal, Spacing.XS)
|
|
114
|
+
.overlay(
|
|
115
|
+
RoundedRectangle(cornerRadius: Radius.S)
|
|
116
|
+
.stroke(borderColor, lineWidth: 1)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Public API — MomoStepper
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/// MomoStepper — quantity +/- control. Layout: [- button] [value] [+ button].
|
|
126
|
+
///
|
|
127
|
+
/// Named `MomoStepper` to avoid shadowing SwiftUI's built-in `Stepper`.
|
|
128
|
+
///
|
|
129
|
+
/// - Parameters:
|
|
130
|
+
/// - value: Binding to the current value. Clamped to `min...max`.
|
|
131
|
+
/// - min: Minimum allowed value (default 0). Minus auto-disables at this bound.
|
|
132
|
+
/// - max: Maximum allowed value (default 100). Plus auto-disables at this bound.
|
|
133
|
+
/// - step: Amount added/subtracted per press (default 1).
|
|
134
|
+
/// - size: Visual size variant (default `.large`).
|
|
135
|
+
/// - disabled: Disable state — `true` for everything, or `StepperDisabled(minus:plus:)`.
|
|
136
|
+
/// - editable: When `true` the number display becomes a keyboard input (default false).
|
|
137
|
+
/// - onValueChange: Callback invoked with the new value on every change.
|
|
138
|
+
public struct MomoStepper: View {
|
|
139
|
+
@Binding private var value: Int
|
|
140
|
+
private let min: Int
|
|
141
|
+
private let max: Int
|
|
142
|
+
private let step: Int
|
|
143
|
+
private let size: StepperSize
|
|
144
|
+
private let disabled: StepperDisabled
|
|
145
|
+
private let editable: Bool
|
|
146
|
+
private let onValueChange: (Int) -> Void
|
|
147
|
+
|
|
148
|
+
@State private var editingText: String = ""
|
|
149
|
+
|
|
150
|
+
public init(
|
|
151
|
+
value: Binding<Int>,
|
|
152
|
+
min: Int = 0,
|
|
153
|
+
max: Int = 100,
|
|
154
|
+
step: Int = 1,
|
|
155
|
+
size: StepperSize = .large,
|
|
156
|
+
disabled: StepperDisabled = false,
|
|
157
|
+
editable: Bool = false,
|
|
158
|
+
onValueChange: @escaping (Int) -> Void = { _ in }
|
|
159
|
+
) {
|
|
160
|
+
self._value = value
|
|
161
|
+
self.min = min
|
|
162
|
+
self.max = max
|
|
163
|
+
self.step = step
|
|
164
|
+
self.size = size
|
|
165
|
+
self.disabled = disabled
|
|
166
|
+
self.editable = editable
|
|
167
|
+
self.onValueChange = onValueChange
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private func clamp(_ raw: Int) -> Int {
|
|
171
|
+
Swift.max(min, Swift.min(max, raw))
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private func applyValue(_ raw: Int) {
|
|
175
|
+
let parsed = clamp(raw)
|
|
176
|
+
value = parsed
|
|
177
|
+
editingText = String(parsed)
|
|
178
|
+
onValueChange(parsed)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
public var body: some View {
|
|
182
|
+
let effectiveMinusDisabled = disabled.minus || value <= min
|
|
183
|
+
let effectivePlusDisabled = disabled.plus || value >= max
|
|
184
|
+
|
|
185
|
+
HStack(spacing: Spacing.S) {
|
|
186
|
+
StepperButton(
|
|
187
|
+
sign: "-",
|
|
188
|
+
size: size,
|
|
189
|
+
disabled: effectiveMinusDisabled,
|
|
190
|
+
onPress: { applyValue(value - step) }
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
StepperNumberView(
|
|
194
|
+
text: Binding(
|
|
195
|
+
get: { editable ? editingText : String(value) },
|
|
196
|
+
set: { newText in
|
|
197
|
+
editingText = newText
|
|
198
|
+
let parsed = Int(newText.filter { $0.isNumber }) ?? 0
|
|
199
|
+
applyValue(parsed)
|
|
200
|
+
}
|
|
201
|
+
),
|
|
202
|
+
size: size,
|
|
203
|
+
disabled: disabled.number,
|
|
204
|
+
editable: editable
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
StepperButton(
|
|
208
|
+
sign: "+",
|
|
209
|
+
size: size,
|
|
210
|
+
disabled: effectivePlusDisabled,
|
|
211
|
+
onPress: { applyValue(value + step) }
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
.onAppear {
|
|
215
|
+
let clamped = clamp(value)
|
|
216
|
+
if clamped != value { value = clamped }
|
|
217
|
+
editingText = String(clamped)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|