Skip to content

Commit 7486060

Browse files
committed
feat: add dev menu item for Storybook entry point
1 parent 1346e5c commit 7486060

File tree

11 files changed

+200
-4
lines changed

11 files changed

+200
-4
lines changed

App.tsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,27 @@
1-
import StorybookUIRoot from './.ondevice/Storybook';
2-
export {StorybookUIRoot as default};
1+
import React from 'react';
2+
import {View, Text, SafeAreaView, StyleSheet} from 'react-native';
3+
import {SafeAreaProvider} from 'react-native-safe-area-context';
4+
import {MyButton} from './components/Button/Button';
5+
6+
export default () => (
7+
<SafeAreaProvider>
8+
<SafeAreaView style={styles.safeArea}>
9+
<View style={styles.container}>
10+
<Text>Application</Text>
11+
<MyButton text="Hello world!" color="purple" onPress={() => {}} />
12+
</View>
13+
</SafeAreaView>
14+
</SafeAreaProvider>
15+
);
16+
17+
const styles = StyleSheet.create({
18+
safeArea: {
19+
flex: 1,
20+
},
21+
container: {
22+
flex: 1,
23+
paddingTop: 16,
24+
gap: 16,
25+
alignItems: 'center',
26+
},
27+
});

android/app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ dependencies {
165165
} else {
166166
implementation jscFlavor
167167
}
168+
169+
// for storybook dev menu
170+
debugImplementation 'com.jakewharton:process-phoenix:2.1.2'
168171
}
169172

170173
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

android/app/src/main/java/com/react_native_storybook_starter/MainActivity.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
package com.react_native_storybook_starter;
22

3+
import android.os.Bundle;
4+
5+
import com.facebook.react.BuildConfig;
36
import com.facebook.react.ReactActivity;
47
import com.facebook.react.ReactActivityDelegate;
58
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
69
import com.facebook.react.defaults.DefaultReactActivityDelegate;
710

