mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
f936780cd5
Summary: Resolves https://github.com/react-native-community/discussions-and-proposals/discussions/893#discussioncomment-13449739 Expose NativeComponentRegistry as a root export on index.js. ## Changelog: [General][Added] - Expose NativeComponentRegistry API as JavaScript root export Pull Request resolved: https://github.com/facebook/react-native/pull/52999 Test Plan: Can import NativeComponentRegistry from root exort Reviewed By: cortinico, cipolleschi Differential Revision: D79721243 Pulled By: huntie fbshipit-source-id: 77d94fb22255de020009ffe0e54d5030213519e2
339 lines
9.5 KiB
JavaScript
339 lines
9.5 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @format
|
|
*/
|
|
|
|
import type {RNTesterModuleInfo, ScreenTypes} from './types/RNTesterTypes';
|
|
|
|
import ReportFullyDrawnView from '../ReportFullyDrawnView/ReportFullyDrawnView';
|
|
import RNTesterModuleContainer from './components/RNTesterModuleContainer';
|
|
import RNTesterModuleList from './components/RNTesterModuleList';
|
|
import RNTesterNavBar, {navBarHeight} from './components/RNTesterNavbar';
|
|
import {RNTesterThemeContext, themes} from './components/RNTesterTheme';
|
|
import RNTTitleBar from './components/RNTTitleBar';
|
|
import {title as PlaygroundTitle} from './examples/Playground/PlaygroundExample';
|
|
import RNTesterList from './utils/RNTesterList';
|
|
import {
|
|
RNTesterNavigationActionsType,
|
|
RNTesterNavigationReducer,
|
|
} from './utils/RNTesterNavigationReducer';
|
|
import {
|
|
Screens,
|
|
getExamplesListWithRecentlyUsed,
|
|
initialNavigationState,
|
|
} from './utils/testerStateUtils';
|
|
import * as React from 'react';
|
|
import {useCallback, useEffect, useMemo, useReducer} from 'react';
|
|
import {
|
|
BackHandler,
|
|
Button,
|
|
Linking,
|
|
NativeComponentRegistry,
|
|
Platform,
|
|
StatusBar,
|
|
StyleSheet,
|
|
View,
|
|
useColorScheme,
|
|
useWindowDimensions,
|
|
} from 'react-native';
|
|
|
|
// In Bridgeless mode, in dev, enable static view config validator
|
|
if (global.RN$Bridgeless === true && __DEV__) {
|
|
NativeComponentRegistry.setRuntimeConfigProvider(() => {
|
|
return {
|
|
native: false,
|
|
verify: true,
|
|
};
|
|
});
|
|
}
|
|
|
|
// RNTester App currently uses in memory storage for storing navigation state
|
|
|
|
type BackButton = ({onBack: () => void}) => React.Node;
|
|
|
|
const RNTesterApp = ({
|
|
testList,
|
|
customBackButton,
|
|
}: {
|
|
testList?: {
|
|
components?: Array<RNTesterModuleInfo>,
|
|
apis?: Array<RNTesterModuleInfo>,
|
|
},
|
|
customBackButton?: BackButton,
|
|
}): React.Node => {
|
|
const [state, dispatch] = useReducer(
|
|
RNTesterNavigationReducer,
|
|
initialNavigationState,
|
|
);
|
|
const colorScheme = useColorScheme();
|
|
|
|
const {
|
|
activeModuleKey,
|
|
activeModuleTitle,
|
|
activeModuleExampleKey,
|
|
screen,
|
|
recentlyUsed,
|
|
hadDeepLink,
|
|
} = state;
|
|
|
|
const isScreenTiny = useWindowDimensions().height < 600;
|
|
|
|
const examplesList = useMemo(
|
|
() => getExamplesListWithRecentlyUsed({recentlyUsed, testList}),
|
|
[recentlyUsed, testList],
|
|
);
|
|
|
|
const handleBackPress = useCallback(() => {
|
|
if (activeModuleKey != null) {
|
|
dispatch({type: RNTesterNavigationActionsType.BACK_BUTTON_PRESS});
|
|
}
|
|
}, [dispatch, activeModuleKey]);
|
|
|
|
// Setup hardware back button press listener
|
|
useEffect(() => {
|
|
const handleHardwareBackPress = () => {
|
|
if (activeModuleKey) {
|
|
handleBackPress();
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
const subscription = BackHandler.addEventListener(
|
|
'hardwareBackPress',
|
|
handleHardwareBackPress,
|
|
);
|
|
return () => subscription.remove();
|
|
}, [activeModuleKey, handleBackPress]);
|
|
|
|
const handleModuleCardPress = useCallback(
|
|
({exampleType, key, title}: any) => {
|
|
dispatch({
|
|
type: RNTesterNavigationActionsType.MODULE_CARD_PRESS,
|
|
data: {exampleType, key, title},
|
|
});
|
|
},
|
|
[dispatch],
|
|
);
|
|
|
|
const handleModuleExampleCardPress = useCallback(
|
|
(exampleName: string) => {
|
|
dispatch({
|
|
type: RNTesterNavigationActionsType.EXAMPLE_CARD_PRESS,
|
|
data: {key: exampleName},
|
|
});
|
|
},
|
|
[dispatch],
|
|
);
|
|
|
|
const handleNavBarPress = useCallback(
|
|
(args: {screen: ScreenTypes}) => {
|
|
if (args.screen === 'playgrounds') {
|
|
dispatch({
|
|
type: RNTesterNavigationActionsType.NAVBAR_OPEN_MODULE_PRESS,
|
|
data: {
|
|
key: 'PlaygroundExample',
|
|
title: PlaygroundTitle,
|
|
screen: args.screen,
|
|
},
|
|
});
|
|
} else {
|
|
dispatch({
|
|
type: RNTesterNavigationActionsType.NAVBAR_PRESS,
|
|
data: {screen: args.screen},
|
|
});
|
|
}
|
|
},
|
|
[dispatch],
|
|
);
|
|
|
|
// Setup Linking event subscription
|
|
const handleOpenUrlRequest = useCallback(
|
|
({url}: {url: string, ...}) => {
|
|
// Supported URL pattern(s):
|
|
// * rntester://example/<moduleKey>
|
|
// * rntester://example/<moduleKey>/<exampleKey>
|
|
const match =
|
|
/^rntester(-legacy)?:\/\/example\/([a-zA-Z0-9_-]+)(?:\/([a-zA-Z0-9_-]+))?$/.exec(
|
|
url,
|
|
);
|
|
if (!match) {
|
|
console.warn(
|
|
`handleOpenUrlRequest: Received unsupported URL: '${url}'`,
|
|
);
|
|
return;
|
|
}
|
|
|
|
const rawModuleKey = match[2];
|
|
const exampleKey = match[3];
|
|
|
|
// For tooling compatibility, allow all these variants for each module key:
|
|
const validModuleKeys = [
|
|
rawModuleKey,
|
|
`${rawModuleKey}Index`,
|
|
`${rawModuleKey}Example`,
|
|
// $FlowFixMe[invalid-computed-prop]
|
|
].filter(k => RNTesterList.Modules[k] != null);
|
|
if (validModuleKeys.length !== 1) {
|
|
if (validModuleKeys.length === 0) {
|
|
console.error(
|
|
`handleOpenUrlRequest: Unable to find requested module with key: '${rawModuleKey}'`,
|
|
);
|
|
} else {
|
|
console.error(
|
|
`handleOpenUrlRequest: Found multiple matching module with key: '${rawModuleKey}', unable to resolve`,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
const resolvedModuleKey = validModuleKeys[0];
|
|
// $FlowFixMe[invalid-computed-prop]
|
|
const exampleModule = RNTesterList.Modules[resolvedModuleKey];
|
|
|
|
if (exampleKey != null) {
|
|
const validExampleKeys = exampleModule.examples.filter(
|
|
e => e.name === exampleKey,
|
|
);
|
|
if (validExampleKeys.length !== 1) {
|
|
if (validExampleKeys.length === 0) {
|
|
console.error(
|
|
`handleOpenUrlRequest: Unable to find requested example with key: '${exampleKey}' within module: '${resolvedModuleKey}'`,
|
|
);
|
|
} else {
|
|
console.error(
|
|
`handleOpenUrlRequest: Found multiple matching example with key: '${exampleKey}' within module: '${resolvedModuleKey}', unable to resolve`,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`handleOpenUrlRequest: Opening module: '${resolvedModuleKey}', example: '${
|
|
exampleKey || 'null'
|
|
}'`,
|
|
);
|
|
|
|
dispatch({
|
|
type: RNTesterNavigationActionsType.EXAMPLE_OPEN_URL_REQUEST,
|
|
data: {
|
|
key: resolvedModuleKey,
|
|
title: exampleModule.title || resolvedModuleKey,
|
|
exampleKey,
|
|
},
|
|
});
|
|
},
|
|
[dispatch],
|
|
);
|
|
useEffect(() => {
|
|
// Initial deeplink
|
|
Linking.getInitialURL()
|
|
.then(url => url != null && handleOpenUrlRequest({url: url}))
|
|
.catch(_ => {});
|
|
const subscription = Linking.addEventListener('url', handleOpenUrlRequest);
|
|
return () => subscription.remove();
|
|
}, [handleOpenUrlRequest]);
|
|
|
|
const theme = colorScheme === 'dark' ? themes.dark : themes.light;
|
|
|
|
if (examplesList === null) {
|
|
return null;
|
|
}
|
|
|
|
const activeModule =
|
|
// $FlowFixMe[invalid-computed-prop]
|
|
activeModuleKey != null ? RNTesterList.Modules[activeModuleKey] : null;
|
|
const activeModuleExample =
|
|
activeModuleExampleKey != null
|
|
? activeModule?.examples.find(e => e.name === activeModuleExampleKey)
|
|
: null;
|
|
const title =
|
|
activeModuleTitle != null
|
|
? activeModuleTitle
|
|
: screen === Screens.COMPONENTS
|
|
? 'Components'
|
|
: 'APIs';
|
|
|
|
const BackButtonComponent: ?BackButton = customBackButton
|
|
? customBackButton
|
|
: Platform.OS === 'ios'
|
|
? ({onBack}) => (
|
|
<Button title="Back" onPress={onBack} color={theme.LinkColor} />
|
|
)
|
|
: null;
|
|
|
|
const activeExampleList =
|
|
screen === Screens.COMPONENTS ? examplesList.components : examplesList.apis;
|
|
|
|
// Hide chrome if we don't have much screen space and are showing UI for tests
|
|
const shouldHideChrome = isScreenTiny && hadDeepLink;
|
|
|
|
return (
|
|
<RNTesterThemeContext.Provider value={theme}>
|
|
{Platform.OS === 'android' ? (
|
|
<StatusBar
|
|
barStyle="dark-content"
|
|
backgroundColor={theme.GroupedBackgroundColor}
|
|
/>
|
|
) : null}
|
|
{!shouldHideChrome && (
|
|
<RNTTitleBar
|
|
title={title}
|
|
theme={theme}
|
|
documentationURL={activeModule?.documentationURL}>
|
|
{activeModule && BackButtonComponent ? (
|
|
<BackButtonComponent onBack={handleBackPress} />
|
|
) : undefined}
|
|
</RNTTitleBar>
|
|
)}
|
|
<View
|
|
style={StyleSheet.compose(styles.container, {
|
|
backgroundColor: theme.GroupedBackgroundColor,
|
|
})}>
|
|
{activeModule != null ? (
|
|
<RNTesterModuleContainer
|
|
module={activeModule}
|
|
example={activeModuleExample}
|
|
onExampleCardPress={handleModuleExampleCardPress}
|
|
/>
|
|
) : (
|
|
<RNTesterModuleList
|
|
sections={activeExampleList}
|
|
handleModuleCardPress={handleModuleCardPress}
|
|
/>
|
|
)}
|
|
</View>
|
|
{!shouldHideChrome && (
|
|
<View style={styles.bottomNavbar}>
|
|
<RNTesterNavBar
|
|
screen={screen || Screens.COMPONENTS}
|
|
isExamplePageOpen={!!activeModule}
|
|
handleNavBarPress={handleNavBarPress}
|
|
/>
|
|
</View>
|
|
)}
|
|
<ReportFullyDrawnView />
|
|
</RNTesterThemeContext.Provider>
|
|
);
|
|
};
|
|
|
|
export default RNTesterApp;
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
},
|
|
bottomNavbar: {
|
|
height: navBarHeight,
|
|
},
|
|
hidden: {
|
|
display: 'none',
|
|
},
|
|
});
|