diff --git a/packages/rn-tester/js/RNTesterAppShared.js b/packages/rn-tester/js/RNTesterAppShared.js index 336fc9d9839156..5eaffdc06cc65c 100644 --- a/packages/rn-tester/js/RNTesterAppShared.js +++ b/packages/rn-tester/js/RNTesterAppShared.js @@ -10,6 +10,7 @@ import type {RNTesterModuleInfo} from './types/RNTesterTypes'; +import {title as PlaygroundTitle} from './examples/Playground/PlaygroundExample'; import RNTesterModuleContainer from './components/RNTesterModuleContainer'; import RNTesterModuleList from './components/RNTesterModuleList'; import RNTesterNavBar, {navBarHeight} from './components/RNTesterNavbar'; @@ -36,6 +37,7 @@ import { useColorScheme, } from 'react-native'; import * as NativeComponentRegistry from 'react-native/Libraries/NativeComponent/NativeComponentRegistry'; +import type {ScreenTypes} from 'RNTesterTypes'; // In Bridgeless mode, in dev, enable static view config validator if (global.RN$Bridgeless === true && __DEV__) { @@ -127,11 +129,22 @@ const RNTesterApp = ({ ); const handleNavBarPress = React.useCallback( - (args: {screen: string}) => { - dispatch({ - type: RNTesterNavigationActionsType.NAVBAR_PRESS, - data: {screen: args.screen}, - }); + (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], ); diff --git a/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-dark.png b/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-dark.png new file mode 100644 index 00000000000000..b027c5abbb08c7 Binary files /dev/null and b/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-dark.png differ diff --git a/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-light.png b/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-light.png new file mode 100644 index 00000000000000..23377fb92997d9 Binary files /dev/null and b/packages/rn-tester/js/assets/bottom-nav-playgrounds-icon-light.png differ diff --git a/packages/rn-tester/js/components/RNTesterNavbar.js b/packages/rn-tester/js/components/RNTesterNavbar.js index 2f0621e4a342c6..610cd6c3e6eef6 100644 --- a/packages/rn-tester/js/components/RNTesterNavbar.js +++ b/packages/rn-tester/js/components/RNTesterNavbar.js @@ -9,11 +9,14 @@ */ import type {RNTesterTheme} from './RNTesterTheme'; +import type {ScreenTypes} from 'RNTesterTypes'; import {RNTesterThemeContext} from './RNTesterTheme'; import * as React from 'react'; import {Image, Pressable, StyleSheet, Text, View} from 'react-native'; +type NavBarOnPressHandler = ({screen: ScreenTypes}) => void; + /* $FlowFixMe[missing-local-annot] The type annotation(s) required by Flow's * LTI update could not be added via codemod */ const NavbarButton = ({ @@ -54,7 +57,7 @@ const ComponentTab = ({ handleNavBarPress, theme, }: $TEMPORARY$object<{ - handleNavBarPress: (data: {screen: string}) => void, + handleNavBarPress: NavBarOnPressHandler, isComponentActive: boolean, theme: RNTesterTheme, }>) => ( @@ -70,12 +73,33 @@ const ComponentTab = ({ /> ); +const PlaygroundTab = ({ + isComponentActive, + handleNavBarPress, + theme, +}: $TEMPORARY$object<{ + handleNavBarPress: NavBarOnPressHandler, + isComponentActive: boolean, + theme: RNTesterTheme, +}>) => ( + handleNavBarPress({screen: 'playgrounds'})} + activeImage={theme.NavBarPlaygroundActiveIcon} + inactiveImage={theme.NavBarPlaygroundInactiveIcon} + isActive={isComponentActive} + theme={theme} + iconStyle={styles.componentIcon} + /> +); + const APITab = ({ isAPIActive, handleNavBarPress, theme, }: $TEMPORARY$object<{ - handleNavBarPress: (data: {screen: string}) => void, + handleNavBarPress: NavBarOnPressHandler, isAPIActive: boolean, theme: RNTesterTheme, }>) => ( @@ -92,7 +116,7 @@ const APITab = ({ ); type Props = $ReadOnly<{| - handleNavBarPress: (data: {screen: string}) => void, + handleNavBarPress: NavBarOnPressHandler, screen: string, isExamplePageOpen: boolean, |}>; @@ -106,6 +130,7 @@ const RNTesterNavbar = ({ const isAPIActive = screen === 'apis' && !isExamplePageOpen; const isComponentActive = screen === 'components' && !isExamplePageOpen; + const isPlaygroundActive = screen === 'playgrounds'; return ( @@ -115,6 +140,11 @@ const RNTesterNavbar = ({ handleNavBarPress={handleNavBarPress} theme={theme} /> + = [Playground]; diff --git a/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js new file mode 100644 index 00000000000000..01613e09377899 --- /dev/null +++ b/packages/rn-tester/js/examples/Playground/RNTesterPlayground.js @@ -0,0 +1,35 @@ +/** + * 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 strict-local + * @format + * @oncall react_native + */ + +import type {RNTesterModuleExample} from '../../types/RNTesterTypes'; + +import * as React from 'react'; +import {StyleSheet, Text, View} from 'react-native'; + +function Playground() { + return ( + + Edit "RNTesterPlayground.js" to change this file + + ); +} + +const styles = StyleSheet.create({ + container: { + padding: 10, + }, +}); + +export default ({ + title: 'Playground', + name: 'playground', + render: (): React.Node => , +}: RNTesterModuleExample); diff --git a/packages/rn-tester/js/types/RNTesterTypes.js b/packages/rn-tester/js/types/RNTesterTypes.js index 53500cc1fa04f6..1e24f41da472c2 100644 --- a/packages/rn-tester/js/types/RNTesterTypes.js +++ b/packages/rn-tester/js/types/RNTesterTypes.js @@ -53,7 +53,7 @@ export type ExamplesList = $ReadOnly<{| apis: $ReadOnlyArray>, |}>; -export type ScreenTypes = 'components' | 'apis' | null; +export type ScreenTypes = 'components' | 'apis' | 'playgrounds' | null; export type ComponentList = null | {components: string[], apis: string[]}; diff --git a/packages/rn-tester/js/utils/RNTesterList.android.js b/packages/rn-tester/js/utils/RNTesterList.android.js index 9e649d6bfc562a..97736336ef42ae 100644 --- a/packages/rn-tester/js/utils/RNTesterList.android.js +++ b/packages/rn-tester/js/utils/RNTesterList.android.js @@ -11,6 +11,7 @@ 'use strict'; import type {RNTesterModuleInfo} from '../types/RNTesterTypes'; +import type {RNTesterModule} from 'RNTesterTypes'; import ReactNativeFeatureFlags from 'react-native/Libraries/ReactNative/ReactNativeFeatureFlags'; @@ -365,9 +366,16 @@ if (ReactNativeFeatureFlags.shouldEmitW3CPointerEvents()) { }); } -const Modules: any = {}; +const Playgrounds: Array = [ + { + key: 'PlaygroundExample', + module: require('../examples/Playground/PlaygroundExample'), + }, +]; + +const Modules: {[key: string]: RNTesterModule} = {}; -APIs.concat(Components).forEach(Example => { +[...APIs, ...Components, ...Playgrounds].forEach(Example => { Modules[Example.key] = Example.module; }); diff --git a/packages/rn-tester/js/utils/RNTesterList.ios.js b/packages/rn-tester/js/utils/RNTesterList.ios.js index b41392a69a5075..384b7d3a5c4aec 100644 --- a/packages/rn-tester/js/utils/RNTesterList.ios.js +++ b/packages/rn-tester/js/utils/RNTesterList.ios.js @@ -11,6 +11,7 @@ 'use strict'; import type {RNTesterModuleInfo} from '../types/RNTesterTypes'; +import type {RNTesterModule} from 'RNTesterTypes'; import ReactNativeFeatureFlags from 'react-native/Libraries/ReactNative/ReactNativeFeatureFlags'; @@ -345,10 +346,16 @@ if (ReactNativeFeatureFlags.shouldEmitW3CPointerEvents()) { }); } -const Modules: {...} = {}; +const Playgrounds: Array = [ + { + key: 'PlaygroundExample', + module: require('../examples/Playground/PlaygroundExample'), + }, +]; + +const Modules: {[key: string]: RNTesterModule} = {}; -APIs.concat(Components).forEach(Example => { - // $FlowFixMe[prop-missing] +[...APIs, ...Components, ...Playgrounds].forEach(Example => { Modules[Example.key] = Example.module; }); diff --git a/packages/rn-tester/js/utils/RNTesterList.js.flow b/packages/rn-tester/js/utils/RNTesterList.js.flow index 70d7ed59fac49d..f78df9d304e3bb 100644 --- a/packages/rn-tester/js/utils/RNTesterList.js.flow +++ b/packages/rn-tester/js/utils/RNTesterList.js.flow @@ -11,9 +11,10 @@ 'use strict'; import type {RNTesterModuleInfo} from '../types/RNTesterTypes'; +import type {RNTesterModule} from 'RNTesterTypes'; declare const APIs: Array; declare const Components: Array; -declare const Modules: {...}; +declare const Modules: {[key: string]: RNTesterModule}; module.exports = {APIs, Components, Modules}; diff --git a/packages/rn-tester/js/utils/RNTesterNavigationReducer.js b/packages/rn-tester/js/utils/RNTesterNavigationReducer.js index 4f0f5c335c01df..637ad0dd552ccd 100644 --- a/packages/rn-tester/js/utils/RNTesterNavigationReducer.js +++ b/packages/rn-tester/js/utils/RNTesterNavigationReducer.js @@ -19,6 +19,7 @@ export const RNTesterNavigationActionsType = { MODULE_CARD_PRESS: 'MODULE_CARD_PRESS', EXAMPLE_CARD_PRESS: 'EXAMPLE_CARD_PRESS', EXAMPLE_OPEN_URL_REQUEST: 'EXAMPLE_OPEN_URL_REQUEST', + NAVBAR_OPEN_MODULE_PRESS: 'NAVBAR_OPEN_MODULE_PRESS', }; const getUpdatedRecentlyUsed = ({ @@ -77,6 +78,16 @@ export const RNTesterNavigationReducer = ( hadDeepLink: false, }; + case RNTesterNavigationActionsType.NAVBAR_OPEN_MODULE_PRESS: + return { + ...state, + activeModuleKey: key, + activeModuleTitle: title, + activeModuleExampleKey: null, + screen, + hadDeepLink: true, + }; + case RNTesterNavigationActionsType.MODULE_CARD_PRESS: return { ...state, @@ -99,7 +110,6 @@ export const RNTesterNavigationReducer = ( case RNTesterNavigationActionsType.BACK_BUTTON_PRESS: // Go back to module or list. - // If there was a deeplink navigation, pressing Back should bring us back to the root. return { ...state, activeModuleExampleKey: null, @@ -112,6 +122,8 @@ export const RNTesterNavigationReducer = ( ? state.activeModuleTitle : null, hadDeepLink: false, + // If there was a deeplink navigation, pressing Back should bring us back to the root. + screen: state.hadDeepLink ? 'components' : state.screen, }; case RNTesterNavigationActionsType.EXAMPLE_OPEN_URL_REQUEST: @@ -121,6 +133,7 @@ export const RNTesterNavigationReducer = ( activeModuleTitle: title, activeModuleExampleKey: exampleKey, hadDeepLink: true, + screen: 'components', }; default: diff --git a/packages/rn-tester/js/utils/testerStateUtils.js b/packages/rn-tester/js/utils/testerStateUtils.js index 596af29acfebd3..df8137bc58e50a 100644 --- a/packages/rn-tester/js/utils/testerStateUtils.js +++ b/packages/rn-tester/js/utils/testerStateUtils.js @@ -21,6 +21,7 @@ import RNTesterList from './RNTesterList'; export const Screens = { COMPONENTS: 'components', APIS: 'apis', + PLAYGROUNDS: 'playgrounds', }; export const initialNavigationState: RNTesterNavigationState = {