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'}
+ >
+
+
+
+
+ );
+};
diff --git a/packages/example/src/pages/ProgramDetail.tsx b/packages/example/src/pages/ProgramDetail.tsx
index 95fe4105..eb5aa48d 100644
--- a/packages/example/src/pages/ProgramDetail.tsx
+++ b/packages/example/src/pages/ProgramDetail.tsx
@@ -8,12 +8,16 @@ import { Spacer } from '../design-system/components/Spacer';
import { Typography } from '../design-system/components/Typography';
import { ProgramListWithTitle } from '../modules/program/view/ProgramListWithTitle';
import { Button } from '../design-system/components/Button';
+import { useState } from 'react';
+import { SubtitlesModal } from '../components/modals/SubtitlesModal';
export const ProgramDetail = ({
route,
}: {
route: RouteProp;
}) => {
+ const [isModalVisible, setIsModalVisible] = useState(false);
+ const [subtitles, setSubtitles] = useState('No');
const { programInfo } = route.params;
return (
@@ -38,12 +42,19 @@ export const ProgramDetail = ({
{/* eslint-disable-next-line no-console */}
console.log('More info!')} />
+
+ setIsModalVisible(true)} />
+
);
};
diff --git a/yarn.lock b/yarn.lock
index e2a4d7d9..edac257c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -14156,7 +14156,7 @@ __metadata:
languageName: node
linkType: hard
-"prop-types@npm:^15.8.1":
+"prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
@@ -14366,6 +14366,15 @@ __metadata:
languageName: node
linkType: hard
+"react-native-animatable@npm:1.3.3":
+ version: 1.3.3
+ resolution: "react-native-animatable@npm:1.3.3"
+ dependencies:
+ prop-types: ^15.7.2
+ checksum: eb35821de36ae4eb00f97d99fc22ed79bd017248092ff1299f9d46a7ab946de16d2e6d6bfbf4cab5ef77d0d3bf47684aec761a7670ec7d36aae8d77ab8ddd171
+ languageName: node
+ linkType: hard
+
"react-native-keyevent-expo-config-plugin@npm:^1.0.49":
version: 1.0.49
resolution: "react-native-keyevent-expo-config-plugin@npm:1.0.49"
@@ -14386,6 +14395,19 @@ __metadata:
languageName: node
linkType: hard
+"react-native-modal@npm:^13.0.1":
+ version: 13.0.1
+ resolution: "react-native-modal@npm:13.0.1"
+ dependencies:
+ prop-types: ^15.6.2
+ react-native-animatable: 1.3.3
+ peerDependencies:
+ react: "*"
+ react-native: ">=0.65.0"
+ checksum: 15985fd6aaae7a2134ec1003c63abd384f4a17beabd0a80b74033c98eccee29161aa07150f479373fb43106cfb33b5837b7a4f1d2b721ff03b3727490c830a07
+ languageName: node
+ linkType: hard
+
"react-native-safe-area-context@npm:^4.8.2":
version: 4.8.2
resolution: "react-native-safe-area-context@npm:4.8.2"
@@ -14550,6 +14572,7 @@ __metadata:
prettier: ^2.8.8
react: ^18.2.0
react-dom: ^18.2.0
+ react-native-modal: ^13.0.1
react-native-web: ^0.19.6
style-loader: ^3.3.3
ts-loader: ^9.4.4