diff --git a/package.json b/package.json index 103e05c4..bc391b39 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@react-navigation/bottom-tabs": "^6.5.11", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-native-modal": "^13.0.1", "react-native-web": "^0.19.6" }, "resolutions": { diff --git a/packages/example/src/components/modals/Modal.tsx b/packages/example/src/components/modals/Modal.tsx new file mode 100644 index 00000000..23034bfb --- /dev/null +++ b/packages/example/src/components/modals/Modal.tsx @@ -0,0 +1,54 @@ +import styled from '@emotion/native'; +import React from 'react'; +import { View, ModalProps } from 'react-native'; +import { Typography } from '../../design-system/components/Typography'; +import { Spacer } from '../../design-system/components/Spacer'; +import { colors } from '../../design-system/theme/colors'; +import { SpatialNavigationOverlay } from './SpatialNavigationOverlay/SpatialNavigationOverlay'; + +type CustomModalProps = ModalProps & { + isModalVisible: boolean; + hideModal: () => void; + children: React.ReactNode; + title: string; +}; + +export const Modal = ({ isModalVisible, hideModal, children, title }: CustomModalProps) => { + if (!isModalVisible) return null; + + return ( + + + + {title} + + + + {children} + + + + ); +}; + +const StyledModal = styled(View)({ + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', +}); + +const ModalContentContainer = styled(View)({ + minHeight: 200, + minWidth: 200, + backgroundColor: colors.background.main, + borderWidth: 2, + borderColor: colors.primary.light, + padding: 32, + margin: 16, + borderRadius: 16, + justifyContent: 'center', +}); diff --git a/packages/example/src/components/modals/SpatialNavigationOverlay/SpatialNavigationOverlay.tsx b/packages/example/src/components/modals/SpatialNavigationOverlay/SpatialNavigationOverlay.tsx new file mode 100644 index 00000000..8587a363 --- /dev/null +++ b/packages/example/src/components/modals/SpatialNavigationOverlay/SpatialNavigationOverlay.tsx @@ -0,0 +1,18 @@ +import { SpatialNavigationRoot } from '../../../../../lib/src/spatial-navigation/components/Root'; +import { useLockOverlay } from './useLockOverlay'; + +type SpatialNavigationOverlayProps = { + isModalVisible: boolean; + hideModal: () => void; + children: React.ReactNode; +}; + +export const SpatialNavigationOverlay = ({ + isModalVisible, + hideModal, + children, +}: SpatialNavigationOverlayProps) => { + useLockOverlay({ isModalVisible, hideModal }); + + return {children}; +}; diff --git a/packages/example/src/components/modals/SpatialNavigationOverlay/useLockOverlay.tsx b/packages/example/src/components/modals/SpatialNavigationOverlay/useLockOverlay.tsx new file mode 100644 index 00000000..ae58104d --- /dev/null +++ b/packages/example/src/components/modals/SpatialNavigationOverlay/useLockOverlay.tsx @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; +import { useLockSpatialNavigation } from '../../../../../lib/src/spatial-navigation/context/LockSpatialNavigationContext'; +import { EventArg, useNavigation } from '@react-navigation/native'; + +interface UseLockProps { + isModalVisible: boolean; + hideModal: () => void; +} + +// This hook is used to lock the spatial navigation of parent navigator when a modal is open +// and to prevent the user from closing the modal by pressing the back button +export const useLockOverlay = ({ isModalVisible, hideModal }: UseLockProps) => { + useLockParentSpatialNavigator(isModalVisible); + usePreventNavigationGoBack(isModalVisible, hideModal); +}; + +const useLockParentSpatialNavigator = (isModalVisible: boolean) => { + const { lock, unlock } = useLockSpatialNavigation(); + useEffect(() => { + if (isModalVisible) { + lock(); + return () => { + unlock(); + }; + } + }, [isModalVisible, lock, unlock]); +}; + +const usePreventNavigationGoBack = (isModalVisible: boolean, hideModal: () => void) => { + const navigation = useNavigation(); + useEffect(() => { + if (isModalVisible) { + const navigationListener = (e: EventArg<'beforeRemove', true>) => { + e.preventDefault(); + hideModal(); + }; + navigation.addListener('beforeRemove', navigationListener); + return () => { + navigation.removeListener('beforeRemove', navigationListener); + }; + } + }, [navigation, isModalVisible, hideModal]); +}; diff --git a/packages/example/src/components/modals/SubtitlesModal.tsx b/packages/example/src/components/modals/SubtitlesModal.tsx new file mode 100644 index 00000000..859f424a --- /dev/null +++ b/packages/example/src/components/modals/SubtitlesModal.tsx @@ -0,0 +1,58 @@ +import { DefaultFocus } from '../../../../lib/src/spatial-navigation/context/DefaultFocusContext'; +import { Button } from '../../design-system/components/Button'; +import { Spacer } from '../../design-system/components/Spacer'; +import { Modal } from './Modal'; + +interface SubtitlesModalProps { + isModalVisible: boolean; + setIsModalVisible: (isVisible: boolean) => void; + setSubtitles: (subtitles: string) => void; +} + +export const SubtitlesModal = ({ + isModalVisible, + setIsModalVisible, + setSubtitles, +}: SubtitlesModalProps) => { + return ( + setIsModalVisible(false)} + title={'Choose subtitles'} + > + +