Skip to content

Commit

Permalink
chore(example): create modal in the example (#59)
Browse files Browse the repository at this point in the history
* Add modal with buttons

* Add ability to deactivate a page navigation

* Add subtitles modal in program details page

* use the useLockSpatialNavigation hook to deactivate parent navigator

* clean modal locking process as it was dirty

* correct dubious type declaration for GenericModal component

* remove isFirstRender as a much more simple solution exists

* add navigation event handling, preventing goBack on back key press

* extract hook to custom hook for the sake of clarity

* displace modal logic to Modal component

* clear useLockModal for better clarity

* change modal display to have better view

* separate modal display from spatial navigation part

* use hideModal instead of setIsModalVisible

* rename Modal to Overlay

* correct placement of Overlay in Modal
  • Loading branch information
thomasrebam committed Feb 8, 2024
1 parent bb98f64 commit 9588f16
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 1 deletion.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
54 changes: 54 additions & 0 deletions packages/example/src/components/modals/Modal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<StyledModal>
<ModalContentContainer>
<Typography variant="title" fontWeight="strong">
{title}
</Typography>
<Spacer gap="$8" />
<SpatialNavigationOverlay isModalVisible={isModalVisible} hideModal={hideModal}>
{children}
</SpatialNavigationOverlay>
</ModalContentContainer>
</StyledModal>
);
};

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',
});
Original file line number Diff line number Diff line change
@@ -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 <SpatialNavigationRoot>{children}</SpatialNavigationRoot>;
};
Original file line number Diff line number Diff line change
@@ -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]);
};
58 changes: 58 additions & 0 deletions packages/example/src/components/modals/SubtitlesModal.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Modal
isModalVisible={isModalVisible}
hideModal={() => setIsModalVisible(false)}
title={'Choose subtitles'}
>
<DefaultFocus>
<Button
label="English"
onSelect={() => {
setSubtitles('English');
setIsModalVisible(false);
}}
/>
</DefaultFocus>
<Spacer gap="$8" />
<Button
label="Spanish"
onSelect={() => {
setSubtitles('Spanish');
setIsModalVisible(false);
}}
/>
<Spacer gap="$8" />
<Button
label="Portuguese"
onSelect={() => {
setSubtitles('Portuguese');
setIsModalVisible(false);
}}
/>
<Spacer gap="$8" />
<Button
label="None"
onSelect={() => {
setSubtitles('No');
setIsModalVisible(false);
}}
/>
</Modal>
);
};
11 changes: 11 additions & 0 deletions packages/example/src/pages/ProgramDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<RootStackParamList, 'ProgramDetail'>;
}) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [subtitles, setSubtitles] = useState('No');
const { programInfo } = route.params;

return (
Expand All @@ -38,12 +42,19 @@ export const ProgramDetail = ({
<Spacer gap="$8" />
{/* eslint-disable-next-line no-console */}
<Button label="More info" onSelect={() => console.log('More info!')} />
<Spacer gap="$8" />
<Button label={`${subtitles} subtitles`} onSelect={() => setIsModalVisible(true)} />
</Box>
</DefaultFocus>
</Container>
<Spacer gap="$5" />
<ProgramListWithTitle title="You may also like..."></ProgramListWithTitle>
</Box>
<SubtitlesModal
isModalVisible={isModalVisible}
setIsModalVisible={setIsModalVisible}
setSubtitles={setSubtitles}
/>
</Page>
);
};
Expand Down
25 changes: 24 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 9588f16

Please sign in to comment.