@mediacubeco/expo-template-fsd 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/git-commit.md +164 -0
- package/.claude/rules/architecture.md +38 -0
- package/.claude/rules/{prettier-eslint-typescript.mdc → prettier-eslint-typescript.md} +5 -10
- package/.claude/settings.json +2 -2
- package/.claude/skills/api/SKILL.md +362 -0
- package/.claude/skills/api/references/client/overview.md +106 -0
- package/.claude/skills/api/references/hooks/index.ts +3 -0
- package/.claude/skills/api/references/hooks/use-api-health.ts +25 -0
- package/.claude/skills/api/references/hooks/use-api-post.ts +46 -0
- package/.claude/skills/api/references/hooks/use-api-posts.ts +24 -0
- package/.claude/skills/api/references/mock/post.mock.ts +29 -0
- package/.claude/skills/{generate-api-model/references → api/references/model}/entities/common.ts +5 -2
- package/.claude/skills/api/references/model/resources/common.ts +9 -0
- package/.claude/skills/api/references/queries/health.ts +14 -0
- package/.claude/skills/api/references/queries/index.ts +2 -0
- package/.claude/skills/api/references/queries/post.ts +23 -0
- package/.claude/skills/api/references/repository/health/health.ts +11 -0
- package/.claude/skills/api/references/repository/health/health.types.ts +25 -0
- package/.claude/skills/api/references/repository/health/index.ts +2 -0
- package/.claude/skills/api/references/repository/post/index.ts +9 -0
- package/.claude/skills/api/references/repository/post/post.ts +34 -0
- package/.claude/skills/api/references/repository/post/post.types.ts +61 -0
- package/.claude/skills/code-style/SKILL.md +1025 -1
- package/.claude/skills/code-style/references/components/payout-speed-option/index.ts +2 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.styles.ts +76 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.tsx +58 -0
- package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.types.ts +22 -0
- package/.claude/skills/code-style/references/components/post/index.ts +2 -0
- package/.claude/skills/code-style/references/components/post/post.styles.ts +19 -0
- package/.claude/skills/code-style/references/components/post/post.tsx +21 -0
- package/.claude/skills/code-style/references/components/post/post.types.ts +9 -0
- package/.claude/skills/code-style/references/components/wrap/index.ts +2 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.styles.ts +15 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.tsx +31 -0
- package/.claude/skills/code-style/references/components/wrap/wrap.types.ts +10 -0
- package/.claude/skills/code-style/references/hooks/use-posts.ts +37 -0
- package/.claude/skills/code-style/references/pages/post/index.ts +4 -0
- package/.claude/skills/code-style/references/pages/post/post-details/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-details/post-details.tsx +13 -0
- package/.claude/skills/code-style/references/pages/post/post-guide/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-guide/post-guide.tsx +17 -0
- package/.claude/skills/code-style/references/pages/post/post-list/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-list/post-list.styles.ts +17 -0
- package/.claude/skills/code-style/references/pages/post/post-list/post-list.tsx +25 -0
- package/.claude/skills/code-style/references/pages/post/post-share/index.ts +1 -0
- package/.claude/skills/code-style/references/pages/post/post-share/post-share.tsx +13 -0
- package/.claude/skills/code-style/references/routes/(stack)/_layout.tsx +18 -0
- package/.claude/skills/code-style/references/routes/(stack)/details.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/guide.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(stack)/share.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/(post-details)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/(post-list)/index.tsx +8 -0
- package/.claude/skills/code-style/references/routes/(tabs)/_layout.tsx +50 -0
- package/.claude/skills/code-style/references/routes/_app.tsx +20 -0
- package/.claude/skills/code-style/references/routes/guide.tsx +8 -0
- package/.claude/skills/code-style/references/routes/share.tsx +8 -0
- package/.claude/skills/code-style/references/store/use-post-draft-store.ts +62 -0
- package/.claude/skills/design-style/SKILL.md +320 -0
- package/.env +3 -1
- package/.env.example +3 -1
- package/CLAUDE.md +5 -11
- package/app.json +6 -3
- package/axios.d.ts +5 -0
- package/env.d.ts +3 -1
- package/package.json +33 -40
- package/patches/react-native-screens+4.25.2.patch +118 -0
- package/react-query.d.ts +13 -0
- package/src/app/(root)/(home)/_layout.tsx +2 -2
- package/src/app/(root)/(home)/index.tsx +2 -2
- package/src/app/(root)/(settings)/_layout.tsx +2 -2
- package/src/app/(root)/(settings)/index.tsx +2 -2
- package/src/app/(root)/_layout.tsx +35 -32
- package/src/pages/home/home-main.tsx +3 -7
- package/src/pages/settings/settings-main.tsx +3 -7
- package/src/shared/assets/fonts/index.ts +3 -3
- package/src/shared/assets/fonts/sf-pro-text-bold.ttf +0 -0
- package/src/shared/assets/fonts/sf-pro-text-regular.ttf +0 -0
- package/src/shared/assets/fonts/sf-pro-text-semibold.ttf +0 -0
- package/src/shared/assets/icons/back.tsx +21 -0
- package/src/shared/assets/icons/close.tsx +21 -0
- package/src/shared/assets/icons/common.ts +8 -0
- package/src/shared/assets/icons/default.tsx +44 -0
- package/src/shared/assets/icons/index.ts +13 -1
- package/src/shared/assets/icons/refresh.tsx +23 -0
- package/src/shared/assets/icons/share.tsx +38 -0
- package/src/shared/assets/icons/type.ts +7 -0
- package/src/shared/assets/translations/index.ts +4 -4
- package/src/shared/constants/config.ts +13 -0
- package/src/shared/constants/environment.ts +16 -4
- package/src/shared/constants/index.ts +1 -0
- package/src/shared/hooks/index.ts +1 -4
- package/src/shared/hooks/use-compose.ts +23 -0
- package/src/shared/lib/api/api.client.ts +157 -0
- package/src/shared/lib/api/api.provider.tsx +14 -36
- package/src/shared/lib/api/api.ts +1 -5
- package/src/shared/lib/api/api.types.ts +6 -3
- package/src/shared/lib/api/hooks/index.ts +1 -0
- package/src/shared/lib/api/index.ts +2 -3
- package/src/shared/lib/api/models/api-instance/api-instance.client.ts +50 -0
- package/src/shared/lib/api/models/api-instance/api-instance.ts +30 -82
- package/src/shared/lib/api/models/api-instance/api-instance.types.ts +16 -18
- package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +13 -20
- package/src/shared/lib/api/models/api-instance/index.ts +3 -0
- package/src/shared/lib/api/queries/index.ts +1 -0
- package/src/shared/lib/api/repositories/index.ts +1 -1
- package/src/shared/lib/localization/constants/index.ts +16 -5
- package/src/shared/lib/localization/index.ts +5 -1
- package/src/shared/lib/navigation/hooks/index.ts +8 -3
- package/src/shared/{hooks/use-breakpoints.ts → lib/navigation/hooks/use-navigation-breakpoints.ts} +2 -5
- package/src/shared/lib/navigation/hooks/use-navigation-dimensions.ts +12 -0
- package/src/shared/lib/navigation/hooks/use-navigation-options.tsx +175 -0
- package/src/shared/lib/navigation/hooks/{use-navigation.ts → use-navigation-route.ts} +2 -2
- package/src/shared/lib/navigation/hooks/use-navigation-safe-area.ts +61 -0
- package/src/shared/lib/navigation/hooks/use-navigation-tabs-native.ts +10 -0
- package/src/shared/lib/navigation/hooks/use-navigation-tabs.tsx +10 -0
- package/src/shared/lib/navigation/hooks/use-navigation-transition.ts +27 -0
- package/src/shared/lib/navigation/index.ts +11 -4
- package/src/shared/lib/navigation/navigation-tabs-native.context.tsx +5 -0
- package/src/shared/lib/navigation/navigation-tabs-native.provider.tsx +17 -0
- package/src/shared/lib/navigation/navigation-tabs.context.tsx +10 -0
- package/src/shared/lib/navigation/navigation-tabs.provider.tsx +22 -0
- package/src/shared/lib/navigation/navigation.provider.tsx +6 -1
- package/src/shared/lib/storage/index.ts +1 -1
- package/src/shared/lib/storage/storage.provider.tsx +4 -0
- package/src/shared/lib/storage/storage.ts +29 -25
- package/src/shared/lib/theme/constants/animations.ts +26 -0
- package/src/shared/lib/theme/constants/colors.ts +336 -43
- package/src/shared/lib/theme/constants/delays.ts +1 -1
- package/src/shared/lib/theme/constants/durations.ts +1 -1
- package/src/shared/lib/theme/constants/fonts.ts +2 -1
- package/src/shared/lib/theme/constants/index.ts +42 -20
- package/src/shared/lib/theme/constants/shadows.ts +7 -25
- package/src/shared/lib/theme/constants/sizes.ts +11 -2
- package/src/shared/lib/theme/constants/transparencies.ts +1 -1
- package/src/shared/lib/theme/hooks/use-theme-setup.ts +3 -1
- package/src/shared/lib/theme/index.ts +10 -1
- package/src/shared/lib/theme/theme.provider.tsx +45 -19
- package/src/shared/lib/theme/theme.scope.tsx +43 -0
- package/src/shared/lib/theme/theme.types.ts +15 -3
- package/src/shared/lib/theme/utils/color.ts +81 -0
- package/src/shared/lib/theme/utils/index.ts +2 -0
- package/src/shared/lib/theme/utils/snapshot.ts +17 -0
- package/src/shared/lib/theme/utils/storage.ts +5 -2
- package/src/shared/services/browser/browser.ts +66 -0
- package/src/shared/services/browser/index.ts +1 -0
- package/src/shared/services/cookies/cookies.ts +73 -0
- package/src/shared/services/cookies/index.ts +1 -0
- package/src/shared/services/index.ts +5 -0
- package/src/shared/services/linking/index.ts +1 -0
- package/src/shared/services/linking/linking.ts +22 -0
- package/src/shared/services/share/index.ts +1 -0
- package/src/shared/services/share/share.ts +28 -0
- package/src/shared/services/universal-links/index.ts +104 -0
- package/src/shared/services/universal-links/universal-links.ts +25 -0
- package/src/shared/services/universal-links/universal-links.types.ts +5 -0
- package/src/shared/ui/atoms/blur/blur.tsx +9 -4
- package/src/shared/ui/atoms/blur/blur.types.ts +1 -1
- package/src/shared/ui/atoms/blur-progress/blur-progress.styles.ts +16 -0
- package/src/shared/ui/atoms/blur-progress/blur-progress.tsx +30 -0
- package/src/shared/ui/atoms/blur-progress/blur-progress.types.tsx +5 -0
- package/src/shared/ui/atoms/blur-progress/index.ts +1 -0
- package/src/shared/ui/atoms/glass/glass.tsx +13 -5
- package/src/shared/ui/atoms/glass/glass.types.ts +1 -1
- package/src/shared/ui/atoms/icon/icon.styles.ts +82 -0
- package/src/shared/ui/atoms/icon/icon.tsx +21 -0
- package/src/shared/ui/atoms/icon/icon.types.ts +23 -0
- package/src/shared/ui/atoms/icon/index.ts +2 -0
- package/src/shared/ui/atoms/index.ts +7 -0
- package/src/shared/ui/atoms/skeleton/index.ts +2 -0
- package/src/shared/ui/atoms/skeleton/skeleton.styles.ts +69 -0
- package/src/shared/ui/atoms/skeleton/skeleton.tsx +26 -0
- package/src/shared/ui/atoms/skeleton/skeleton.types.ts +9 -0
- package/src/shared/ui/atoms/spinner/index.ts +2 -0
- package/src/shared/ui/atoms/spinner/spinner.styles.ts +102 -0
- package/src/shared/ui/atoms/spinner/spinner.tsx +48 -0
- package/src/shared/ui/atoms/spinner/spinner.types.ts +25 -0
- package/src/shared/ui/atoms/text/index.ts +2 -0
- package/src/shared/ui/atoms/text/text.styles.ts +186 -0
- package/src/shared/ui/atoms/text/text.tsx +37 -0
- package/src/shared/ui/atoms/text/text.types.ts +35 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.styles.ts +21 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.tsx +28 -0
- package/src/shared/ui/atoms/touch/components/haptics/haptics.types.ts +17 -0
- package/src/shared/ui/atoms/touch/components/haptics/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/index.ts +3 -0
- package/src/shared/ui/atoms/touch/components/ripple/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.styles.ts +78 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.tsx +29 -0
- package/src/shared/ui/atoms/touch/components/ripple/ripple.types.ts +17 -0
- package/src/shared/ui/atoms/touch/components/spring/index.ts +2 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.styles.ts +31 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.tsx +28 -0
- package/src/shared/ui/atoms/touch/components/spring/spring.types.ts +17 -0
- package/src/shared/ui/atoms/touch/index.ts +2 -0
- package/src/shared/ui/atoms/touch/touch.styles.ts +14 -0
- package/src/shared/ui/atoms/touch/touch.tsx +137 -0
- package/src/shared/ui/atoms/touch/touch.types.ts +27 -0
- package/src/shared/ui/atoms/visibility/index.ts +2 -0
- package/src/shared/ui/atoms/visibility/visibility.styles.ts +85 -0
- package/src/shared/ui/atoms/visibility/visibility.tsx +61 -0
- package/src/shared/ui/atoms/visibility/visibility.types.ts +34 -0
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +11 -9
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +3 -6
- package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +3 -5
- package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.styles.ts +41 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.tsx +12 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.types.ts +13 -0
- package/src/shared/ui/molecules/slider/components/indicator/components/dot/index.ts +2 -0
- package/src/shared/ui/molecules/slider/components/indicator/index.ts +2 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.styles.ts +20 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.tsx +22 -0
- package/src/shared/ui/molecules/slider/components/indicator/indicator.types.ts +10 -0
- package/src/shared/ui/molecules/slider/index.ts +3 -0
- package/src/shared/ui/molecules/slider/slider.styles.ts +68 -0
- package/src/shared/ui/molecules/slider/slider.tsx +64 -0
- package/src/shared/ui/molecules/slider/slider.types.ts +28 -0
- package/src/shared/ui/organisms/bar/bar.styles.ts +100 -0
- package/src/shared/ui/organisms/bar/bar.tsx +89 -0
- package/src/shared/ui/organisms/bar/bar.types.ts +49 -0
- package/src/shared/ui/organisms/bar/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab/bar-tab.tsx +9 -0
- package/src/shared/ui/organisms/bar-tab/bar-tab.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab/components/index.ts +1 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.styles.ts +21 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.tsx +18 -0
- package/src/shared/ui/organisms/bar-tab/components/tab/tab.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.styles.ts +22 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.tsx +66 -0
- package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.types.ts +5 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/index.ts +1 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/index.ts +2 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.styles.ts +56 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.tsx +30 -0
- package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.types.ts +7 -0
- package/src/shared/ui/organisms/bar-tab-navigation/index.ts +2 -0
- package/src/shared/ui/organisms/index.ts +4 -1
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.styles.ts +41 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.tsx +12 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.types.ts +13 -0
- package/src/shared/ui/organisms/slider/components/indicator/components/dot/index.ts +2 -0
- package/src/shared/ui/organisms/slider/components/indicator/index.ts +2 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.styles.ts +20 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.tsx +22 -0
- package/src/shared/ui/organisms/slider/components/indicator/indicator.types.ts +10 -0
- package/src/shared/ui/organisms/slider/index.ts +3 -0
- package/src/shared/ui/organisms/slider/slider.styles.ts +68 -0
- package/src/shared/ui/organisms/slider/slider.tsx +64 -0
- package/src/shared/ui/organisms/slider/slider.types.ts +28 -0
- package/src/shared/ui/templates/index.ts +3 -0
- package/src/shared/ui/templates/modal-container/index.ts +13 -0
- package/src/shared/ui/templates/modal-container/modal-container-list.tsx +45 -0
- package/src/shared/ui/templates/modal-container/modal-container-scroll.tsx +66 -0
- package/src/shared/ui/templates/modal-container/modal-container-section-list.tsx +46 -0
- package/src/shared/ui/templates/modal-container/modal-container.hooks.tsx +172 -0
- package/src/shared/ui/templates/modal-container/modal-container.styles.ts +121 -0
- package/src/shared/ui/templates/modal-container/modal-container.types.ts +69 -0
- package/src/shared/ui/templates/screen-container/index.ts +1 -1
- package/src/shared/ui/templates/screen-container/screen-container-list.tsx +28 -10
- package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +28 -10
- package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +29 -11
- package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +79 -8
- package/src/shared/ui/templates/screen-container/screen-container.styles.ts +105 -15
- package/src/shared/ui/templates/screen-container/screen-container.types.tsx +34 -7
- package/src/shared/ui/templates/screen-header/components/index.ts +6 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.tsx +15 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.types.ts +11 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.styles.ts +44 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.tsx +65 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.types.ts +14 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.tsx +17 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.types.ts +6 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.tsx +27 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.types.ts +10 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.tsx +15 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.types.ts +10 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/index.ts +2 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.tsx +19 -0
- package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.types.ts +13 -0
- package/src/shared/ui/templates/screen-header/index.ts +3 -0
- package/src/shared/ui/templates/screen-header/screen-header.styles.ts +49 -0
- package/src/shared/ui/templates/screen-header/screen-header.tsx +26 -0
- package/src/shared/ui/templates/screen-header/screen-header.types.ts +15 -0
- package/src/shared/ui/templates/web-container/index.ts +2 -0
- package/src/shared/ui/templates/web-container/web-container.styles.ts +34 -0
- package/src/shared/ui/templates/web-container/web-container.tsx +172 -0
- package/src/shared/ui/templates/web-container/web-container.types.ts +21 -0
- package/.claude/rules/app-routing.mdc +0 -47
- package/.claude/rules/exports-at-end.mdc +0 -55
- package/.claude/rules/kebab-case-naming.mdc +0 -29
- package/.claude/rules/mediacubeco-packages.mdc +0 -56
- package/.claude/skills/generate-api-model/SKILL.md +0 -230
- package/.claude/skills/generate-api-model/references/resources/common.ts +0 -8
- package/.claude/skills/generate-api-model-mock/SKILL.md +0 -56
- package/.claude/skills/git-commit/SKILL.md +0 -139
- package/.gitignore +0 -34
- package/src/shared/assets/icons/like.tsx +0 -17
- package/src/shared/hooks/use-animated.ts +0 -200
- package/src/shared/hooks/use-mixins.ts +0 -36
- package/src/shared/hooks/use-safe-area.ts +0 -75
- package/src/shared/lib/api/repositories/auth/auth.ts +0 -23
- package/src/shared/lib/api/repositories/auth/auth.types.ts +0 -22
- package/src/shared/lib/api/repositories/auth/index.ts +0 -1
- package/src/shared/lib/navigation/hooks/use-navigation-native-tabs.ts +0 -10
- package/src/shared/lib/navigation/hooks/use-navigation-options.ts +0 -123
- package/src/shared/lib/navigation/navigation-native-tabs.context.tsx +0 -5
- package/src/shared/lib/navigation/navigation-native-tabs.provider.tsx +0 -12
- package/src/shared/lib/theme/constants/borders.ts +0 -8
- package/src/shared/lib/theme/constants/weights.ts +0 -13
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post-comment.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-settings.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-verification.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post-comment.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/index.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-settings.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-verification.ts +0 -0
- /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user.ts +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import Animated from 'react-native-reanimated'
|
|
2
|
+
import { useDotStyles } from './dot.styles'
|
|
3
|
+
import { type DotProps } from './dot.types'
|
|
4
|
+
|
|
5
|
+
/** Одна точка индикатора-«пилюли»: расширяется и подсвечивается по мере приближения своего слайда. */
|
|
6
|
+
const Dot = ({ index, scrollX, itemWidth }: DotProps) => {
|
|
7
|
+
const { styles, animatedStyle } = useDotStyles({ index, scrollX, itemWidth })
|
|
8
|
+
|
|
9
|
+
return <Animated.View style={[styles.dot, animatedStyle]} />
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default Dot
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type SharedValue } from 'react-native-reanimated'
|
|
2
|
+
|
|
3
|
+
/** Пропсы одной точки индикатора: её позиция в ряду и общий прогресс скролла слайдера. */
|
|
4
|
+
type DotProps = {
|
|
5
|
+
index: number
|
|
6
|
+
scrollX: SharedValue<number>
|
|
7
|
+
itemWidth: SharedValue<number>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Контекст стиль-хука точки — совпадает с её пропсами. */
|
|
11
|
+
type DotStylesContext = DotProps
|
|
12
|
+
|
|
13
|
+
export type { DotProps, DotStylesContext }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native'
|
|
2
|
+
import { useThemeMode } from '../../../../../lib'
|
|
3
|
+
|
|
4
|
+
/** Визуальная модель индикатора: горизонтальный ряд точек по центру с равными промежутками. */
|
|
5
|
+
const useIndicatorStyles = () => {
|
|
6
|
+
const { SIZES } = useThemeMode()
|
|
7
|
+
|
|
8
|
+
const styles = StyleSheet.create({
|
|
9
|
+
wrapper: {
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
justifyContent: 'center',
|
|
13
|
+
gap: SIZES['M'],
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return { styles }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { useIndicatorStyles }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useMemo } from 'react'
|
|
2
|
+
import { View } from 'react-native'
|
|
3
|
+
import { Dot } from './components/dot'
|
|
4
|
+
import { useIndicatorStyles } from './indicator.styles'
|
|
5
|
+
import { type IndicatorProps } from './indicator.types'
|
|
6
|
+
|
|
7
|
+
/** Индикатор текущего слайда: ряд точек, где активная плавно расширяется в «пилюлю» вслед за скроллом. */
|
|
8
|
+
const Indicator = ({ itemCount, scrollX, itemWidth }: IndicatorProps) => {
|
|
9
|
+
const { styles } = useIndicatorStyles()
|
|
10
|
+
|
|
11
|
+
const dotList = useMemo(() => Array.from({ length: itemCount }, (_, index) => index), [itemCount])
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<View style={styles.wrapper}>
|
|
15
|
+
{dotList.map((index) => (
|
|
16
|
+
<Dot key={index} index={index} scrollX={scrollX} itemWidth={itemWidth} />
|
|
17
|
+
))}
|
|
18
|
+
</View>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default Indicator
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type SharedValue } from 'react-native-reanimated'
|
|
2
|
+
|
|
3
|
+
/** Пропсы индикатора слайдов: число точек и общий прогресс скролла слайдера с шириной слайда. */
|
|
4
|
+
type IndicatorProps = {
|
|
5
|
+
itemCount: number
|
|
6
|
+
scrollX: SharedValue<number>
|
|
7
|
+
itemWidth: SharedValue<number>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type { IndicatorProps }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DEFAULT_LAYOUT, useLayout } from '@mediacubeco/base-react-native/hooks'
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import { StyleSheet } from 'react-native'
|
|
4
|
+
import { useNavigationDimensions } from '../../../lib'
|
|
5
|
+
import { type SliderStylesContext } from './slider.types'
|
|
6
|
+
|
|
7
|
+
const useSliderStyles = ({
|
|
8
|
+
itemCount,
|
|
9
|
+
itemWidth,
|
|
10
|
+
itemGap,
|
|
11
|
+
sliderItemWidth,
|
|
12
|
+
sliderItemGap,
|
|
13
|
+
}: SliderStylesContext) => {
|
|
14
|
+
const [wrapperLayout, setLayout] = useState(DEFAULT_LAYOUT)
|
|
15
|
+
const { WIDTH } = useNavigationDimensions()
|
|
16
|
+
const handleWrapperLayout = useLayout({ setLayout })
|
|
17
|
+
|
|
18
|
+
const SLIDER_ITEM_WIDTH = itemWidth ?? wrapperLayout.width
|
|
19
|
+
const SLIDER_ITEM_GAP = itemGap ?? (WIDTH - SLIDER_ITEM_WIDTH) / 2
|
|
20
|
+
|
|
21
|
+
const styles = StyleSheet.create({
|
|
22
|
+
wrapper: {},
|
|
23
|
+
content: {
|
|
24
|
+
overflow: 'visible',
|
|
25
|
+
},
|
|
26
|
+
container: {
|
|
27
|
+
gap: SLIDER_ITEM_GAP,
|
|
28
|
+
},
|
|
29
|
+
item: {
|
|
30
|
+
width: SLIDER_ITEM_WIDTH,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const snapToOffsets = useMemo(() => {
|
|
35
|
+
return Array.from(
|
|
36
|
+
{ length: itemCount },
|
|
37
|
+
(_, index) => index * (SLIDER_ITEM_WIDTH + SLIDER_ITEM_GAP)
|
|
38
|
+
)
|
|
39
|
+
}, [itemCount, SLIDER_ITEM_WIDTH, SLIDER_ITEM_GAP])
|
|
40
|
+
|
|
41
|
+
const getItemLayout = useCallback(
|
|
42
|
+
(_: unknown, index: number) => ({
|
|
43
|
+
length: SLIDER_ITEM_WIDTH,
|
|
44
|
+
offset: (SLIDER_ITEM_WIDTH + SLIDER_ITEM_GAP) * index,
|
|
45
|
+
index,
|
|
46
|
+
}),
|
|
47
|
+
[SLIDER_ITEM_WIDTH, SLIDER_ITEM_GAP]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (sliderItemWidth) {
|
|
52
|
+
sliderItemWidth.set(SLIDER_ITEM_WIDTH)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (sliderItemGap) {
|
|
56
|
+
sliderItemGap.set(SLIDER_ITEM_GAP)
|
|
57
|
+
}
|
|
58
|
+
}, [sliderItemWidth, sliderItemGap, SLIDER_ITEM_WIDTH, SLIDER_ITEM_GAP])
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
styles,
|
|
62
|
+
snapToOffsets,
|
|
63
|
+
getItemLayout,
|
|
64
|
+
handleWrapperLayout,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { useSliderStyles }
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { runCallback } from '@mediacubeco/base/utils'
|
|
2
|
+
import { useCallback } from 'react'
|
|
3
|
+
import { type ListRenderItem, View } from 'react-native'
|
|
4
|
+
import Animated from 'react-native-reanimated'
|
|
5
|
+
import { useSliderStyles } from './slider.styles'
|
|
6
|
+
import { type SliderProps } from './slider.types'
|
|
7
|
+
|
|
8
|
+
const Slider = <Item,>({
|
|
9
|
+
ref,
|
|
10
|
+
style,
|
|
11
|
+
data = [],
|
|
12
|
+
itemWidth,
|
|
13
|
+
itemGap,
|
|
14
|
+
sliderItemWidth,
|
|
15
|
+
sliderItemGap,
|
|
16
|
+
onScroll,
|
|
17
|
+
onScrollBeginDrag,
|
|
18
|
+
onMomentumScrollEnd,
|
|
19
|
+
renderItem: inputRenderItem,
|
|
20
|
+
}: SliderProps<Item>) => {
|
|
21
|
+
const itemCount = data.length
|
|
22
|
+
|
|
23
|
+
const { styles, snapToOffsets, getItemLayout, handleWrapperLayout } = useSliderStyles({
|
|
24
|
+
itemCount,
|
|
25
|
+
itemWidth,
|
|
26
|
+
itemGap,
|
|
27
|
+
sliderItemWidth,
|
|
28
|
+
sliderItemGap,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const renderItem: ListRenderItem<Item> = useCallback(
|
|
32
|
+
({ item, index, separators }) => {
|
|
33
|
+
return (
|
|
34
|
+
<View style={styles.item}>{runCallback(inputRenderItem, { item, index, separators })}</View>
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
[styles.item, inputRenderItem]
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<View style={[styles.wrapper, style]} onLayout={handleWrapperLayout}>
|
|
42
|
+
<Animated.FlatList
|
|
43
|
+
ref={ref}
|
|
44
|
+
data={data}
|
|
45
|
+
style={styles.content}
|
|
46
|
+
contentContainerStyle={styles.container}
|
|
47
|
+
scrollEventThrottle={16}
|
|
48
|
+
decelerationRate={'fast'}
|
|
49
|
+
snapToAlignment={'center'}
|
|
50
|
+
snapToOffsets={snapToOffsets}
|
|
51
|
+
horizontal={true}
|
|
52
|
+
showsVerticalScrollIndicator={false}
|
|
53
|
+
showsHorizontalScrollIndicator={false}
|
|
54
|
+
onScroll={onScroll}
|
|
55
|
+
onScrollBeginDrag={onScrollBeginDrag}
|
|
56
|
+
onMomentumScrollEnd={onMomentumScrollEnd}
|
|
57
|
+
getItemLayout={getItemLayout}
|
|
58
|
+
renderItem={renderItem}
|
|
59
|
+
/>
|
|
60
|
+
</View>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default Slider
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type FlatListProps, type ListRenderItem, type ViewProps } from 'react-native'
|
|
2
|
+
import type Animated from 'react-native-reanimated'
|
|
3
|
+
import { type AnimatedProps, type AnimatedRef, type SharedValue } from 'react-native-reanimated'
|
|
4
|
+
|
|
5
|
+
type SliderRef = Animated.FlatList
|
|
6
|
+
|
|
7
|
+
type SliderProps<Item = unknown> = {
|
|
8
|
+
ref?: AnimatedRef<SliderRef>
|
|
9
|
+
style?: ViewProps['style']
|
|
10
|
+
data: Item[]
|
|
11
|
+
itemWidth?: number
|
|
12
|
+
itemGap?: number
|
|
13
|
+
sliderItemWidth?: SharedValue<number>
|
|
14
|
+
sliderItemGap?: SharedValue<number>
|
|
15
|
+
onScroll?: AnimatedProps<FlatListProps<Item>>['onScroll']
|
|
16
|
+
onScrollBeginDrag?: FlatListProps<Item>['onScrollBeginDrag']
|
|
17
|
+
onMomentumScrollEnd?: FlatListProps<Item>['onMomentumScrollEnd']
|
|
18
|
+
renderItem: ListRenderItem<Item>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type SliderStylesContext = Pick<
|
|
22
|
+
SliderProps,
|
|
23
|
+
'itemWidth' | 'itemGap' | 'sliderItemWidth' | 'sliderItemGap'
|
|
24
|
+
> & {
|
|
25
|
+
itemCount: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type { SliderRef, SliderProps, SliderStylesContext }
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as ModalContainerList } from './modal-container-list'
|
|
2
|
+
export { default as ModalContainerScroll } from './modal-container-scroll'
|
|
3
|
+
export { default as ModalContainerSectionList } from './modal-container-section-list'
|
|
4
|
+
export type {
|
|
5
|
+
ModalContainerScrollProps,
|
|
6
|
+
ModalContainerListProps,
|
|
7
|
+
ModalContainerSectionListProps,
|
|
8
|
+
ModalContainerListRenderItemInfo,
|
|
9
|
+
ModalContainerSectionListRenderItemInfo,
|
|
10
|
+
ModalContainerSectionListRenderHeaderInfo,
|
|
11
|
+
} from './modal-container.types'
|
|
12
|
+
|
|
13
|
+
// TODO: extends of screen-container
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { FlatList, View } from 'react-native'
|
|
2
|
+
import { useModalContainerProps } from './modal-container.hooks'
|
|
3
|
+
import { type ModalContainerListProps } from './modal-container.types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Контейнер модалки поверх FlatList: список облегает контент и скроллится при переполнении.
|
|
7
|
+
* Высота списка задаётся явно (`bodyHeight` = `min(контент, MAX)`), поэтому короткий список
|
|
8
|
+
* облегается, а длинный упирается в максимум и виртуализируется. Шапка и футер — абсолютные
|
|
9
|
+
* оверлеи с блюром поверх списка, поэтому элементы плавно уходят под них.
|
|
10
|
+
*/
|
|
11
|
+
const ModalContainerList = <Item,>(props: ModalContainerListProps<Item>) => {
|
|
12
|
+
const {
|
|
13
|
+
ref,
|
|
14
|
+
header,
|
|
15
|
+
footer,
|
|
16
|
+
scrollGuard,
|
|
17
|
+
isOverflow: _isOverflow,
|
|
18
|
+
bodyHeight,
|
|
19
|
+
bottomOffset: _bottomOffset,
|
|
20
|
+
handleContentLayout: _handleContentLayout,
|
|
21
|
+
handleContentSizeChange,
|
|
22
|
+
wrapperStyle,
|
|
23
|
+
bodyStyle: _bodyStyle,
|
|
24
|
+
outerStyle,
|
|
25
|
+
innerStyle,
|
|
26
|
+
...flatListProps
|
|
27
|
+
} = useModalContainerProps<ModalContainerListProps<Item>>(props)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={wrapperStyle}>
|
|
31
|
+
{scrollGuard}
|
|
32
|
+
<FlatList
|
|
33
|
+
ref={ref}
|
|
34
|
+
style={[{ height: bodyHeight }, outerStyle]}
|
|
35
|
+
contentContainerStyle={innerStyle}
|
|
36
|
+
onContentSizeChange={handleContentSizeChange}
|
|
37
|
+
{...flatListProps}
|
|
38
|
+
/>
|
|
39
|
+
{header}
|
|
40
|
+
{footer}
|
|
41
|
+
</View>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default ModalContainerList
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type ReactNode, type Ref } from 'react'
|
|
2
|
+
import { type ScrollView, type ScrollViewProps, View } from 'react-native'
|
|
3
|
+
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
|
|
4
|
+
import { useModalContainerProps } from './modal-container.hooks'
|
|
5
|
+
import { type ModalContainerScrollProps } from './modal-container.types'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Тело скролл-варианта — `KeyboardAwareScrollView`: доскролливает сфокусированный инпут выше
|
|
9
|
+
* клавиатуры и приподнятого футера (`bottomOffset`) и добавляет ghost-отступ снизу, чтобы
|
|
10
|
+
* контент за клавиатурой оставался досягаемым. Окно formSheet не ресайзится ни на одной из
|
|
11
|
+
* платформ (лист сдвигает система), поэтому обе живут на клавиатурных событиях
|
|
12
|
+
* keyboard-controller.
|
|
13
|
+
*/
|
|
14
|
+
const ScrollBody = KeyboardAwareScrollView as (
|
|
15
|
+
props: ScrollViewProps & { ref?: Ref<ScrollView>; bottomOffset?: number }
|
|
16
|
+
) => ReactNode
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Контейнер модалки со скроллом: короткий контент облегается плоской `View`, длинный
|
|
20
|
+
* скроллится внутри `ScrollView`. Шапка и футер — абсолютные оверлеи с блюром поверх тела,
|
|
21
|
+
* поэтому контент плавно уходит под них.
|
|
22
|
+
*/
|
|
23
|
+
const ModalContainerScroll = (props: ModalContainerScrollProps) => {
|
|
24
|
+
const {
|
|
25
|
+
ref,
|
|
26
|
+
header,
|
|
27
|
+
footer,
|
|
28
|
+
scrollGuard,
|
|
29
|
+
isOverflow,
|
|
30
|
+
bodyHeight: _bodyHeight,
|
|
31
|
+
bottomOffset,
|
|
32
|
+
handleContentLayout,
|
|
33
|
+
handleContentSizeChange: _handleContentSizeChange,
|
|
34
|
+
wrapperStyle,
|
|
35
|
+
bodyStyle,
|
|
36
|
+
outerStyle,
|
|
37
|
+
innerStyle,
|
|
38
|
+
children,
|
|
39
|
+
...scrollViewProps
|
|
40
|
+
} = useModalContainerProps<ModalContainerScrollProps>(props)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<View style={wrapperStyle}>
|
|
44
|
+
{scrollGuard}
|
|
45
|
+
{isOverflow ? (
|
|
46
|
+
<ScrollBody
|
|
47
|
+
ref={ref}
|
|
48
|
+
style={[bodyStyle, outerStyle]}
|
|
49
|
+
contentContainerStyle={innerStyle}
|
|
50
|
+
bottomOffset={bottomOffset}
|
|
51
|
+
{...scrollViewProps}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</ScrollBody>
|
|
55
|
+
) : (
|
|
56
|
+
<View style={innerStyle} collapsable={false} onLayout={handleContentLayout}>
|
|
57
|
+
{children}
|
|
58
|
+
</View>
|
|
59
|
+
)}
|
|
60
|
+
{header}
|
|
61
|
+
{footer}
|
|
62
|
+
</View>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default ModalContainerScroll
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SectionList, View } from 'react-native'
|
|
2
|
+
import { useModalContainerProps } from './modal-container.hooks'
|
|
3
|
+
import { type ModalContainerSectionListProps } from './modal-container.types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Контейнер модалки поверх SectionList: секции облегают контент и скроллятся при переполнении.
|
|
7
|
+
* Высота списка задаётся явно (`bodyHeight` = `min(контент, MAX)`), поэтому короткие секции
|
|
8
|
+
* облегаются, а длинные упираются в максимум и виртуализируются. Шапка и футер — абсолютные
|
|
9
|
+
* оверлеи с блюром поверх списка, поэтому элементы плавно уходят под них.
|
|
10
|
+
*/
|
|
11
|
+
const ModalContainerSectionList = <Item,>(props: ModalContainerSectionListProps<Item>) => {
|
|
12
|
+
const {
|
|
13
|
+
ref,
|
|
14
|
+
header,
|
|
15
|
+
footer,
|
|
16
|
+
scrollGuard,
|
|
17
|
+
isOverflow: _isOverflow,
|
|
18
|
+
bodyHeight,
|
|
19
|
+
bottomOffset: _bottomOffset,
|
|
20
|
+
handleContentLayout: _handleContentLayout,
|
|
21
|
+
handleContentSizeChange,
|
|
22
|
+
wrapperStyle,
|
|
23
|
+
bodyStyle: _bodyStyle,
|
|
24
|
+
outerStyle,
|
|
25
|
+
innerStyle,
|
|
26
|
+
...sectionListProps
|
|
27
|
+
} = useModalContainerProps<ModalContainerSectionListProps<Item>>(props)
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<View style={wrapperStyle}>
|
|
31
|
+
{scrollGuard}
|
|
32
|
+
<SectionList
|
|
33
|
+
ref={ref}
|
|
34
|
+
style={[{ height: bodyHeight }, outerStyle]}
|
|
35
|
+
contentContainerStyle={innerStyle}
|
|
36
|
+
onContentSizeChange={handleContentSizeChange}
|
|
37
|
+
stickySectionHeadersEnabled={false}
|
|
38
|
+
{...sectionListProps}
|
|
39
|
+
/>
|
|
40
|
+
{header}
|
|
41
|
+
{footer}
|
|
42
|
+
</View>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default ModalContainerSectionList
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react'
|
|
2
|
+
import { type LayoutChangeEvent, useWindowDimensions, View } from 'react-native'
|
|
3
|
+
import { useAnimatedKeyboard } from 'react-native-keyboard-controller'
|
|
4
|
+
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
|
|
5
|
+
import { LinearGradient } from 'expo-linear-gradient'
|
|
6
|
+
import { BlurProgress } from '../../atoms'
|
|
7
|
+
import { useModalContainerStyles } from './modal-container.styles'
|
|
8
|
+
import {
|
|
9
|
+
type ModalContainerListProps,
|
|
10
|
+
type ModalContainerScrollProps,
|
|
11
|
+
type ModalContainerSectionListProps,
|
|
12
|
+
} from './modal-container.types'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Собирает общие пропсы для всех вариантов контейнера модалки. Шапка и футер — абсолютные
|
|
16
|
+
* оверлеи с прогрессивным блюром (`BlurProgress`): контент скроллится под ними и плавно
|
|
17
|
+
* растворяется у краёв. Тело отступает от краёв на замеренные высоты оверлеев
|
|
18
|
+
* (`paddingTop`/`paddingBottom`), поэтому крайние элементы не прячутся под шапкой/футером.
|
|
19
|
+
* Высоты шапки и футера снимаются через `onLayout`, высота контента — через `onLayout`
|
|
20
|
+
* плоского тела (скролл-вариант) или `onContentSizeChange` списка. Пока контент помещается в
|
|
21
|
+
* `MAX_SHEET_HEIGHT`, тело облегает его; когда выше — включается `isOverflow`: враппер
|
|
22
|
+
* фиксируется по высоте (`wrapperCapped`), тело начинает скроллиться. Нижний отступ — из
|
|
23
|
+
* сырого safe-area, без таб-бар-логики `useNavigationSafeArea`. Поверх блюра оверлеев лежит градиент
|
|
24
|
+
* цвета фона, растворяющий контент у кромок листа. Клавиатура: лист целиком сдвигает система
|
|
25
|
+
* (UIKit на iOS, транслейт react-native-screens на Android) — вверх до упора в
|
|
26
|
+
* `SHEET_KEYBOARD_TOP`, — а не поместившийся над клавиатурой остаток листа компенсирует
|
|
27
|
+
* `footerLiftStyle`, приподнимая футер внутри листа на величину перекрытия (детент при этом
|
|
28
|
+
* не меняется, иначе его анимация сбивает клавиатурный сдвиг листа и лист
|
|
29
|
+
* «переоткрывается»). `bottomOffset` — зазор для `KeyboardAwareScrollView` скролл-варианта:
|
|
30
|
+
* сфокусированный инпут доскролливается выше приподнятого футера. `nestedScrollEnabled`
|
|
31
|
+
* обязателен: на Android formSheet управляется `BottomSheetBehavior`, и без nested scrolling
|
|
32
|
+
* он забирает жест «палец вниз» себе — закрывает лист вместо скролла контента вверх.
|
|
33
|
+
*/
|
|
34
|
+
const useModalContainerProps = <
|
|
35
|
+
Props extends
|
|
36
|
+
| ModalContainerScrollProps
|
|
37
|
+
| ModalContainerListProps<any>
|
|
38
|
+
| ModalContainerSectionListProps<any>,
|
|
39
|
+
>({
|
|
40
|
+
// omit
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
style,
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
contentContainerStyle,
|
|
45
|
+
// native
|
|
46
|
+
header: initHeader,
|
|
47
|
+
footer: initFooter,
|
|
48
|
+
wrapperStyle,
|
|
49
|
+
outerStyle,
|
|
50
|
+
innerStyle,
|
|
51
|
+
...props
|
|
52
|
+
}: Props) => {
|
|
53
|
+
const {
|
|
54
|
+
styles,
|
|
55
|
+
headerGradientColorList,
|
|
56
|
+
footerGradientColorList,
|
|
57
|
+
MAX_SHEET_HEIGHT,
|
|
58
|
+
SHEET_KEYBOARD_TOP,
|
|
59
|
+
TOP,
|
|
60
|
+
BOTTOM,
|
|
61
|
+
} = useModalContainerStyles()
|
|
62
|
+
|
|
63
|
+
const withHeader = !!initHeader
|
|
64
|
+
const withFooter = !!initFooter
|
|
65
|
+
|
|
66
|
+
const [headerHeight, setHeaderHeight] = useState(0)
|
|
67
|
+
const [footerHeight, setFooterHeight] = useState(0)
|
|
68
|
+
const [contentHeight, setContentHeight] = useState(0)
|
|
69
|
+
|
|
70
|
+
const handleHeaderLayout = useCallback((event: LayoutChangeEvent) => {
|
|
71
|
+
setHeaderHeight(event.nativeEvent.layout.height)
|
|
72
|
+
}, [])
|
|
73
|
+
|
|
74
|
+
const handleFooterLayout = useCallback((event: LayoutChangeEvent) => {
|
|
75
|
+
setFooterHeight(event.nativeEvent.layout.height)
|
|
76
|
+
}, [])
|
|
77
|
+
|
|
78
|
+
const handleContentLayout = useCallback((event: LayoutChangeEvent) => {
|
|
79
|
+
setContentHeight(event.nativeEvent.layout.height)
|
|
80
|
+
}, [])
|
|
81
|
+
|
|
82
|
+
const handleContentSizeChange = useCallback((_width: number, height: number) => {
|
|
83
|
+
setContentHeight(height)
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
const isOverflow = contentHeight > 0 && contentHeight > MAX_SHEET_HEIGHT
|
|
87
|
+
const bodyHeight =
|
|
88
|
+
contentHeight > 0 ? Math.min(contentHeight, MAX_SHEET_HEIGHT) : MAX_SHEET_HEIGHT
|
|
89
|
+
|
|
90
|
+
const { height: windowHeight } = useWindowDimensions()
|
|
91
|
+
const { height: keyboardHeight } = useAnimatedKeyboard()
|
|
92
|
+
|
|
93
|
+
const footerLiftStyle = useAnimatedStyle(() => {
|
|
94
|
+
const overlap = Math.max(
|
|
95
|
+
0,
|
|
96
|
+
SHEET_KEYBOARD_TOP + bodyHeight + keyboardHeight.value - windowHeight
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return { transform: [{ translateY: -overlap }] }
|
|
100
|
+
}, [SHEET_KEYBOARD_TOP, bodyHeight, windowHeight])
|
|
101
|
+
|
|
102
|
+
const contentPadding = {
|
|
103
|
+
paddingTop: withHeader ? headerHeight : TOP,
|
|
104
|
+
paddingBottom: withFooter ? footerHeight : BOTTOM,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Первый ребёнок враппера — обрывает поиск скролла в react-native-screens: для formSheet
|
|
109
|
+
* RNS идёт по цепочке первых сабвью и найденному скроллу насильно ставит frame размером с
|
|
110
|
+
* нативный экран листа (выше видимой области), из-за чего низ контента невозможно
|
|
111
|
+
* доскроллить. Нулевая вью первой в цепочке прячет скролл от этой «коррекции», и его
|
|
112
|
+
* размером управляет обычный layout.
|
|
113
|
+
*/
|
|
114
|
+
const scrollGuard = <View style={styles.scrollGuard} collapsable={false} />
|
|
115
|
+
|
|
116
|
+
const header = useMemo(() => {
|
|
117
|
+
return (
|
|
118
|
+
initHeader && (
|
|
119
|
+
<View style={styles.header} collapsable={false} onLayout={handleHeaderLayout}>
|
|
120
|
+
<BlurProgress direction={'blurredTopClearBottom'} style={styles.blur} />
|
|
121
|
+
<LinearGradient colors={headerGradientColorList} style={styles.blur} />
|
|
122
|
+
{initHeader}
|
|
123
|
+
</View>
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
}, [styles.header, styles.blur, headerGradientColorList, initHeader, handleHeaderLayout])
|
|
127
|
+
|
|
128
|
+
const footer = useMemo(() => {
|
|
129
|
+
return (
|
|
130
|
+
initFooter && (
|
|
131
|
+
<Animated.View style={[styles.footerShell, footerLiftStyle]} collapsable={false}>
|
|
132
|
+
<View style={styles.footer} collapsable={false} onLayout={handleFooterLayout}>
|
|
133
|
+
<BlurProgress direction={'blurredBottomClearTop'} style={styles.blur} />
|
|
134
|
+
<LinearGradient colors={footerGradientColorList} style={styles.blur} />
|
|
135
|
+
{initFooter}
|
|
136
|
+
</View>
|
|
137
|
+
</Animated.View>
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
}, [
|
|
141
|
+
styles.footerShell,
|
|
142
|
+
styles.footer,
|
|
143
|
+
styles.blur,
|
|
144
|
+
footerGradientColorList,
|
|
145
|
+
footerLiftStyle,
|
|
146
|
+
initFooter,
|
|
147
|
+
handleFooterLayout,
|
|
148
|
+
])
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
...props,
|
|
152
|
+
header,
|
|
153
|
+
footer,
|
|
154
|
+
scrollGuard,
|
|
155
|
+
isOverflow,
|
|
156
|
+
bodyHeight,
|
|
157
|
+
bottomOffset: footerHeight + BOTTOM,
|
|
158
|
+
handleContentLayout,
|
|
159
|
+
handleContentSizeChange,
|
|
160
|
+
wrapperStyle: [styles.wrapper, isOverflow && styles.wrapperCapped, wrapperStyle],
|
|
161
|
+
bodyStyle: styles.body,
|
|
162
|
+
outerStyle,
|
|
163
|
+
innerStyle: [styles.content, contentPadding, innerStyle],
|
|
164
|
+
showsVerticalScrollIndicator: false,
|
|
165
|
+
showsHorizontalScrollIndicator: false,
|
|
166
|
+
nestedScrollEnabled: true,
|
|
167
|
+
keyboardDismissMode: 'interactive' as const,
|
|
168
|
+
keyboardShouldPersistTaps: 'handled' as const,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export { useModalContainerProps }
|