Skip to content

Commit

Permalink
MVP version of the on device React Native UI
Browse files Browse the repository at this point in the history
This is a combination of 10 commits.

- Add a basic implementation of the native UI for the storybook
- Fix the lint errors
- Add a param to turn the native UI on and off using the same package
- Correctly set the width of the story list
- Added a size selector for the different sized iPhone screens
- Show the selected story in bold
- Add some styling to the panels
- Removed the canvas size switcher
- Fix lint errors
- Shortened the long render item and header lines
  • Loading branch information
matt-oakes committed Jul 7, 2017
1 parent fbc665f commit 86679eb
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 8 deletions.
41 changes: 41 additions & 0 deletions app/react-native/src/preview/components/OnDeviceUI/index.js
Original file line number Diff line number Diff line change
@@ -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 (
<View style={style.main}>
<View style={style.leftPanel}>
<StoryListView stories={stories} events={events} />
</View>
<View style={style.rightPanel}>
<View style={style.preview}>
<StoryView url={url} events={events} />
</View>
</View>
</View>
);
}

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,
};
28 changes: 28 additions & 0 deletions app/react-native/src/preview/components/OnDeviceUI/style.js
Original file line number Diff line number Diff line change
@@ -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,
},
};
127 changes: 127 additions & 0 deletions app/react-native/src/preview/components/StoryListView/index.js
Original file line number Diff line number Diff line change
@@ -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 }) => (
<View key={title} style={style.header}>
<Text style={[style.headerText, selected && style.headerTextSelected]}>
{title}
</Text>
</View>
);

SectionHeader.propTypes = {
title: PropTypes.string.isRequired,
selected: PropTypes.bool.isRequired,
};

const ListItem = ({ title, selected, onPress }) => (
<TouchableOpacity
key={title}
style={style.item}
onPress={onPress}
>
<Text style={[style.itemText, selected && style.itemTextSelected]}>
{title}
</Text>
</TouchableOpacity>
);

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 (
<SectionList
style={style.list}
renderItem={({ item }) => (
<ListItem
title={item.name}
selected={item.kind === this.state.selectedKind && item.name === this.state.selectedStory}
onPress={() => this.changeStory(item.kind, item.name)}
/>
)}
renderSectionHeader={({ section }) => (
<SectionHeader
title={section.title}
selected={section.title === this.state.selectedKind}
/>
)}
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,
};
26 changes: 26 additions & 0 deletions app/react-native/src/preview/components/StoryListView/style.js
Original file line number Diff line number Diff line change
@@ -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',
},
};
8 changes: 7 additions & 1 deletion app/react-native/src/preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 <StoryView url={webUrl} events={this._events} />;
return (params.onDeviceUI) ? (
<OnDeviceUI stories={this._stories} events={this._events} url={webUrl} />
) : (
<StoryView url={webUrl} events={this._events} />
);
};
}

Expand Down
7 changes: 6 additions & 1 deletion app/react-native/src/preview/story_store.js
Original file line number Diff line number Diff line change
@@ -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 = {};
}

Expand All @@ -21,6 +24,8 @@ export default class StoryStore {
index: count,
fn,
};

this.emit('storyAdded', kind, name, fn);
}

getStoryKinds() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -419,7 +419,7 @@
isa = PBXGroup;
children = (
5E9157331DD0AC6500FF2AA8 /* libRCTAnimation.a */,
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation-tvOS.a */,
5E9157351DD0AC6500FF2AA8 /* libRCTAnimation.a */,
);
name = Products;
sourceTree = "<group>";
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -1006,6 +1006,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
Expand All @@ -1023,6 +1024,7 @@
"-lc++",
);
PRODUCT_NAME = ReactNativeVanilla;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
Expand Down
2 changes: 1 addition & 1 deletion examples/react-native-vanilla/storybook/storybook.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(
() =>
Expand Down

0 comments on commit 86679eb

Please sign in to comment.