diff --git a/app/react-native/src/preview/components/OnDeviceUI/index.js b/app/react-native/src/preview/components/OnDeviceUI/index.js
new file mode 100644
index 000000000000..c939414dd5ca
--- /dev/null
+++ b/app/react-native/src/preview/components/OnDeviceUI/index.js
@@ -0,0 +1,41 @@
+import React, { PropTypes } from 'react';
+import { View } from 'react-native';
+import style from './style';
+import StoryListView from '../StoryListView';
+import StoryView from '../StoryView';
+
+export default function OnDeviceUI(props) {
+ const {
+ stories,
+ events,
+ url
+ } = props;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+OnDeviceUI.propTypes = {
+ stories: PropTypes.shape({
+ dumpStoryBook: PropTypes.func.isRequired,
+ on: PropTypes.func.isRequired,
+ emit: PropTypes.func.isRequired,
+ removeListener: PropTypes.func.isRequired,
+ }).isRequired,
+ events: PropTypes.shape({
+ on: PropTypes.func.isRequired,
+ emit: PropTypes.func.isRequired,
+ removeListener: PropTypes.func.isRequired,
+ }).isRequired,
+ url: PropTypes.string.isRequired,
+};
diff --git a/app/react-native/src/preview/components/OnDeviceUI/style.js b/app/react-native/src/preview/components/OnDeviceUI/style.js
new file mode 100644
index 000000000000..819d4cd6449c
--- /dev/null
+++ b/app/react-native/src/preview/components/OnDeviceUI/style.js
@@ -0,0 +1,28 @@
+import { StyleSheet } from 'react-native';
+
+export default {
+ main: {
+ flex: 1,
+ flexDirection: 'row',
+ paddingTop: 20,
+ backgroundColor: 'rgba(247, 247, 247, 1)',
+ },
+ leftPanel: {
+ flex: 1,
+ maxWidth: 250,
+ paddingHorizontal: 8,
+ paddingBottom: 8,
+ },
+ rightPanel: {
+ flex: 1,
+ backgroundColor: 'rgba(255, 255, 255, 1)',
+ borderWidth: StyleSheet.hairlineWidth,
+ borderColor: 'rgba(236, 236, 236, 1)',
+ borderRadius: 4,
+ marginBottom: 8,
+ marginHorizontal: 8,
+ },
+ preview: {
+ ...StyleSheet.absoluteFillObject,
+ },
+};
diff --git a/app/react-native/src/preview/components/StoryListView/index.js b/app/react-native/src/preview/components/StoryListView/index.js
new file mode 100644
index 000000000000..8ea27dd5fe01
--- /dev/null
+++ b/app/react-native/src/preview/components/StoryListView/index.js
@@ -0,0 +1,127 @@
+import React, { Component, PropTypes } from 'react';
+import { SectionList, View, Text, TouchableOpacity } from 'react-native';
+import style from './style';
+
+const SectionHeader = ({ title, selected }) => (
+
+
+ {title}
+
+
+);
+
+SectionHeader.propTypes = {
+ title: PropTypes.string.isRequired,
+ selected: PropTypes.bool.isRequired,
+};
+
+const ListItem = ({ title, selected, onPress }) => (
+
+
+ {title}
+
+
+);
+
+ListItem.propTypes = {
+ title: PropTypes.string.isRequired,
+ onPress: PropTypes.func.isRequired,
+ selected: PropTypes.bool.isRequired,
+};
+
+export default class StoryListView extends Component {
+ constructor(props, ...args) {
+ super(props, ...args);
+ this.state = {
+ sections: [],
+ selectedKind: null,
+ selectedStory: null,
+ };
+
+ this.storyAddedHandler = this.handleStoryAdded.bind(this);
+ this.storyChangedHandler = this.handleStoryChanged.bind(this);
+ this.changeStoryHandler = this.changeStory.bind(this);
+
+ this.props.stories.on('storyAdded', this.storyAddedHandler);
+ this.props.events.on('story', this.storyChangedHandler);
+ }
+
+ componentDidMount() {
+ this.handleStoryAdded();
+ }
+
+ componentWillUnmount() {
+ this.props.stories.removeListener('storyAdded', this.storiesHandler);
+ this.props.events.removeListener('story', this.storyChangedHandler);
+ }
+
+ handleStoryAdded() {
+ if (this.props.stories) {
+ const data = this.props.stories.dumpStoryBook();
+ this.setState({
+ sections: data.map((section) => ({
+ key: section.kind,
+ title: section.kind,
+ data: section.stories.map((story) => ({
+ key: story,
+ kind: section.kind,
+ name: story
+ }))
+ }))
+ });
+ }
+ }
+
+ handleStoryChanged(storyFn, selection) {
+ const { kind, story } = selection;
+ this.setState({
+ selectedKind: kind,
+ selectedStory: story
+ });
+ }
+
+ changeStory(kind, story) {
+ this.props.events.emit('setCurrentStory', { kind, story });
+ }
+
+ render() {
+ return (
+ (
+ this.changeStory(item.kind, item.name)}
+ />
+ )}
+ renderSectionHeader={({ section }) => (
+
+ )}
+ sections={this.state.sections}
+ stickySectionHeadersEnabled={false}
+ />
+ );
+ }
+}
+
+StoryListView.propTypes = {
+ stories: PropTypes.shape({
+ dumpStoryBook: PropTypes.func.isRequired,
+ on: PropTypes.func.isRequired,
+ emit: PropTypes.func.isRequired,
+ removeListener: PropTypes.func.isRequired,
+ }).isRequired,
+ events: PropTypes.shape({
+ on: PropTypes.func.isRequired,
+ emit: PropTypes.func.isRequired,
+ removeListener: PropTypes.func.isRequired,
+ }).isRequired,
+};
diff --git a/app/react-native/src/preview/components/StoryListView/style.js b/app/react-native/src/preview/components/StoryListView/style.js
new file mode 100644
index 000000000000..00b79d358308
--- /dev/null
+++ b/app/react-native/src/preview/components/StoryListView/style.js
@@ -0,0 +1,26 @@
+export default {
+ list: {
+ flex: 1,
+ maxWidth: 250,
+ },
+ header: {
+ paddingTop: 24,
+ paddingBottom: 4,
+ },
+ headerText: {
+ fontSize: 16,
+ },
+ headerTextSelected: {
+ fontWeight: 'bold',
+ },
+ item: {
+ paddingVertical: 4,
+ paddingHorizontal: 16,
+ },
+ itemText: {
+ fontSize: 14,
+ },
+ itemTextSelected: {
+ fontWeight: 'bold',
+ },
+};
diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js
index c5d253887083..52e521cd1320 100644
--- a/app/react-native/src/preview/index.js
+++ b/app/react-native/src/preview/index.js
@@ -6,6 +6,7 @@ import createChannel from '@storybook/channel-websocket';
import { EventEmitter } from 'events';
import StoryStore from './story_store';
import StoryKindApi from './story_kind';
+import OnDeviceUI from './components/OnDeviceUI';
import StoryView from './components/StoryView';
export default class Preview {
@@ -70,11 +71,16 @@ export default class Preview {
}
channel.on('getStories', () => this._sendSetStories());
channel.on('setCurrentStory', d => this._selectStory(d));
+ this._events.on('setCurrentStory', d => this._selectStory(d));
this._sendSetStories();
this._sendGetCurrentStory();
// finally return the preview component
- return ;
+ return (params.onDeviceUI) ? (
+
+ ) : (
+
+ );
};
}
diff --git a/app/react-native/src/preview/story_store.js b/app/react-native/src/preview/story_store.js
index 86021172b09f..07e91876b125 100644
--- a/app/react-native/src/preview/story_store.js
+++ b/app/react-native/src/preview/story_store.js
@@ -1,8 +1,11 @@
/* eslint no-underscore-dangle: 0 */
+import { EventEmitter } from 'events';
+
let count = 0;
-export default class StoryStore {
+export default class StoryStore extends EventEmitter {
constructor() {
+ super();
this._data = {};
}
@@ -21,6 +24,8 @@ export default class StoryStore {
index: count,
fn,
};
+
+ this.emit('storyAdded', kind, name, fn);
}
getStoryKinds() {
diff --git a/examples/react-native-vanilla/ios/ReactNativeVanilla.xcodeproj/project.pbxproj b/examples/react-native-vanilla/ios/ReactNativeVanilla.xcodeproj/project.pbxproj
index 758386b95aba..b6fe386869b9 100644
--- a/examples/react-native-vanilla/ios/ReactNativeVanilla.xcodeproj/project.pbxproj
+++ b/examples/react-native-vanilla/ios/ReactNativeVanilla.xcodeproj/project.pbxproj
@@ -25,7 +25,7 @@
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
- 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */; };
+ 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */; };
2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E841DF850E9000B6D8A /* libRCTImage-tvOS.a */; };
2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E881DF850E9000B6D8A /* libRCTLinking-tvOS.a */; };
2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3DAD3E8C1DF850E9000B6D8A /* libRCTNetwork-tvOS.a */; };
@@ -289,7 +289,7 @@
buildActionMask = 2147483647;
files = (
2D02E4C91E0B4AEC006451C7 /* libReact.a in Frameworks */,
- 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation-tvOS.a in Frameworks */,
+ 2D02E4C21E0B4AEC006451C7 /* libRCTAnimation.a in Frameworks */,
2D02E4C31E0B4AEC006451C7 /* libRCTImage-tvOS.a in Frameworks */,
2D02E4C41E0B4AEC006451C7 /* libRCTLinking-tvOS.a in Frameworks */,
2D02E4C51E0B4AEC006451C7 /* libRCTNetwork-tvOS.a in Frameworks */,
@@ -419,7 +419,7 @@
isa = PBXGroup;
children = (
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */,
- 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */,
+ 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "";
@@ -804,10 +804,10 @@
remoteRef = 5E9157321DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
- 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */ = {
+ 5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
- path = "libRCTAnimation-tvOS.a";
+ path = libRCTAnimation.a;
remoteRef = 5E9157341DD0AC6500FF2AA8 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
@@ -1006,6 +1006,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
@@ -1023,6 +1024,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
+ TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
diff --git a/examples/react-native-vanilla/storybook/storybook.js b/examples/react-native-vanilla/storybook/storybook.js
index 9af39a2ee00c..d25d21f9d9c7 100644
--- a/examples/react-native-vanilla/storybook/storybook.js
+++ b/examples/react-native-vanilla/storybook/storybook.js
@@ -8,7 +8,7 @@ configure(() => {
require('./stories');
}, module);
-const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost' });
+const StorybookUI = getStorybookUI({ port: 7007, host: 'localhost', onDeviceUI: true });
setTimeout(
() =>