811
public class MainActivity extends ReactActivity {
912

13+
@Override
14+
protected void onCreate(Bundle savedInstanceState) {
15+
// null is for react-native-screens
16+
super.onCreate(null);
17+
18+
if (BuildConfig.DEBUG) {
19+
StorybookDevMenuOptionHandler.initDevMenuItem(
20+
getApplicationContext(),
21+
getReactInstanceManager().getDevSupportManager()
22+
);
23+
}
24+
}
25+
1026
/**
1127
* Returns the name of the main component registered from JavaScript. This is used to schedule
1228
* rendering of the component.

android/app/src/main/java/com/react_native_storybook_starter/MainApplication.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ protected List<ReactPackage> getPackages() {
3030

3131
@Override
3232
protected String getJSMainModuleName() {
33-
return "index";
33+
if (BuildConfig.DEBUG) {
34+
return StorybookDevMenuPreferencesService.isStorybookEnabled(getApplicationContext()) ? "index.storybook" : "index";
35+
} else {
36+
return "index";
37+
}
3438
}
3539

3640
@Override
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.react_native_storybook_starter;
2+
3+
import android.content.Context;
4+
5+
import com.facebook.react.BuildConfig;
6+
import com.facebook.react.devsupport.interfaces.DevOptionHandler;
7+
import com.facebook.react.devsupport.interfaces.DevSupportManager;
8+
9+
import java.lang.reflect.InvocationTargetException;
10+
import java.lang.reflect.Method;
11+
12+
public class StorybookDevMenuOptionHandler implements DevOptionHandler {
13+
14+
private final Context mContext;
15+
16+
public static void initDevMenuItem(Context context, DevSupportManager devSupportManager) {
17+
devSupportManager.addCustomDevOption(
18+
"Toggle Storybook",
19+
new StorybookDevMenuOptionHandler(context)
20+
);
21+
}
22+
23+
public StorybookDevMenuOptionHandler(Context context) {
24+
mContext = context;
25+
}
26+
27+
@Override
28+
public void onOptionSelected() {
29+
StorybookDevMenuPreferencesService.toggleStorybookFlag(mContext);
30+
if (BuildConfig.DEBUG) {
31+
try {
32+
// ProcessPhoenix.triggerRebirth(mContext);
33+
// calling it using reflection, to include the package only to debug build
34+
Class<?> processPhoenixClass = Class.forName("com.jakewharton.processphoenix.ProcessPhoenix");
35+
Method method = processPhoenixClass.getMethod("triggerRebirth", Context.class);
36+
method.invoke(null, mContext);
37+
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
38+
e.printStackTrace();
39+
}
40+
}
41+
}
42+
43+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.react_native_storybook_starter;
2+
3+
import android.content.Context;
4+
import android.content.SharedPreferences;
5+
import android.preference.PreferenceManager;
6+
7+
public class StorybookDevMenuPreferencesService {
8+
private static final String STORYBOOK_ENABLED_KEY = "storybook-enabled-dev-menu-item";
9+
10+
public static boolean isStorybookEnabled(Context context) {
11+
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
12+
return preferences.getBoolean(STORYBOOK_ENABLED_KEY, false);
13+
}
14+
15+
public static void toggleStorybookFlag(Context context) {
16+
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
17+
boolean storybookEnabled = preferences.getBoolean(STORYBOOK_ENABLED_KEY, false);
18+
SharedPreferences.Editor editor = preferences.edit();
19+
editor.putBoolean(STORYBOOK_ENABLED_KEY, !storybookEnabled);
20+
editor.apply();
21+
}
22+
}

index.storybook.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Entry point for Storybook bundle
3+
*/
4+
import {AppRegistry} from 'react-native';
5+
import StorybookUIRoot from './.ondevice/Storybook';
6+
import {name as appName} from './app.json';
7+
8+
AppRegistry.registerComponent(appName, () => StorybookUIRoot);

ios/RNSStorybookDevMenuModule.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef RNSStorybookDevMenuModule_h
2+
#define RNSStorybookDevMenuModule_h
3+
4+
#import <React/RCTBridgeModule.h>
5+
6+
RCT_EXTERN BOOL RNSStorybookDevMenuIsStorybookEnabled(void);
7+
8+
@interface RNSStorybookDevMenuModule : NSObject <RCTBridgeModule>
9+
10+
@end
11+
12+
#endif /* RNSStorybookDevMenuModule_h */

ios/RNSStorybookDevMenuModule.m

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#import "RNSStorybookDevMenuModule.h"
2+
3+
#if __has_include(<React/RCTDevMenu.h>)
4+
#import <React/RCTDevMenu.h>
5+
#endif
6+
7+
#import <React/RCTReloadCommand.h>
8+
9+
static BOOL IsStorybookEnabled = NO;
10+
11+
BOOL RNSStorybookDevMenuIsStorybookEnabled(void) {
12+
return IsStorybookEnabled;
13+
}
14+
15+
@implementation RNSStorybookDevMenuModule {
16+
#if __has_include(<React/RCTDevMenu.h>)
17+
RCTDevMenuItem *_devMenuItem;
18+
#endif
19+
}
20+
21+
@synthesize bridge = _bridge;
22+
23+
RCT_EXPORT_MODULE();
24+
25+
+ (BOOL)requiresMainQueueSetup {
26+
return YES;
27+
}
28+
29+
- (dispatch_queue_t)methodQueue {
30+
return dispatch_get_main_queue();
31+
}
32+
33+
#if __has_include(<React/RCTDevMenu.h>)
34+
- (RCTDevMenuItem *)devMenuItem {
35+
if (!_devMenuItem) {
36+
_devMenuItem = [RCTDevMenuItem buttonItemWithTitleBlock:^NSString *{
37+
return @"Toggle Storybook";
38+
} handler:^{
39+
IsStorybookEnabled = !IsStorybookEnabled;
40+
RCTTriggerReloadCommandListeners(@"Toggle Storybook");
41+
}];
42+
}
43+
return _devMenuItem;
44+
}
45+
#endif
46+
47+
- (void)setBridge:(RCTBridge *)bridge {
48+
_bridge = bridge;
49+
#if __has_include(<React/RCTDevMenu.h>)
50+
[_bridge.devMenu addItem:self.devMenuItem];
51+
#endif
52+
}
53+
54+
@end

ios/react_native_storybook_starter.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
00E356F31AD99517003FC87E /* react_native_storybook_starterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* react_native_storybook_starterTests.m */; };
1111
0C80B921A6F3F58F76C31292 /* libPods-react_native_storybook_starter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-react_native_storybook_starter.a */; };
12+
118E6F6629EB114000637371 /* RNSStorybookDevMenuModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 118E6F6429EB114000637371 /* RNSStorybookDevMenuModule.m */; };
1213
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
1314
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
1415
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
@@ -30,6 +31,8 @@
3031
00E356EE1AD99517003FC87E /* react_native_storybook_starterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = react_native_storybook_starterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3132
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
3233
00E356F21AD99517003FC87E /* react_native_storybook_starterTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = react_native_storybook_starterTests.m; sourceTree = "<group>"; };
34+
118E6F6429EB114000637371 /* RNSStorybookDevMenuModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSStorybookDevMenuModule.m; sourceTree = "<group>"; };
35+
118E6F6529EB114000637371 /* RNSStorybookDevMenuModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSStorybookDevMenuModule.h; sourceTree = "<group>"; };
3336
13B07F961A680F5B00A75B9A /* react_native_storybook_starter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = react_native_storybook_starter.app; sourceTree = BUILT_PRODUCTS_DIR; };
3437
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = react_native_storybook_starter/AppDelegate.h; sourceTree = "<group>"; };
3538
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = react_native_storybook_starter/AppDelegate.mm; sourceTree = "<group>"; };
@@ -92,6 +95,8 @@
9295
13B07FB61A68108700A75B9A /* Info.plist */,
9396
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
9497
13B07FB71A68108700A75B9A /* main.m */,
98+
118E6F6529EB114000637371 /* RNSStorybookDevMenuModule.h */,
99+
118E6F6429EB114000637371 /* RNSStorybookDevMenuModule.m */,
95100
);
96101
name = react_native_storybook_starter;
97102
sourceTree = "<group>";
@@ -412,6 +417,7 @@
412417
isa = PBXSourcesBuildPhase;
413418
buildActionMask = 2147483647;
414419
files = (
420+
118E6F6629EB114000637371 /* RNSStorybookDevMenuModule.m in Sources */,
415421
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
416422
13B07FC11A68108700A75B9A /* main.m in Sources */,
417423
);

ios/react_native_storybook_starter/AppDelegate.mm

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#import <React/RCTBundleURLProvider.h>
44

5+
#import "RNSStorybookDevMenuModule.h"
6+
57
@implementation AppDelegate
68

79
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
@@ -17,7 +19,8 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
1719
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
1820
{
1921
#if DEBUG
20-
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
22+
BOOL isStorybookEnabled = RNSStorybookDevMenuIsStorybookEnabled();
23+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:isStorybookEnabled ? @"index.storybook" : @"index"];
2124
#else
2225
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
2326
#endif

0 commit comments

Comments
 (0)