create-du-app 0.1.3 → 0.1.4
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 +10 -7
- package/package.json +6 -5
- package/src/index.js +8 -8
- 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 +42 -7
- package/templates/mobile/expo/_gitignore +20 -0
- package/templates/mobile/expo/_package.json +62 -1
- package/templates/mobile/expo/app.json +18 -0
- package/templates/mobile/expo/babel.config.js +21 -0
- package/templates/mobile/expo/index.js +5 -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 +36 -0
- package/templates/mobile/expo/src/app/config/translation.ts +26 -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 +12 -0
- package/templates/mobile/expo/src/assets/i18n/fr.json +12 -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/hf-date-time.tsx +301 -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 +117 -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 +252 -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 +104 -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 +23 -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 +22 -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 +84 -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 +36 -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 +33 -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/.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/README.md +40 -7
- package/templates/mobile/rn/_gitignore +24 -0
- package/templates/mobile/rn/_package.json +67 -1
- package/templates/mobile/rn/app.json +4 -0
- package/templates/mobile/rn/babel.config.js +18 -0
- package/templates/mobile/rn/index.js +8 -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 +36 -0
- package/templates/mobile/rn/src/app/config/translation.ts +26 -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 +12 -0
- package/templates/mobile/rn/src/assets/i18n/fr.json +12 -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 +33 -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,90 @@
|
|
|
1
|
+
import { Font, fontSize, 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 } from 'react-native';
|
|
5
|
+
import FieldFrame from '../field/field-frame';
|
|
6
|
+
import { FIELD_BORDER_WIDTH, resolveFieldBorderColor } from '../field/field.shared';
|
|
7
|
+
import { TextAreaProps } from './text-area.type';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Design-system multiline text area (Life-Master-Design "Textarea input field").
|
|
11
|
+
*
|
|
12
|
+
* Shares the field frame + border-state logic with {@link TextField}.
|
|
13
|
+
* States: default / focus / filled / error / disabled.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <TextArea label="Description" placeholder="Text" value={v} onChangeText={setV} />
|
|
17
|
+
*/
|
|
18
|
+
const TextArea = (props: TextAreaProps) => {
|
|
19
|
+
const {
|
|
20
|
+
label,
|
|
21
|
+
message,
|
|
22
|
+
error = false,
|
|
23
|
+
disabled = false,
|
|
24
|
+
minHeight = 140,
|
|
25
|
+
maxHeight = 240,
|
|
26
|
+
containerStyle,
|
|
27
|
+
inputStyle,
|
|
28
|
+
onFocus,
|
|
29
|
+
onBlur,
|
|
30
|
+
...inputProps
|
|
31
|
+
} = props;
|
|
32
|
+
|
|
33
|
+
const { colors } = useTheme();
|
|
34
|
+
const styles = useThemedStyles(makeStyles);
|
|
35
|
+
|
|
36
|
+
const [focused, setFocused] = useState(false);
|
|
37
|
+
const borderColor = resolveFieldBorderColor(colors, { focused, error, disabled });
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<FieldFrame label={label} message={message} error={error} disabled={disabled} style={containerStyle}>
|
|
41
|
+
<TextInput
|
|
42
|
+
{...inputProps}
|
|
43
|
+
multiline
|
|
44
|
+
editable={!disabled}
|
|
45
|
+
textAlignVertical="top"
|
|
46
|
+
placeholderTextColor={colors.fg_neutral_faded}
|
|
47
|
+
onFocus={(e) => {
|
|
48
|
+
setFocused(true);
|
|
49
|
+
onFocus?.(e);
|
|
50
|
+
}}
|
|
51
|
+
onBlur={(e) => {
|
|
52
|
+
setFocused(false);
|
|
53
|
+
onBlur?.(e);
|
|
54
|
+
}}
|
|
55
|
+
scrollEnabled
|
|
56
|
+
style={[
|
|
57
|
+
styles.box,
|
|
58
|
+
{
|
|
59
|
+
minHeight,
|
|
60
|
+
maxHeight,
|
|
61
|
+
borderColor,
|
|
62
|
+
backgroundColor: disabled ? colors.bg_disable : colors.bg_input,
|
|
63
|
+
},
|
|
64
|
+
disabled && styles.inputDisabled,
|
|
65
|
+
inputStyle,
|
|
66
|
+
]}
|
|
67
|
+
/>
|
|
68
|
+
</FieldFrame>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default TextArea;
|
|
73
|
+
|
|
74
|
+
const makeStyles = (c: ThemeColors) =>
|
|
75
|
+
StyleSheet.create({
|
|
76
|
+
box: {
|
|
77
|
+
alignSelf: 'stretch',
|
|
78
|
+
borderRadius: Radius.radius_lg, // 12
|
|
79
|
+
borderWidth: FIELD_BORDER_WIDTH, // 1
|
|
80
|
+
paddingHorizontal: Spacing.spacing_md, // 12
|
|
81
|
+
paddingVertical: Spacing.spacing_m_nudge, // 10
|
|
82
|
+
fontFamily: Font.dmSansRegular,
|
|
83
|
+
fontSize: fontSize(16),
|
|
84
|
+
lineHeight: 24,
|
|
85
|
+
color: c.fg_neutral_normal,
|
|
86
|
+
},
|
|
87
|
+
inputDisabled: {
|
|
88
|
+
color: c.fg_neutral_faded,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleProp, TextInputProps, TextStyle, ViewStyle } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export type TextAreaProps = Omit<TextInputProps, 'style' | 'editable' | 'multiline'> & {
|
|
4
|
+
/** Top label / description. */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Bottom helper / error message. */
|
|
7
|
+
message?: string;
|
|
8
|
+
/** Error (validation) visual + red message. */
|
|
9
|
+
error?: boolean;
|
|
10
|
+
/** Disable input. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/** Minimum height of the box. @default 140 */
|
|
13
|
+
minHeight?: number;
|
|
14
|
+
/** Maximum height; beyond this the field scrolls instead of growing. @default 240 */
|
|
15
|
+
maxHeight?: number;
|
|
16
|
+
/** Override the outer container style. */
|
|
17
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
18
|
+
/** Override the text input style. */
|
|
19
|
+
inputStyle?: StyleProp<TextStyle>;
|
|
20
|
+
};
|
|
@@ -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
|
+
};
|