create-du-app 0.1.3 → 0.1.5
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/README.md +15 -9
- package/package.json +6 -5
- package/src/generate.js +15 -2
- package/src/index.js +15 -9
- package/src/prompts.js +1 -1
- package/templates/mobile/expo/.env.example +5 -0
- package/templates/mobile/expo/.eslintrc.js +7 -0
- package/templates/mobile/expo/.prettierrc.js +7 -0
- package/templates/mobile/expo/.svgrrc.js +9 -0
- package/templates/mobile/expo/README.md +70 -7
- package/templates/mobile/expo/_gitignore +20 -0
- package/templates/mobile/expo/_package.json +60 -1
- package/templates/mobile/expo/app.json +26 -0
- package/templates/mobile/expo/babel.config.js +21 -0
- package/templates/mobile/expo/index.js +7 -0
- package/templates/mobile/expo/metro.config.js +31 -0
- package/templates/mobile/expo/src/app/App.tsx +24 -0
- package/templates/mobile/expo/src/app/app-provider.tsx +40 -0
- package/templates/mobile/expo/src/app/config/translation.ts +30 -0
- package/templates/mobile/expo/src/app/index.ts +1 -0
- package/templates/mobile/expo/src/app/navigation/app-route-type.ts +27 -0
- package/templates/mobile/expo/src/app/navigation/bottom-tabs.tsx +34 -0
- package/templates/mobile/expo/src/app/navigation/index.tsx +14 -0
- package/templates/mobile/expo/src/app/stores/auth.store.ts +33 -0
- package/templates/mobile/expo/src/app/stores/common.store.ts +19 -0
- package/templates/mobile/expo/src/app/stores/index.ts +3 -0
- package/templates/mobile/expo/src/app/stores/loading.store.ts +22 -0
- package/templates/mobile/expo/src/assets/Images/empty-list.png +0 -0
- package/templates/mobile/expo/src/assets/Images/index.ts +5 -0
- package/templates/mobile/expo/src/assets/Images/screen-bg-gradian.png +0 -0
- package/templates/mobile/expo/src/assets/i18n/en.json +28 -0
- package/templates/mobile/expo/src/assets/i18n/fr.json +28 -0
- package/templates/mobile/expo/src/assets/lotties/index.ts +3 -0
- package/templates/mobile/expo/src/assets/lotties/loading.json +1 -0
- package/templates/mobile/expo/src/assets/svgs/arrow-left.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/arrow-right.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/calendar.svg +12 -0
- package/templates/mobile/expo/src/assets/svgs/check.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/close.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/eye-hide.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/eye.svg +4 -0
- package/templates/mobile/expo/src/assets/svgs/index.ts +29 -0
- package/templates/mobile/expo/src/assets/svgs/minus.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/plus.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/search.svg +3 -0
- package/templates/mobile/expo/src/assets/svgs/toast-error.svg +5 -0
- package/templates/mobile/expo/src/assets/svgs/toast-success.svg +4 -0
- package/templates/mobile/expo/src/core/api/endpoints.ts +8 -0
- package/templates/mobile/expo/src/core/api/example.api.ts +53 -0
- package/templates/mobile/expo/src/core/api/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/common/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.tsx +30 -0
- package/templates/mobile/expo/src/core/components/common/list-empty/list-empty.type.ts +5 -0
- package/templates/mobile/expo/src/core/components/forms/date-time-picker.modal.tsx +116 -0
- package/templates/mobile/expo/src/core/components/forms/hf-date-time.tsx +293 -0
- package/templates/mobile/expo/src/core/components/forms/hf-password-input.tsx +153 -0
- package/templates/mobile/expo/src/core/components/forms/hf-text-input.tsx +59 -0
- package/templates/mobile/expo/src/core/components/forms/hf-time-picker.tsx +116 -0
- package/templates/mobile/expo/src/core/components/forms/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/index.ts +5 -0
- package/templates/mobile/expo/src/core/components/loading/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/loading/loading-app/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/loading/loading-app/loading-app.tsx +50 -0
- package/templates/mobile/expo/src/core/components/offline/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/offline/offline-banner.tsx +186 -0
- package/templates/mobile/expo/src/core/components/screen/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/screen/screen-container/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/screen/screen-container/screen-container.tsx +248 -0
- package/templates/mobile/expo/src/core/components/splash/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/splash/splash-overlay/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/animated-list-item/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/app-image.tsx +101 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/app-image.type.ts +19 -0
- package/templates/mobile/expo/src/core/components/ui/app-image/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
- package/templates/mobile/expo/src/core/components/ui/asset-placeholder/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
- package/templates/mobile/expo/src/core/components/ui/avatar-image/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/bottom-sheet/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.style.ts +146 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.tsx +97 -0
- package/templates/mobile/expo/src/core/components/ui/button/button.type.ts +47 -0
- package/templates/mobile/expo/src/core/components/ui/button/index.ts +4 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.tsx +127 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
- package/templates/mobile/expo/src/core/components/ui/checkbox/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/collapsible-section/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/components.registry.json +313 -0
- package/templates/mobile/expo/src/core/components/ui/field/field-frame.tsx +62 -0
- package/templates/mobile/expo/src/core/components/ui/field/field.shared.ts +31 -0
- package/templates/mobile/expo/src/core/components/ui/header/header.tsx +196 -0
- package/templates/mobile/expo/src/core/components/ui/header/header.type.ts +30 -0
- package/templates/mobile/expo/src/core/components/ui/header/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.tsx +66 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
- package/templates/mobile/expo/src/core/components/ui/icon-button/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.tsx +107 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
- package/templates/mobile/expo/src/core/components/ui/image-slider/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/index.ts +25 -0
- package/templates/mobile/expo/src/core/components/ui/label/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/label/label.tsx +36 -0
- package/templates/mobile/expo/src/core/components/ui/label/label.type.ts +12 -0
- package/templates/mobile/expo/src/core/components/ui/modal/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/modal/modal.tsx +62 -0
- package/templates/mobile/expo/src/core/components/ui/modal/modal.type.ts +11 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.tsx +129 -0
- package/templates/mobile/expo/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/page-dots/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/page-dots/page-dots.tsx +60 -0
- package/templates/mobile/expo/src/core/components/ui/radio/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/radio/radio.tsx +121 -0
- package/templates/mobile/expo/src/core/components/ui/radio/radio.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/screen/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/screen/screen-gradient.tsx +33 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/search-box.tsx +162 -0
- package/templates/mobile/expo/src/core/components/ui/search-box/search-box.type.ts +26 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
- package/templates/mobile/expo/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.tsx +106 -0
- package/templates/mobile/expo/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
- package/templates/mobile/expo/src/core/components/ui/success-state/index.ts +1 -0
- package/templates/mobile/expo/src/core/components/ui/success-state/success-state.tsx +68 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/tabs.tsx +273 -0
- package/templates/mobile/expo/src/core/components/ui/tabs/tabs.type.ts +21 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.tsx +146 -0
- package/templates/mobile/expo/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/text-area.tsx +90 -0
- package/templates/mobile/expo/src/core/components/ui/text-area/text-area.type.ts +20 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/text-field.tsx +116 -0
- package/templates/mobile/expo/src/core/components/ui/text-field/text-field.type.ts +21 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/index.ts +2 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/toggle.tsx +110 -0
- package/templates/mobile/expo/src/core/components/ui/toggle/toggle.type.ts +19 -0
- package/templates/mobile/expo/src/core/constants/external-urls.constant.ts +5 -0
- package/templates/mobile/expo/src/core/constants/hard-data.constant.ts +0 -0
- package/templates/mobile/expo/src/core/constants/index.ts +2 -0
- package/templates/mobile/expo/src/core/constants/type.constant.ts +3 -0
- package/templates/mobile/expo/src/core/context/index.ts +1 -0
- package/templates/mobile/expo/src/core/context/shared-transition-context.tsx +35 -0
- package/templates/mobile/expo/src/core/hook/index.ts +8 -0
- package/templates/mobile/expo/src/core/hook/useActiveRouteName.ts +63 -0
- package/templates/mobile/expo/src/core/hook/useAppNavigation.tsx +7 -0
- package/templates/mobile/expo/src/core/hook/useBottomInset.tsx +26 -0
- package/templates/mobile/expo/src/core/hook/useDebounce.tsx +16 -0
- package/templates/mobile/expo/src/core/hook/useEndReached.tsx +46 -0
- package/templates/mobile/expo/src/core/hook/useManualRefetch.ts +56 -0
- package/templates/mobile/expo/src/core/hook/useNetworkStatus.ts +68 -0
- package/templates/mobile/expo/src/core/hook/useTimeout.tsx +30 -0
- package/templates/mobile/expo/src/core/index.ts +7 -0
- package/templates/mobile/expo/src/core/services/api.service.ts +230 -0
- package/templates/mobile/expo/src/core/services/device-id.service.ts +37 -0
- package/templates/mobile/expo/src/core/services/index.ts +3 -0
- package/templates/mobile/expo/src/core/services/session-end.bridge.ts +26 -0
- package/templates/mobile/expo/src/core/theme/dark.theme.ts +10 -0
- package/templates/mobile/expo/src/core/theme/index.ts +5 -0
- package/templates/mobile/expo/src/core/theme/light.theme.ts +44 -0
- package/templates/mobile/expo/src/core/theme/theme-context.tsx +82 -0
- package/templates/mobile/expo/src/core/theme/theme.types.ts +26 -0
- package/templates/mobile/expo/src/core/theme/use-themed-styles.ts +25 -0
- package/templates/mobile/expo/src/core/utils/color.util.tsx +198 -0
- package/templates/mobile/expo/src/core/utils/date.util.ts +97 -0
- package/templates/mobile/expo/src/core/utils/device-locale.util.ts +24 -0
- package/templates/mobile/expo/src/core/utils/emitter/index.ts +161 -0
- package/templates/mobile/expo/src/core/utils/emitter/type.ts +40 -0
- package/templates/mobile/expo/src/core/utils/enum.util.tsx +15 -0
- package/templates/mobile/expo/src/core/utils/font.util.tsx +42 -0
- package/templates/mobile/expo/src/core/utils/func.util.ts +48 -0
- package/templates/mobile/expo/src/core/utils/greeting.util.ts +20 -0
- package/templates/mobile/expo/src/core/utils/image-format.util.ts +117 -0
- package/templates/mobile/expo/src/core/utils/image-picker.util.ts +63 -0
- package/templates/mobile/expo/src/core/utils/index.ts +18 -0
- package/templates/mobile/expo/src/core/utils/linking.util.ts +16 -0
- package/templates/mobile/expo/src/core/utils/navigation.util.tsx +100 -0
- package/templates/mobile/expo/src/core/utils/number-format.ts +28 -0
- package/templates/mobile/expo/src/core/utils/query-client.util.ts +35 -0
- package/templates/mobile/expo/src/core/utils/query-persister.util.ts +31 -0
- package/templates/mobile/expo/src/core/utils/schema.util.tsx +2713 -0
- package/templates/mobile/expo/src/core/utils/size.util.tsx +74 -0
- package/templates/mobile/expo/src/core/utils/storage.util.tsx +53 -0
- package/templates/mobile/expo/src/core/utils/toast.util.tsx +151 -0
- package/templates/mobile/expo/src/core/utils/translator.util.tsx +23 -0
- package/templates/mobile/expo/src/core/utils/typography.util.tsx +69 -0
- package/templates/mobile/expo/src/core/utils/validate.util.tsx +13 -0
- package/templates/mobile/expo/src/declarations.d.ts +54 -0
- package/templates/mobile/expo/src/modules/home/home.screen.tsx +110 -0
- package/templates/mobile/expo/src/modules/profile/profile.screen.tsx +29 -0
- package/templates/mobile/expo/src/scripts/link-fonts.js +60 -0
- package/templates/mobile/expo/src/scripts/sync-images.js +56 -0
- package/templates/mobile/expo/src/scripts/sync-svgs.js +50 -0
- package/templates/mobile/expo/src/types/models.d.ts +24 -0
- package/templates/mobile/expo/tsconfig.json +19 -0
- package/templates/mobile/rn/.bundle/config +2 -0
- package/templates/mobile/rn/.env.example +5 -0
- package/templates/mobile/rn/.eslintrc.js +7 -0
- package/templates/mobile/rn/.prettierrc.js +7 -0
- package/templates/mobile/rn/.svgrrc.js +9 -0
- package/templates/mobile/rn/.watchmanconfig +1 -0
- package/templates/mobile/rn/Gemfile +17 -0
- package/templates/mobile/rn/README.md +72 -7
- package/templates/mobile/rn/_gitignore +24 -0
- package/templates/mobile/rn/_package.json +69 -1
- package/templates/mobile/rn/android/app/build.gradle +126 -0
- package/templates/mobile/rn/android/app/debug.keystore +0 -0
- package/templates/mobile/rn/android/app/proguard-rules.pro +10 -0
- package/templates/mobile/rn/android/app/src/main/AndroidManifest.xml +27 -0
- package/templates/mobile/rn/android/app/src/main/java/com/dumobile/MainActivity.kt +22 -0
- package/templates/mobile/rn/android/app/src/main/java/com/dumobile/MainApplication.kt +27 -0
- package/templates/mobile/rn/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- package/templates/mobile/rn/android/app/src/main/res/values/strings.xml +3 -0
- package/templates/mobile/rn/android/app/src/main/res/values/styles.xml +9 -0
- package/templates/mobile/rn/android/build.gradle +21 -0
- package/templates/mobile/rn/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/mobile/rn/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/mobile/rn/android/gradle.properties +44 -0
- package/templates/mobile/rn/android/gradlew +248 -0
- package/templates/mobile/rn/android/gradlew.bat +98 -0
- package/templates/mobile/rn/android/settings.gradle +21 -0
- package/templates/mobile/rn/app.json +4 -0
- package/templates/mobile/rn/babel.config.js +18 -0
- package/templates/mobile/rn/index.js +10 -0
- package/templates/mobile/rn/ios/.xcode.env +11 -0
- package/templates/mobile/rn/ios/DuMobile/AppDelegate.swift +48 -0
- package/templates/mobile/rn/ios/DuMobile/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
- package/templates/mobile/rn/ios/DuMobile/Images.xcassets/Contents.json +6 -0
- package/templates/mobile/rn/ios/DuMobile/Info.plist +59 -0
- package/templates/mobile/rn/ios/DuMobile/LaunchScreen.storyboard +47 -0
- package/templates/mobile/rn/ios/DuMobile/PrivacyInfo.xcprivacy +37 -0
- package/templates/mobile/rn/ios/DuMobile.xcodeproj/project.pbxproj +475 -0
- package/templates/mobile/rn/ios/DuMobile.xcodeproj/xcshareddata/xcschemes/DuMobile.xcscheme +88 -0
- package/templates/mobile/rn/ios/Podfile +34 -0
- package/templates/mobile/rn/metro.config.js +33 -0
- package/templates/mobile/rn/src/app/App.tsx +24 -0
- package/templates/mobile/rn/src/app/app-provider.tsx +41 -0
- package/templates/mobile/rn/src/app/config/translation.ts +29 -0
- package/templates/mobile/rn/src/app/index.ts +1 -0
- package/templates/mobile/rn/src/app/navigation/app-route-type.ts +27 -0
- package/templates/mobile/rn/src/app/navigation/bottom-tabs.tsx +34 -0
- package/templates/mobile/rn/src/app/navigation/index.tsx +14 -0
- package/templates/mobile/rn/src/app/stores/auth.store.ts +33 -0
- package/templates/mobile/rn/src/app/stores/common.store.ts +19 -0
- package/templates/mobile/rn/src/app/stores/index.ts +3 -0
- package/templates/mobile/rn/src/app/stores/loading.store.ts +22 -0
- package/templates/mobile/rn/src/assets/Images/empty-list.png +0 -0
- package/templates/mobile/rn/src/assets/Images/index.ts +5 -0
- package/templates/mobile/rn/src/assets/Images/screen-bg-gradian.png +0 -0
- package/templates/mobile/rn/src/assets/i18n/en.json +22 -0
- package/templates/mobile/rn/src/assets/i18n/fr.json +22 -0
- package/templates/mobile/rn/src/assets/lotties/index.ts +3 -0
- package/templates/mobile/rn/src/assets/lotties/loading.json +1 -0
- package/templates/mobile/rn/src/assets/svgs/arrow-left.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/arrow-right.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/calendar.svg +12 -0
- package/templates/mobile/rn/src/assets/svgs/check.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/close.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/eye-hide.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/eye.svg +4 -0
- package/templates/mobile/rn/src/assets/svgs/index.ts +29 -0
- package/templates/mobile/rn/src/assets/svgs/minus.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/plus.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/search.svg +3 -0
- package/templates/mobile/rn/src/assets/svgs/toast-error.svg +5 -0
- package/templates/mobile/rn/src/assets/svgs/toast-success.svg +4 -0
- package/templates/mobile/rn/src/core/api/endpoints.ts +8 -0
- package/templates/mobile/rn/src/core/api/example.api.ts +53 -0
- package/templates/mobile/rn/src/core/api/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/common/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.tsx +30 -0
- package/templates/mobile/rn/src/core/components/common/list-empty/list-empty.type.ts +5 -0
- package/templates/mobile/rn/src/core/components/forms/hf-date-time.tsx +301 -0
- package/templates/mobile/rn/src/core/components/forms/hf-password-input.tsx +153 -0
- package/templates/mobile/rn/src/core/components/forms/hf-text-input.tsx +59 -0
- package/templates/mobile/rn/src/core/components/forms/hf-time-picker.tsx +117 -0
- package/templates/mobile/rn/src/core/components/forms/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/index.ts +5 -0
- package/templates/mobile/rn/src/core/components/loading/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/loading/loading-app/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/loading/loading-app/loading-app.tsx +50 -0
- package/templates/mobile/rn/src/core/components/offline/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/offline/offline-banner.tsx +186 -0
- package/templates/mobile/rn/src/core/components/screen/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/screen/screen-container/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/screen/screen-container/screen-container.tsx +252 -0
- package/templates/mobile/rn/src/core/components/splash/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/splash/splash-overlay/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/splash/splash-overlay/splash-overlay.tsx +93 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.tsx +48 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/animated-list-item.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/animated-list-item/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/app-image.tsx +104 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/app-image.type.ts +19 -0
- package/templates/mobile/rn/src/core/components/ui/app-image/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/asset-placeholder/asset-placeholder.tsx +76 -0
- package/templates/mobile/rn/src/core/components/ui/asset-placeholder/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/avatar-image/avatar-image.tsx +90 -0
- package/templates/mobile/rn/src/core/components/ui/avatar-image/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.tsx +145 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/bottom-sheet.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/bottom-sheet/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.style.ts +146 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.tsx +97 -0
- package/templates/mobile/rn/src/core/components/ui/button/button.type.ts +47 -0
- package/templates/mobile/rn/src/core/components/ui/button/index.ts +4 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.tsx +127 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/checkbox.type.ts +24 -0
- package/templates/mobile/rn/src/core/components/ui/checkbox/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.tsx +141 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/collapsible-section.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/collapsible-section/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/components.registry.json +313 -0
- package/templates/mobile/rn/src/core/components/ui/field/field-frame.tsx +62 -0
- package/templates/mobile/rn/src/core/components/ui/field/field.shared.ts +31 -0
- package/templates/mobile/rn/src/core/components/ui/header/header.tsx +196 -0
- package/templates/mobile/rn/src/core/components/ui/header/header.type.ts +30 -0
- package/templates/mobile/rn/src/core/components/ui/header/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.style.ts +23 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.tsx +66 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/icon-button.type.ts +25 -0
- package/templates/mobile/rn/src/core/components/ui/icon-button/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.tsx +107 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/image-slider.type.ts +10 -0
- package/templates/mobile/rn/src/core/components/ui/image-slider/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/index.ts +25 -0
- package/templates/mobile/rn/src/core/components/ui/label/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/label/label.tsx +36 -0
- package/templates/mobile/rn/src/core/components/ui/label/label.type.ts +12 -0
- package/templates/mobile/rn/src/core/components/ui/modal/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/modal/modal.tsx +62 -0
- package/templates/mobile/rn/src/core/components/ui/modal/modal.type.ts +11 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.tsx +129 -0
- package/templates/mobile/rn/src/core/components/ui/otp-input/otp-input.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/page-dots/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/page-dots/page-dots.tsx +60 -0
- package/templates/mobile/rn/src/core/components/ui/radio/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/radio/radio.tsx +121 -0
- package/templates/mobile/rn/src/core/components/ui/radio/radio.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/screen/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/screen/screen-gradient.tsx +33 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/search-box.tsx +162 -0
- package/templates/mobile/rn/src/core/components/ui/search-box/search-box.type.ts +26 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.tsx +86 -0
- package/templates/mobile/rn/src/core/components/ui/segmented-control/segmented-control.type.ts +22 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.tsx +106 -0
- package/templates/mobile/rn/src/core/components/ui/skeleton/skeleton.type.ts +8 -0
- package/templates/mobile/rn/src/core/components/ui/success-state/index.ts +1 -0
- package/templates/mobile/rn/src/core/components/ui/success-state/success-state.tsx +68 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/tabs.tsx +273 -0
- package/templates/mobile/rn/src/core/components/ui/tabs/tabs.type.ts +21 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.tsx +146 -0
- package/templates/mobile/rn/src/core/components/ui/tag-input/tag-input.type.ts +22 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/text-area.tsx +90 -0
- package/templates/mobile/rn/src/core/components/ui/text-area/text-area.type.ts +20 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/text-field.tsx +116 -0
- package/templates/mobile/rn/src/core/components/ui/text-field/text-field.type.ts +21 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/index.ts +2 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/toggle.tsx +110 -0
- package/templates/mobile/rn/src/core/components/ui/toggle/toggle.type.ts +19 -0
- package/templates/mobile/rn/src/core/constants/external-urls.constant.ts +5 -0
- package/templates/mobile/rn/src/core/constants/hard-data.constant.ts +0 -0
- package/templates/mobile/rn/src/core/constants/index.ts +2 -0
- package/templates/mobile/rn/src/core/constants/type.constant.ts +3 -0
- package/templates/mobile/rn/src/core/context/index.ts +1 -0
- package/templates/mobile/rn/src/core/context/shared-transition-context.tsx +35 -0
- package/templates/mobile/rn/src/core/hook/index.ts +8 -0
- package/templates/mobile/rn/src/core/hook/useActiveRouteName.ts +63 -0
- package/templates/mobile/rn/src/core/hook/useAppNavigation.tsx +7 -0
- package/templates/mobile/rn/src/core/hook/useBottomInset.tsx +26 -0
- package/templates/mobile/rn/src/core/hook/useDebounce.tsx +16 -0
- package/templates/mobile/rn/src/core/hook/useEndReached.tsx +46 -0
- package/templates/mobile/rn/src/core/hook/useManualRefetch.ts +56 -0
- package/templates/mobile/rn/src/core/hook/useNetworkStatus.ts +68 -0
- package/templates/mobile/rn/src/core/hook/useTimeout.tsx +30 -0
- package/templates/mobile/rn/src/core/index.ts +7 -0
- package/templates/mobile/rn/src/core/services/api.service.ts +230 -0
- package/templates/mobile/rn/src/core/services/device-id.service.ts +23 -0
- package/templates/mobile/rn/src/core/services/index.ts +3 -0
- package/templates/mobile/rn/src/core/services/session-end.bridge.ts +26 -0
- package/templates/mobile/rn/src/core/theme/dark.theme.ts +10 -0
- package/templates/mobile/rn/src/core/theme/index.ts +5 -0
- package/templates/mobile/rn/src/core/theme/light.theme.ts +44 -0
- package/templates/mobile/rn/src/core/theme/theme-context.tsx +82 -0
- package/templates/mobile/rn/src/core/theme/theme.types.ts +26 -0
- package/templates/mobile/rn/src/core/theme/use-themed-styles.ts +25 -0
- package/templates/mobile/rn/src/core/utils/color.util.tsx +198 -0
- package/templates/mobile/rn/src/core/utils/date.util.ts +97 -0
- package/templates/mobile/rn/src/core/utils/device-locale.util.ts +22 -0
- package/templates/mobile/rn/src/core/utils/emitter/index.ts +161 -0
- package/templates/mobile/rn/src/core/utils/emitter/type.ts +40 -0
- package/templates/mobile/rn/src/core/utils/enum.util.tsx +15 -0
- package/templates/mobile/rn/src/core/utils/font.util.tsx +42 -0
- package/templates/mobile/rn/src/core/utils/func.util.ts +48 -0
- package/templates/mobile/rn/src/core/utils/greeting.util.ts +20 -0
- package/templates/mobile/rn/src/core/utils/image-format.util.ts +117 -0
- package/templates/mobile/rn/src/core/utils/image-picker.util.ts +84 -0
- package/templates/mobile/rn/src/core/utils/index.ts +18 -0
- package/templates/mobile/rn/src/core/utils/linking.util.ts +16 -0
- package/templates/mobile/rn/src/core/utils/navigation.util.tsx +100 -0
- package/templates/mobile/rn/src/core/utils/number-format.ts +28 -0
- package/templates/mobile/rn/src/core/utils/query-client.util.ts +35 -0
- package/templates/mobile/rn/src/core/utils/query-persister.util.ts +36 -0
- package/templates/mobile/rn/src/core/utils/schema.util.tsx +2713 -0
- package/templates/mobile/rn/src/core/utils/size.util.tsx +74 -0
- package/templates/mobile/rn/src/core/utils/storage.util.tsx +53 -0
- package/templates/mobile/rn/src/core/utils/toast.util.tsx +151 -0
- package/templates/mobile/rn/src/core/utils/translator.util.tsx +23 -0
- package/templates/mobile/rn/src/core/utils/typography.util.tsx +69 -0
- package/templates/mobile/rn/src/core/utils/validate.util.tsx +13 -0
- package/templates/mobile/rn/src/declarations.d.ts +54 -0
- package/templates/mobile/rn/src/modules/home/home.screen.tsx +67 -0
- package/templates/mobile/rn/src/modules/profile/profile.screen.tsx +29 -0
- package/templates/mobile/rn/src/scripts/link-fonts.js +60 -0
- package/templates/mobile/rn/src/scripts/sync-images.js +56 -0
- package/templates/mobile/rn/src/scripts/sync-svgs.js +50 -0
- package/templates/mobile/rn/src/types/models.d.ts +24 -0
- package/templates/mobile/rn/tsconfig.json +21 -0
- package/templates/shared/src/api-endpoints.ts +8 -0
- package/templates/shared/src/enums.ts +34 -0
- package/templates/shared/src/external-urls.ts +5 -0
- package/templates/shared/src/index.ts +6 -3
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Font, fontSize, horizontalScale, Radius, Spacing } from '@src/core/utils';
|
|
2
|
+
import { ThemeColors, useTheme, useThemedStyles } from '@src/core/theme';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { StyleSheet, TextInput, TouchableOpacity, View } from 'react-native';
|
|
5
|
+
import FieldFrame from '../field/field-frame';
|
|
6
|
+
import { FIELD_BORDER_WIDTH, resolveFieldBorderColor } from '../field/field.shared';
|
|
7
|
+
import { TextFieldProps } from './text-field.type';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Design-system text input (Life-Master-Design "Input field").
|
|
11
|
+
*
|
|
12
|
+
* Label + input box + optional trailing icon + helper/error message.
|
|
13
|
+
* States: default / focus / filled / error / disabled (hover = native).
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <TextField label="Email" placeholder="you@mail.com" value={v} onChangeText={setV} />
|
|
17
|
+
* <TextField label="Code" error message="Invalid code" IconTrailing={IconAdd} />
|
|
18
|
+
*/
|
|
19
|
+
const TextField = (props: TextFieldProps) => {
|
|
20
|
+
const {
|
|
21
|
+
label,
|
|
22
|
+
message,
|
|
23
|
+
error = false,
|
|
24
|
+
disabled = false,
|
|
25
|
+
IconTrailing,
|
|
26
|
+
onTrailingPress,
|
|
27
|
+
containerStyle,
|
|
28
|
+
inputStyle,
|
|
29
|
+
onFocus,
|
|
30
|
+
onBlur,
|
|
31
|
+
...inputProps
|
|
32
|
+
} = props;
|
|
33
|
+
|
|
34
|
+
const { colors } = useTheme();
|
|
35
|
+
const styles = useThemedStyles(makeStyles);
|
|
36
|
+
|
|
37
|
+
const [focused, setFocused] = useState(false);
|
|
38
|
+
const borderColor = resolveFieldBorderColor(colors, { focused, error, disabled });
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<FieldFrame label={label} message={message} error={error} disabled={disabled} style={containerStyle}>
|
|
42
|
+
<View
|
|
43
|
+
style={[
|
|
44
|
+
styles.box,
|
|
45
|
+
{ borderColor, backgroundColor: disabled ? colors.bg_disable : colors.bg_input },
|
|
46
|
+
]}
|
|
47
|
+
>
|
|
48
|
+
<TextInput
|
|
49
|
+
{...inputProps}
|
|
50
|
+
editable={!disabled}
|
|
51
|
+
placeholderTextColor={colors.fg_neutral_faded}
|
|
52
|
+
onFocus={(e) => {
|
|
53
|
+
setFocused(true);
|
|
54
|
+
onFocus?.(e);
|
|
55
|
+
}}
|
|
56
|
+
onBlur={(e) => {
|
|
57
|
+
setFocused(false);
|
|
58
|
+
onBlur?.(e);
|
|
59
|
+
}}
|
|
60
|
+
style={[styles.input, disabled && styles.inputDisabled, inputStyle]}
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
{IconTrailing ? (
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
disabled={disabled || !onTrailingPress}
|
|
66
|
+
onPress={onTrailingPress}
|
|
67
|
+
style={styles.trailing}
|
|
68
|
+
>
|
|
69
|
+
<IconTrailing
|
|
70
|
+
width={horizontalScale(16)}
|
|
71
|
+
height={horizontalScale(16)}
|
|
72
|
+
color={disabled ? colors.fg_neutral_faded : colors.fg_neutral_normal}
|
|
73
|
+
/>
|
|
74
|
+
</TouchableOpacity>
|
|
75
|
+
) : null}
|
|
76
|
+
</View>
|
|
77
|
+
</FieldFrame>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export default TextField;
|
|
82
|
+
|
|
83
|
+
const makeStyles = (c: ThemeColors) =>
|
|
84
|
+
StyleSheet.create({
|
|
85
|
+
box: {
|
|
86
|
+
flexDirection: 'row',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
alignSelf: 'stretch',
|
|
89
|
+
borderRadius: Radius.radius_lg, // 12
|
|
90
|
+
borderWidth: FIELD_BORDER_WIDTH, // 1
|
|
91
|
+
overflow: 'hidden',
|
|
92
|
+
},
|
|
93
|
+
input: {
|
|
94
|
+
flex: 1,
|
|
95
|
+
paddingHorizontal: Spacing.spacing_md, // 12
|
|
96
|
+
paddingVertical: Spacing.spacing_m_nudge, // 10
|
|
97
|
+
fontFamily: Font.dmSansRegular,
|
|
98
|
+
fontSize: fontSize(16),
|
|
99
|
+
color: c.fg_neutral_normal,
|
|
100
|
+
// No fixed lineHeight on a single-line input — it offsets the glyph
|
|
101
|
+
// vertically on iOS. Let the line box center the text naturally.
|
|
102
|
+
includeFontPadding: false, // Android: drop extra top/bottom font padding
|
|
103
|
+
textAlignVertical: 'center', // Android vertical centering
|
|
104
|
+
},
|
|
105
|
+
inputDisabled: {
|
|
106
|
+
color: c.fg_neutral_faded,
|
|
107
|
+
},
|
|
108
|
+
trailing: {
|
|
109
|
+
alignSelf: 'stretch',
|
|
110
|
+
flexDirection: 'row',
|
|
111
|
+
alignItems: 'center',
|
|
112
|
+
paddingHorizontal: Spacing.spacing_md, // 12
|
|
113
|
+
borderLeftWidth: FIELD_BORDER_WIDTH,
|
|
114
|
+
borderLeftColor: c.bd_neutral_faded,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { StyleProp, TextInputProps, TextStyle, ViewStyle } from 'react-native';
|
|
2
|
+
import { SvgProps } from 'react-native-svg';
|
|
3
|
+
|
|
4
|
+
export type TextFieldProps = Omit<TextInputProps, 'style' | 'editable'> & {
|
|
5
|
+
/** Top label. */
|
|
6
|
+
label?: string;
|
|
7
|
+
/** Bottom helper / error message. */
|
|
8
|
+
message?: string;
|
|
9
|
+
/** Error (validation) visual + red message. */
|
|
10
|
+
error?: boolean;
|
|
11
|
+
/** Disable input. */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Trailing icon (SVG), shown after a divider. */
|
|
14
|
+
IconTrailing?: React.FC<SvgProps>;
|
|
15
|
+
/** Press handler for the trailing icon. */
|
|
16
|
+
onTrailingPress?: () => void;
|
|
17
|
+
/** Override the outer container style. */
|
|
18
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
19
|
+
/** Override the text input style. */
|
|
20
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ThemeColors, useTheme, useThemedStyles } from '@src/core/theme';
|
|
2
|
+
import { horizontalScale, Stroke } from '@src/core/utils';
|
|
3
|
+
import React, { useEffect, useRef } from 'react';
|
|
4
|
+
import { Animated, StyleSheet, TouchableWithoutFeedback } from 'react-native';
|
|
5
|
+
import { ToggleProps, ToggleSize } from './toggle.type';
|
|
6
|
+
|
|
7
|
+
/** Track + thumb dimensions per size (from Figma). */
|
|
8
|
+
const SIZE_TOKENS: Record<
|
|
9
|
+
ToggleSize,
|
|
10
|
+
{ width: number; height: number; thumb: number; padding: number }
|
|
11
|
+
> = {
|
|
12
|
+
sm: { width: horizontalScale(48), height: horizontalScale(24), thumb: horizontalScale(18), padding: horizontalScale(3) },
|
|
13
|
+
lg: { width: horizontalScale(60), height: horizontalScale(32), thumb: horizontalScale(26), padding: horizontalScale(3) },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Design-system Toggle / Switch (Life-Master-Design).
|
|
18
|
+
*
|
|
19
|
+
* Sizes: `sm` (48×24) | `lg` (60×32). States: on / off / disabled.
|
|
20
|
+
* Animated thumb. Controlled via `value` / `onValueChange`.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <Toggle value={enabled} onValueChange={setEnabled} />
|
|
24
|
+
* <Toggle value={enabled} onValueChange={setEnabled} size="lg" disabled />
|
|
25
|
+
*/
|
|
26
|
+
const Toggle = (props: ToggleProps) => {
|
|
27
|
+
const { value = false, onValueChange, size = 'sm', disabled = false, style, accessibilityLabel } = props;
|
|
28
|
+
const styles = useThemedStyles(makeStyles);
|
|
29
|
+
const { colors } = useTheme();
|
|
30
|
+
const tokens = SIZE_TOKENS[size];
|
|
31
|
+
const travel = tokens.width - tokens.thumb - tokens.padding * 2;
|
|
32
|
+
|
|
33
|
+
const anim = useRef(new Animated.Value(value ? 1 : 0)).current;
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
Animated.timing(anim, {
|
|
37
|
+
toValue: value ? 1 : 0,
|
|
38
|
+
duration: 160,
|
|
39
|
+
useNativeDriver: false,
|
|
40
|
+
}).start();
|
|
41
|
+
}, [value, anim]);
|
|
42
|
+
|
|
43
|
+
const trackColor = anim.interpolate({
|
|
44
|
+
inputRange: [0, 1],
|
|
45
|
+
outputRange: [colors.bg_elevation_level_2_normal, colors.bg_primary_normal], // #292929 -> #6927da
|
|
46
|
+
});
|
|
47
|
+
const translateX = anim.interpolate({ inputRange: [0, 1], outputRange: [0, travel] });
|
|
48
|
+
|
|
49
|
+
const thumbColor = value ? colors.fg_primary_on_background : colors.fg_neutral_normal;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<TouchableWithoutFeedback
|
|
53
|
+
accessibilityRole="switch"
|
|
54
|
+
accessibilityState={{ checked: value, disabled }}
|
|
55
|
+
accessibilityLabel={accessibilityLabel}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
onPress={() => onValueChange?.(!value)}
|
|
58
|
+
>
|
|
59
|
+
<Animated.View
|
|
60
|
+
style={[
|
|
61
|
+
styles.track,
|
|
62
|
+
{
|
|
63
|
+
width: tokens.width,
|
|
64
|
+
height: tokens.height,
|
|
65
|
+
borderRadius: tokens.height / 2,
|
|
66
|
+
padding: tokens.padding,
|
|
67
|
+
backgroundColor: disabled ? colors.bg_elevation_level_2_normal : trackColor,
|
|
68
|
+
borderWidth: value && !disabled ? 0 : Stroke.stroke_xs,
|
|
69
|
+
borderColor: colors.bd_neutral_faded,
|
|
70
|
+
},
|
|
71
|
+
disabled && styles.disabled,
|
|
72
|
+
style,
|
|
73
|
+
]}
|
|
74
|
+
>
|
|
75
|
+
<Animated.View
|
|
76
|
+
style={[
|
|
77
|
+
styles.thumb,
|
|
78
|
+
{
|
|
79
|
+
width: tokens.thumb,
|
|
80
|
+
height: tokens.thumb,
|
|
81
|
+
borderRadius: tokens.thumb / 2,
|
|
82
|
+
backgroundColor: disabled ? colors.fg_neutral_faded : thumbColor,
|
|
83
|
+
transform: [{ translateX }],
|
|
84
|
+
},
|
|
85
|
+
]}
|
|
86
|
+
/>
|
|
87
|
+
</Animated.View>
|
|
88
|
+
</TouchableWithoutFeedback>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default Toggle;
|
|
93
|
+
|
|
94
|
+
const makeStyles = (c: ThemeColors) =>
|
|
95
|
+
StyleSheet.create({
|
|
96
|
+
track: {
|
|
97
|
+
justifyContent: 'center',
|
|
98
|
+
},
|
|
99
|
+
thumb: {
|
|
100
|
+
// skeuomorphic drop shadow (shadow-xs)
|
|
101
|
+
shadowColor: c.shadow_skeumorphic_inner,
|
|
102
|
+
shadowOffset: { width: 0, height: 1 },
|
|
103
|
+
shadowOpacity: 1,
|
|
104
|
+
shadowRadius: 2,
|
|
105
|
+
elevation: 1,
|
|
106
|
+
},
|
|
107
|
+
disabled: {
|
|
108
|
+
opacity: 0.6,
|
|
109
|
+
},
|
|
110
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
/** Size token — `sm` = 48×24, `lg` = 60×32. */
|
|
4
|
+
export type ToggleSize = 'sm' | 'lg';
|
|
5
|
+
|
|
6
|
+
export interface ToggleProps {
|
|
7
|
+
/** Controlled on/off value. */
|
|
8
|
+
value?: boolean;
|
|
9
|
+
/** Fired with the next value when pressed. */
|
|
10
|
+
onValueChange?: (value: boolean) => void;
|
|
11
|
+
/** Size token. @default 'sm' */
|
|
12
|
+
size?: ToggleSize;
|
|
13
|
+
/** Disable interaction + faded visual. */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** Override container style. */
|
|
16
|
+
style?: StyleProp<ViewStyle>;
|
|
17
|
+
/** Accessibility label. */
|
|
18
|
+
accessibilityLabel?: string;
|
|
19
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './shared-transition-context';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
interface CardLayout {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
imageUri: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SharedTransitionContextType {
|
|
12
|
+
cardLayout: CardLayout | null;
|
|
13
|
+
setCardLayout: (layout: CardLayout | null) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const SharedTransitionContext = createContext<SharedTransitionContextType>({
|
|
17
|
+
cardLayout: null,
|
|
18
|
+
setCardLayout: () => {},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const SharedTransitionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
22
|
+
const [cardLayout, setCardLayout] = useState<CardLayout | null>(null);
|
|
23
|
+
|
|
24
|
+
const handleSetCardLayout = useCallback((layout: CardLayout | null) => {
|
|
25
|
+
setCardLayout(layout);
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<SharedTransitionContext.Provider value={{ cardLayout, setCardLayout: handleSetCardLayout }}>
|
|
30
|
+
{children}
|
|
31
|
+
</SharedTransitionContext.Provider>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const useSharedTransition = () => useContext(SharedTransitionContext);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './useAppNavigation';
|
|
2
|
+
export * from './useDebounce';
|
|
3
|
+
export * from './useTimeout';
|
|
4
|
+
export * from './useBottomInset';
|
|
5
|
+
export * from './useNetworkStatus';
|
|
6
|
+
export * from './useActiveRouteName';
|
|
7
|
+
export * from './useEndReached';
|
|
8
|
+
export * from './useManualRefetch';
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import type { NavigationState, PartialState } from '@react-navigation/native';
|
|
3
|
+
import { navigationRef } from '@src/core/utils';
|
|
4
|
+
|
|
5
|
+
type AnyState = NavigationState | PartialState<NavigationState> | undefined;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Walks a navigation state to the deepest focused route and returns its name.
|
|
9
|
+
* More reliable across nested tab + stack navigators than reading only the
|
|
10
|
+
* top-level route, and resilient to `getCurrentRoute()` returning an
|
|
11
|
+
* intermediate (tab) route rather than the leaf screen.
|
|
12
|
+
*/
|
|
13
|
+
const focusedRouteName = (state: AnyState): string | undefined => {
|
|
14
|
+
if (!state || !state.routes.length) return undefined;
|
|
15
|
+
const index = state.index ?? state.routes.length - 1;
|
|
16
|
+
const route = state.routes[index];
|
|
17
|
+
// Recurse into nested navigators until we reach a leaf route.
|
|
18
|
+
return focusedRouteName(route.state as AnyState) ?? route.name;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The name of the currently focused leaf route, tracked reactively off the
|
|
23
|
+
* shared {@link navigationRef}. Returns `undefined` until the navigation
|
|
24
|
+
* container is ready. Lets components outside the navigator (e.g. the global
|
|
25
|
+
* offline banner) react to where the user is without prop-drilling.
|
|
26
|
+
*/
|
|
27
|
+
export const useActiveRouteName = (): string | undefined => {
|
|
28
|
+
const [routeName, setRouteName] = useState<string | undefined>(undefined);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
let unsubscribe: (() => void) | undefined;
|
|
32
|
+
let retry: ReturnType<typeof setInterval> | undefined;
|
|
33
|
+
|
|
34
|
+
const attach = (): boolean => {
|
|
35
|
+
const ref = navigationRef.current;
|
|
36
|
+
if (!ref || !ref.isReady()) return false;
|
|
37
|
+
|
|
38
|
+
const sync = () =>
|
|
39
|
+
setRouteName(focusedRouteName(ref.getRootState() as AnyState));
|
|
40
|
+
|
|
41
|
+
// Seed once the container is ready, then keep in sync on every
|
|
42
|
+
// navigation state change (including bottom-tab switches).
|
|
43
|
+
sync();
|
|
44
|
+
unsubscribe = ref.addListener('state', sync);
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// The banner mounts alongside the navigator, so the container ref may
|
|
49
|
+
// not be ready on the first effect run — poll briefly until it is.
|
|
50
|
+
if (!attach()) {
|
|
51
|
+
retry = setInterval(() => {
|
|
52
|
+
if (attach() && retry) clearInterval(retry);
|
|
53
|
+
}, 100);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
if (retry) clearInterval(retry);
|
|
58
|
+
unsubscribe?.();
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return routeName;
|
|
63
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { NavigationProp } from '@react-navigation/native';
|
|
2
|
+
import { useNavigation } from '@react-navigation/native';
|
|
3
|
+
import { NavigatorParamList } from '@src/app/navigation/app-route-type';
|
|
4
|
+
|
|
5
|
+
export const useAppNavigation = () => {
|
|
6
|
+
return useNavigation<NavigationProp<NavigatorParamList>>();
|
|
7
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
2
|
+
import { isIOS } from '../utils'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Raw OS safe-area bottom inset (with a +12 nudge on Android, which lacks a
|
|
6
|
+
* home-indicator gap). The floating tab bar uses this for its own bottom
|
|
7
|
+
* padding; screens should prefer {@link useScreenBottomInset} so their content
|
|
8
|
+
* also clears the bar.
|
|
9
|
+
*/
|
|
10
|
+
export const useBottomInset = (): number => {
|
|
11
|
+
const insets = useSafeAreaInsets()
|
|
12
|
+
return isIOS ? insets.bottom : insets.bottom + 12
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Height of the floating tab pill (icon + label + padding) above the safe-area
|
|
16
|
+
// inset — the space scroll content must reserve to clear the bar.
|
|
17
|
+
const TAB_BAR_CLEARANCE = 96
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Bottom padding a scrollable screen needs so its last rows clear the floating
|
|
21
|
+
* tab bar. Use directly as the list's `contentContainerStyle.paddingBottom`.
|
|
22
|
+
*/
|
|
23
|
+
export const useScreenBottomInset = (): number => {
|
|
24
|
+
const bottom = useBottomInset()
|
|
25
|
+
return bottom + TAB_BAR_CLEARANCE
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DependencyList, useEffect } from 'react';
|
|
2
|
+
import useTimeout from './useTimeout';
|
|
3
|
+
|
|
4
|
+
export default function useDebounce(
|
|
5
|
+
callback: () => void,
|
|
6
|
+
delay: number,
|
|
7
|
+
dependencies: DependencyList
|
|
8
|
+
) {
|
|
9
|
+
const { reset, clear } = useTimeout(callback, delay);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
reset();
|
|
13
|
+
return clear;
|
|
14
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
15
|
+
}, dependencies);
|
|
16
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Guards a FlatList's `onEndReached` so it only fires AFTER the user has actually
|
|
5
|
+
* scrolled — not on mount.
|
|
6
|
+
*
|
|
7
|
+
* A short list whose content doesn't fill the viewport makes FlatList fire
|
|
8
|
+
* `onEndReached` immediately on layout, which would auto-load page 2 the moment
|
|
9
|
+
* the screen opens. Gating on the first real scroll defers load-more until
|
|
10
|
+
* there's genuine scroll intent.
|
|
11
|
+
*
|
|
12
|
+
* The flag is armed by `onScrollBeginDrag` (the user touches and drags the list)
|
|
13
|
+
* as well as `onMomentumScrollBegin`. Arming on the drag is what makes load-more
|
|
14
|
+
* reliable: a SLOW scroll to the bottom produces no momentum phase, so gating on
|
|
15
|
+
* momentum alone (LIFEMASTER-396) swallowed `onEndReached` — the user had to
|
|
16
|
+
* scroll up and flick back down to trigger the next page. The drag fires on the
|
|
17
|
+
* very first touch-move, momentum or not.
|
|
18
|
+
*
|
|
19
|
+
* Spread the returned props onto the FlatList:
|
|
20
|
+
* ```tsx
|
|
21
|
+
* const endReached = useEndReached(loadMore);
|
|
22
|
+
* <FlatList onEndReached={endReached.onEndReached}
|
|
23
|
+
* onScrollBeginDrag={endReached.onScrollBeginDrag}
|
|
24
|
+
* onMomentumScrollBegin={endReached.onMomentumScrollBegin}
|
|
25
|
+
* onEndReachedThreshold={0.4} />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const useEndReached = (loadMore: () => void) => {
|
|
29
|
+
// Becomes true on the first real scroll (drag or momentum), so the initial
|
|
30
|
+
// layout's onEndReached (fired before any scroll) is ignored.
|
|
31
|
+
const hasScrolled = useRef(false);
|
|
32
|
+
|
|
33
|
+
const arm = useCallback(() => {
|
|
34
|
+
hasScrolled.current = true;
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const onEndReached = useCallback(() => {
|
|
38
|
+
if (hasScrolled.current) loadMore();
|
|
39
|
+
}, [loadMore]);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
onEndReached,
|
|
43
|
+
onScrollBeginDrag: arm,
|
|
44
|
+
onMomentumScrollBegin: arm,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseManualRefetchResult {
|
|
4
|
+
/**
|
|
5
|
+
* True only while a *user-initiated* refetch (pull-to-refresh / retry) is in
|
|
6
|
+
* flight. Automatic background refetches (focus, reconnect, stale refresh)
|
|
7
|
+
* never flip this, so they fetch silently without driving the spinner.
|
|
8
|
+
*/
|
|
9
|
+
isRefreshing: boolean;
|
|
10
|
+
/** Run all the wrapped refetches together and gate the spinner on them. */
|
|
11
|
+
refetch: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Gates a pull-to-refresh spinner on *manual* refetches only.
|
|
16
|
+
*
|
|
17
|
+
* React Query's `isFetching` is true for every background refetch — screen
|
|
18
|
+
* focus, reconnect, stale revalidation — so wiring `RefreshControl.refreshing`
|
|
19
|
+
* to it makes the spinner flash on auto-refetches the user never asked for.
|
|
20
|
+
* This hook instead exposes a `refetch` that flips an internal flag, so the
|
|
21
|
+
* spinner shows only while that user-driven call is in flight.
|
|
22
|
+
*
|
|
23
|
+
* Pass more than one query's `refetch` (e.g. a detail + its list) and they run
|
|
24
|
+
* together via `Promise.all`; the flag clears once all settle.
|
|
25
|
+
*
|
|
26
|
+
* The flag clear is guarded against a post-unmount `setState` (the user can
|
|
27
|
+
* leave the screen mid-pull before the request settles).
|
|
28
|
+
*/
|
|
29
|
+
export const useManualRefetch = (
|
|
30
|
+
...refetchFns: Array<() => Promise<unknown>>
|
|
31
|
+
): UseManualRefetchResult => {
|
|
32
|
+
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
33
|
+
|
|
34
|
+
// Snapshot the latest refetch fns so `refetch` stays referentially stable
|
|
35
|
+
// (RefreshControl/onRefresh don't need a new identity each render) while
|
|
36
|
+
// still calling the current query instances.
|
|
37
|
+
const fnsRef = useRef(refetchFns);
|
|
38
|
+
fnsRef.current = refetchFns;
|
|
39
|
+
|
|
40
|
+
const mountedRef = useRef(true);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
mountedRef.current = true;
|
|
43
|
+
return () => {
|
|
44
|
+
mountedRef.current = false;
|
|
45
|
+
};
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
const refetch = useCallback(() => {
|
|
49
|
+
setIsRefreshing(true);
|
|
50
|
+
Promise.all(fnsRef.current.map((fn) => fn())).finally(() => {
|
|
51
|
+
if (mountedRef.current) setIsRefreshing(false);
|
|
52
|
+
});
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
return { isRefreshing, refetch };
|
|
56
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import NetInfo, { NetInfoState } from '@react-native-community/netinfo';
|
|
3
|
+
|
|
4
|
+
export interface NetworkStatus {
|
|
5
|
+
/** The device has an active network interface (wifi / cellular). */
|
|
6
|
+
isConnected: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The interface can actually reach the internet — distinguishes a captive
|
|
9
|
+
* portal or a "connected but no data" wifi from a real connection. `null`
|
|
10
|
+
* while NetInfo is still probing (treated as reachable to avoid a false
|
|
11
|
+
* offline flash on launch).
|
|
12
|
+
*/
|
|
13
|
+
isInternetReachable: boolean | null;
|
|
14
|
+
/**
|
|
15
|
+
* The single flag screens should branch on: truly offline only when there
|
|
16
|
+
* is no interface, or the interface is confirmed unable to reach the
|
|
17
|
+
* internet. A `null` (probing) reachability does NOT count as offline.
|
|
18
|
+
*/
|
|
19
|
+
isOffline: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Maps a raw NetInfo state to our {@link NetworkStatus}. Exported for unit
|
|
24
|
+
* testing the offline-detection rule (connected + reachable handling).
|
|
25
|
+
*/
|
|
26
|
+
export const deriveNetworkStatus = (state: NetInfoState): NetworkStatus => {
|
|
27
|
+
const isConnected = state.isConnected ?? true;
|
|
28
|
+
const isInternetReachable = state.isInternetReachable;
|
|
29
|
+
const isOffline = !isConnected || isInternetReachable === false;
|
|
30
|
+
return { isConnected, isInternetReachable, isOffline };
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Latest known offline state, kept in sync by a module-scope NetInfo listener.
|
|
35
|
+
* Lets non-React code (the player store / queue transport, which runs outside
|
|
36
|
+
* the component tree) read offline status synchronously via {@link isDeviceOffline}
|
|
37
|
+
* — the React hook below can't reach those call sites. Starts `false` so we never
|
|
38
|
+
* report a false "offline" before NetInfo's first probe lands.
|
|
39
|
+
*/
|
|
40
|
+
let deviceOffline = false;
|
|
41
|
+
NetInfo.addEventListener(state => {
|
|
42
|
+
deviceOffline = deriveNetworkStatus(state).isOffline;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Synchronous offline check for non-React callers (e.g. the player queue, which
|
|
47
|
+
* must decide whether stepping to the next track is playable without internet).
|
|
48
|
+
* Components should use {@link useNetworkStatus} instead so they re-render on change.
|
|
49
|
+
*/
|
|
50
|
+
export const isDeviceOffline = (): boolean => deviceOffline;
|
|
51
|
+
|
|
52
|
+
export const useNetworkStatus = (): NetworkStatus => {
|
|
53
|
+
const [status, setStatus] = useState<NetworkStatus>({
|
|
54
|
+
isConnected: true,
|
|
55
|
+
isInternetReachable: null,
|
|
56
|
+
isOffline: false,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
const unsubscribe = NetInfo.addEventListener(state => {
|
|
61
|
+
setStatus(deriveNetworkStatus(state));
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return unsubscribe;
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
return status;
|
|
68
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export default function useTimeout(callback: () => void, delay: number) {
|
|
4
|
+
const callbackRef = useRef(callback);
|
|
5
|
+
const timeoutRef = useRef<any>(null);
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
callbackRef.current = callback;
|
|
9
|
+
}, [callback]);
|
|
10
|
+
|
|
11
|
+
const set = useCallback(() => {
|
|
12
|
+
timeoutRef.current = setTimeout(() => callbackRef.current(), delay);
|
|
13
|
+
}, [delay]);
|
|
14
|
+
|
|
15
|
+
const clear = useCallback(() => {
|
|
16
|
+
timeoutRef.current && clearTimeout(timeoutRef.current);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
set();
|
|
21
|
+
return clear;
|
|
22
|
+
}, [delay, set, clear]);
|
|
23
|
+
|
|
24
|
+
const reset = useCallback(() => {
|
|
25
|
+
clear();
|
|
26
|
+
set();
|
|
27
|
+
}, [clear, set]);
|
|
28
|
+
|
|
29
|
+
return { reset, clear };
|
|
30
|
+
}
|