feat(rn-tester): Improve the Modal tester (#38977)

Summary:
## Summary:

The modal tester in the sandbox was lacking a bit of functionality that the Modal component exposes -- especially on Android. This change revamps the Modal page to more-closely resemble the API documentation by exposing all of the options, and annotating which ones are reserved for the different platforms.

Additionally, this change puts the modal controls into the created modal itself. This allows the user to more-easily test what happens if the props change during the lifespan of the modal (related PR: https://github.com/facebook/react-native/issues/38947).

 ## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests -->

[INTERNAL] [CHANGED] - Revamp Modal tester in rn-tester

Pull Request resolved: https://github.com/facebook/react-native/pull/38977

Test Plan:
| Control page | Modal (with `transparent=true` on Android) |
|:--:|:--:|
| ![image](https://github.com/facebook/react-native/assets/418560/ef49cb94-14d8-4ebe-ade5-699b8e67ba5c) | ![image](https://github.com/facebook/react-native/assets/418560/d4bb6ef5-7d8c-4d5f-b4d8-37b8d736b19a) |

Reviewed By: yungsters

Differential Revision: D48414351

Pulled By: NickGerleman

fbshipit-source-id: 54bece639f4c64132dfb21c54c91d46972f5a335
This commit is contained in:
Evan Charlton
2023-08-17 23:58:28 -07:00
committed by Facebook GitHub Bot
parent 3eeee11d7a
commit 04fb0122b2
2 changed files with 201 additions and 152 deletions
@@ -42,7 +42,7 @@ export default function RNTOption(props: Props): React.Node {
: props.selected
}
hitSlop={4}
onPress={props.onPress}
onPress={props.disabled === true ? undefined : props.onPress}
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
testID={props.testID}>
@@ -13,18 +13,10 @@
import * as React from 'react';
import {Modal, Platform, StyleSheet, Switch, Text, View} from 'react-native';
import type {RNTesterModuleExample} from '../../types/RNTesterTypes';
import type {Props as ModalProps} from 'react-native/Libraries/Modal/Modal';
import RNTOption from '../../components/RNTOption';
const RNTesterButton = require('../../components/RNTesterButton');
const supportedOrientations = {
Portrait: ['portrait'],
Landscape: ['landscape'],
'Landscape Left': ['landscape-left'],
'Portrait and Landscape Right': ['portrait', 'landscape-right'],
'Portrait and Landscape': ['portrait', 'landscape'],
Default: [],
};
const animationTypes = ['slide', 'none', 'fade'];
const presentationStyles = [
'fullScreen',
@@ -32,76 +24,220 @@ const presentationStyles = [
'formSheet',
'overFullScreen',
];
const iOSActions = ['None', 'On Dismiss', 'On Show'];
const noniOSActions = ['None', 'On Show'];
const supportedOrientations = [
'portrait',
'portrait-upside-down',
'landscape',
'landscape-left',
'landscape-right',
];
function ModalPresentation() {
const [animationType, setAnimationType] = React.useState('none');
const [transparent, setTransparent] = React.useState(false);
const [visible, setVisible] = React.useState(false);
const [hardwareAccelerated, setHardwareAccelerated] = React.useState(false);
const [statusBarTranslucent, setStatusBarTranslucent] = React.useState(false);
const [presentationStyle, setPresentationStyle] =
React.useState('fullScreen');
const [supportedOrientationKey, setSupportedOrientationKey] =
React.useState('Portrait');
const onDismiss = React.useCallback(() => {
alert('onDismiss');
}, []);
const onShow = React.useCallback(() => {
alert('onShow');
}, []);
const onRequestClose = React.useCallback(() => {
console.log('onRequestClose');
}, []);
const [props, setProps] = React.useState<ModalProps>({
animationType: 'none',
transparent: false,
hardwareAccelerated: false,
statusBarTranslucent: false,
presentationStyle: Platform.select({
ios: 'fullScreen',
default: undefined,
}),
supportedOrientations: Platform.select({
ios: ['portrait'],
default: undefined,
}),
onDismiss: undefined,
onShow: undefined,
visible: false,
});
const presentationStyle = props.presentationStyle;
const hardwareAccelerated = props.hardwareAccelerated;
const statusBarTranslucent = props.statusBarTranslucent;
const [currentOrientation, setCurrentOrientation] = React.useState('unknown');
const [action, setAction] = React.useState('None');
const actions = Platform.OS === 'ios' ? iOSActions : noniOSActions;
const onDismiss = () => {
setVisible(false);
if (action === 'onDismiss') {
alert('onDismiss');
}
};
const onShow = () => {
if (action === 'onShow') {
alert('onShow');
}
};
/* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's
* LTI update could not be added via codemod */
const onOrientationChange = event =>
setCurrentOrientation(event.nativeEvent.orientation);
const modalBackgroundStyle = {
backgroundColor: transparent ? 'rgba(0, 0, 0, 0.5)' : '#f5fcff',
};
const innerContainerTransparentStyle = transparent
? {backgroundColor: '#fff', padding: 20}
: null;
const controls = (
<>
<View style={styles.inlineBlock}>
<Text style={styles.title}>Status Bar Translucent 🟢</Text>
<Switch
value={statusBarTranslucent}
onValueChange={enabled =>
setProps(prev => ({...prev, statusBarTranslucent: enabled}))
}
/>
</View>
<View style={styles.inlineBlock}>
<Text style={styles.title}>Hardware Acceleration 🟢</Text>
<Switch
value={hardwareAccelerated}
onValueChange={enabled =>
setProps(prev => ({
...prev,
hardwareAccelerated: enabled,
}))
}
/>
</View>
<View style={styles.block}>
<Text style={styles.title}>Presentation Style </Text>
<View style={styles.row}>
{presentationStyles.map(type => (
<RNTOption
key={type}
disabled={Platform.OS !== 'ios'}
style={styles.option}
label={type}
multiSelect={true}
onPress={() =>
setProps(prev => {
if (type === 'overFullScreen' && prev.transparent === true) {
return {
...prev,
presentationStyle: type,
transparent: false,
};
}
return {
...prev,
presentationStyle:
type === prev.presentationStyle ? undefined : type,
};
})
}
selected={type === presentationStyle}
/>
))}
</View>
</View>
<View style={styles.block}>
<View style={styles.rowWithSpaceBetween}>
<Text style={styles.title}>Transparent</Text>
<Switch
value={props.transparent}
onValueChange={enabled =>
setProps(prev => ({...prev, transparent: enabled}))
}
/>
</View>
{Platform.OS === 'ios' && presentationStyle !== 'overFullScreen' ? (
<Text style={styles.warning}>
iOS Modal can only be transparent with 'overFullScreen' Presentation
Style
</Text>
) : null}
</View>
<View style={styles.block}>
<Text style={styles.title}>Supported Orientation </Text>
<View style={styles.row}>
{supportedOrientations.map(orientation => (
<RNTOption
key={orientation}
disabled={Platform.OS !== 'ios'}
style={styles.option}
label={orientation}
multiSelect={true}
onPress={() =>
setProps(prev => {
if (prev.supportedOrientations?.includes(orientation)) {
return {
...prev,
supportedOrientations: prev.supportedOrientations?.filter(
o => o !== orientation,
),
};
}
return {
...prev,
supportedOrientations: [
...(prev.supportedOrientations ?? []),
orientation,
],
};
})
}
selected={props.supportedOrientations?.includes(orientation)}
/>
))}
</View>
</View>
<View style={styles.block}>
<Text style={styles.title}>Actions</Text>
<View style={styles.row}>
<RNTOption
key="onShow"
style={styles.option}
label="onShow"
multiSelect={true}
onPress={() =>
setProps(prev => ({
...prev,
onShow: prev.onShow ? undefined : onShow,
}))
}
selected={!!props.onShow}
/>
<RNTOption
key="onDismiss"
style={styles.option}
label="onDismiss ⚫️"
disabled={Platform.OS !== 'ios'}
onPress={() =>
setProps(prev => ({
...prev,
onDismiss: prev.onDismiss ? undefined : onDismiss,
}))
}
selected={!!props.onDismiss}
/>
</View>
</View>
</>
);
return (
<View>
<RNTesterButton onPress={() => setVisible(true)}>
<RNTesterButton
onPress={() => setProps(prev => ({...prev, visible: true}))}>
Show Modal
</RNTesterButton>
<Modal
animationType={animationType}
presentationStyle={presentationStyle}
transparent={transparent}
hardwareAccelerated={hardwareAccelerated}
statusBarTranslucent={statusBarTranslucent}
visible={visible}
onRequestClose={onDismiss}
supportedOrientations={supportedOrientations[supportedOrientationKey]}
onOrientationChange={onOrientationChange}
onDismiss={onDismiss}
onShow={onShow}>
<View style={[styles.modalContainer, modalBackgroundStyle]}>
<View
style={[
styles.modalInnerContainer,
innerContainerTransparentStyle,
]}>
{...props}
onRequestClose={onRequestClose}
onOrientationChange={onOrientationChange}>
<View style={styles.modalContainer}>
<View style={styles.modalInnerContainer}>
<Text>
This modal was presented with animationType: '{animationType}'
This modal was presented with animationType: '
{props.animationType}'
</Text>
{Platform.OS === 'ios' ? (
<Text>
It is currently displayed in {currentOrientation} mode.
</Text>
) : null}
<RNTesterButton onPress={onDismiss}>Close</RNTesterButton>
<RNTesterButton
onPress={() => setProps(prev => ({...prev, visible: false}))}>
Close
</RNTesterButton>
{controls}
</View>
</View>
</Modal>
@@ -113,101 +249,13 @@ function ModalPresentation() {
key={type}
style={styles.option}
label={type}
onPress={() => setAnimationType(type)}
selected={type === animationType}
onPress={() => setProps(prev => ({...prev, animationType: type}))}
selected={type === props.animationType}
/>
))}
</View>
</View>
{Platform.OS === 'android' && Platform.isTV !== true ? (
<>
<View style={styles.inlineBlock}>
<Text style={styles.title}>Status Bar Translucent</Text>
<Switch
value={statusBarTranslucent}
onValueChange={() =>
setStatusBarTranslucent(!statusBarTranslucent)
}
/>
</View>
<View style={styles.inlineBlock}>
<Text style={styles.title}>Hardware Acceleration</Text>
<Switch
value={hardwareAccelerated}
onValueChange={() => setHardwareAccelerated(!hardwareAccelerated)}
/>
</View>
</>
) : null}
{Platform.isTV !== true ? (
<>
{Platform.OS === 'ios' ? (
<View style={styles.block}>
<Text style={styles.title}>Presentation Style</Text>
<View style={styles.row}>
{presentationStyles.map(type => (
<RNTOption
key={type}
style={styles.option}
label={type}
onPress={() => {
if (type !== 'overFullScreen' && transparent) {
setTransparent(false);
}
setPresentationStyle(type);
}}
selected={type === presentationStyle}
/>
))}
</View>
</View>
) : null}
<View style={styles.block}>
<View style={styles.rowWithSpaceBetween}>
<Text style={styles.title}>Transparent</Text>
<Switch
value={transparent}
disabled={presentationStyle !== 'overFullScreen'}
onValueChange={() => setTransparent(!transparent)}
/>
</View>
{Platform.OS === 'ios' && presentationStyle !== 'overFullScreen' ? (
<Text style={styles.warning}>
iOS Modal can only be transparent with 'overFullScreen'
Presentation Style
</Text>
) : null}
</View>
<View style={styles.block}>
<Text style={styles.title}>Supported Orientation</Text>
<View style={styles.row}>
{Object.keys(supportedOrientations).map(label => (
<RNTOption
key={label}
style={styles.option}
label={label}
onPress={() => setSupportedOrientationKey(label)}
selected={label === supportedOrientationKey}
/>
))}
</View>
</View>
<View style={styles.block}>
<Text style={styles.title}>Actions</Text>
<View style={styles.row}>
{actions.map(value => (
<RNTOption
key={value}
style={styles.option}
label={value}
onPress={() => setAction(value)}
selected={value === action}
/>
))}
</View>
</View>
</>
) : null}
{controls}
</View>
);
}
@@ -249,7 +297,8 @@ const styles = StyleSheet.create({
},
modalInnerContainer: {
borderRadius: 10,
alignItems: 'center',
backgroundColor: '#fff',
padding: 10,
},
warning: {
margin: 3,