Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Calvium React Native Project Default ESLint Settings.
// To have these appear in-line in WebStorm/PhpStorm go to Settings / Languages & Frameworks / JavaScript / Code Quality Tools / ESLint / enable
{
"extends": "calvium"
}
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This library does not assume any specific navigation library is in use. As a res

Here's how to use it in react-native-router-flux:

- First, add the Scene to your list of scenes at the root of your app:
- First, add the `ComponentViewer` component to your list of scenes at the root of your app:

```js
import {ComponentViewer} from 'react-native-component-viewer';
Expand Down Expand Up @@ -134,7 +134,7 @@ addComponentTest(

Multiple tests for a single component appear in the ComponentViewer list as a single entry. Tapping the entry displays a ScrollView containing all your tests.

## Usage with Redux
# Usage with Redux

If you're using the `react-redux` `connect` method, make sure you pass the 'unconnected' version of the component to `addTestScene`, e.g.:

Expand All @@ -158,6 +158,11 @@ addTestScene(<MyComponent {...testData}/>);

This way you can make sure your test scenes are completely independent of the Redux state.

# Other options

By default the list will save and restore your last search term via `AsyncStorage`. This is useful if you have many registered tests, as it saves you from having to type the search term every time you reload.

Saving your last search can be disabled by setting the optional `saveSearch` prop to `false` on `ComponentViewer`. The default is `true`.



Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-component-viewer",
"version": "0.2.1",
"version": "0.2.2",
"description": "A searchable list of components or scenes in your app. Handy for tweaking layout or design without using the app",
"main": "index.js",
"scripts": {},
Expand Down Expand Up @@ -28,5 +28,8 @@
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"devDependencies": {
"eslint-config-calvium": "^0.3.0"
}
}
58 changes: 43 additions & 15 deletions src/DebugSceneList.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {Component, PropTypes} from 'react';

import {View, StyleSheet, Text, TouchableHighlight, Alert, ScrollView} from 'react-native';
import {View, StyleSheet, Text, TouchableHighlight, ScrollView, AsyncStorage} from 'react-native';
import {getTests} from './TestRegistry';
import type {RegisteredItemType} from './TestRegistry';
import SearchableList from './SearchableList';
Expand Down Expand Up @@ -45,6 +45,8 @@ const styles = StyleSheet.create({
},
});

const SAVED_SEARCH_KEY = 'com.calvium.react-native-component-viewer.savedSearch';

/**
* Fills SearchableList with list of scenes.
* Displays scenes full-screen when tapped, along with
Expand All @@ -59,39 +61,56 @@ class DebugSceneList extends Component {
all: allScenes,
modalVisible: false,
selectedItem: undefined,
search: '', // loaded in componentDidMount
};

this.onHideScene = () =>
this.setState({modalVisible: false, selectedItem: undefined});
this.onHideScene = () => this.setState({modalVisible: false, selectedItem: undefined});

this.onPressRow = (data: RegisteredItemType) => {
this.setState({
modalVisible: true,
selectedItem: data,
});
};

// TextInput calls this when text changed.
// We don't need to set the state here.
this.handleSearchChanged = search => {
if (this.props.saveSearch) {
AsyncStorage.setItem(SAVED_SEARCH_KEY, search);
}
};
}

componentDidMount() {
// Load saved value
if (this.props.saveSearch) {
AsyncStorage.getItem(SAVED_SEARCH_KEY).then(search => this.setState({search: search || ''}));
}
}

renderSceneModal() {
return (
<View key={'modal1'} style={[styles.selectedComponentWrapper, this.state.selectedItem && this.state.selectedItem.wrapperStyle]}>
<View
key={'component-viewer-modal'}
style={[styles.selectedComponentWrapper, this.state.selectedItem && this.state.selectedItem.wrapperStyle]}
>
{this.state.selectedItem.component}
</View>
);
}

renderComponentModal() {
const {selectedItem}:{ selectedItem: RegisteredItemType } = this.state;
const {selectedItem}: {selectedItem: RegisteredItemType} = this.state;
return (
<View key={'modal1'} style={[styles.selectedComponentWrapper, selectedItem && selectedItem.wrapperStyle]}>
<View key={'component-viewer-modal'} style={[styles.selectedComponentWrapper, selectedItem && selectedItem.wrapperStyle]}>
<ScrollView contentContainerStyle={styles.componentModalScrollView} automaticallyAdjustContentInsets={true}>
{selectedItem.states.map((i: RegisteredItemType) => {
return [
<Text key={`${i.name}_${i.title}_title`} style={styles.componentTitle}>{i.title}</Text>,
<View key={`${i.name}_${i.title}_component`} style={[styles.componentWrapper, i.wrapperStyle]}>
{i.component}
</View>];
})}
{selectedItem.states.map((i: RegisteredItemType) => [
<Text key={`${i.name}_${i.title}_title`} style={styles.componentTitle}>{i.title}</Text>,
<View key={`${i.name}_${i.title}_component`} style={[styles.componentWrapper, i.wrapperStyle]}>
{i.component}
</View>,
])}
</ScrollView>
</View>
);
Expand All @@ -100,7 +119,7 @@ class DebugSceneList extends Component {
renderModal() {
return [
this.state.selectedItem.type === 'scene' ? this.renderSceneModal() : this.renderComponentModal(),
<TouchableHighlight key={'modal2'} style={styles.closeButton} onPress={this.onHideScene}>
<TouchableHighlight key={'component-viewer-close-button'} style={styles.closeButton} onPress={this.onHideScene}>
<Text style={styles.closeButtonText}>Close</Text>
</TouchableHighlight>,
];
Expand All @@ -109,7 +128,14 @@ class DebugSceneList extends Component {
render() {
return (
<View style={styles.container}>
<SearchableList onClose={this.props.onClose} onPressRow={this.onPressRow} items={this.state.all} />
<SearchableList
search={this.state.search}
onClose={this.props.onClose}
onPressRow={this.onPressRow}
onSearchChanged={this.handleSearchChanged}
items={this.state.all}
saveSearch={this.props.saveSearch}
/>
{this.state.modalVisible ? this.renderModal() : undefined}
</View>
);
Expand All @@ -118,10 +144,12 @@ class DebugSceneList extends Component {

DebugSceneList.defaultProps = {
onClose: () => {},
saveSearch: true,
};

DebugSceneList.propTypes = {
onClose: PropTypes.func,
saveSearch: PropTypes.bool,
};

export default DebugSceneList;
24 changes: 21 additions & 3 deletions src/SearchableList.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const styles = StyleSheet.create({
},
searchInput: {
flex: 1,
borderRadius: Platform.OS == 'ios' ? 10 : 0,
borderRadius: Platform.OS === 'ios' ? 10 : 0,
backgroundColor: colors.whiteColor,
alignSelf: 'stretch',
borderWidth: 1,
Expand Down Expand Up @@ -83,14 +83,18 @@ class SearchableList extends Component {
all: allItems,
ds: ds.cloneWithRows(allItems),
selectedComponent: undefined,
search: this.props.search,
};

this.renderRow = data => <SceneRow onPress={() => this.props.onPressRow(data)} {...data} />;

// type sets state - state change performs actual searching!
this.handleSearchInputChanged = filter => this.setState({search: filter});

/**
* Search - lowercase everything. Search on name and title
*/
this.search = filter => {
this.performSearch = filter => {
this.props.onSearchChanged(filter);
const filterLC = String(filter).toLowerCase();
const filtered = R.filter(
Expand All @@ -110,6 +114,16 @@ class SearchableList extends Component {
}, 150);
}

componentWillUpdate(nextProps, nextState) {
if (nextProps.search !== this.props.search) {
this.setState({search: nextProps.search});
}

if (nextState.search !== this.state.search) {
this.performSearch(nextState.search);
}
}

listView: ListView;

render() {
Expand All @@ -122,7 +136,8 @@ class SearchableList extends Component {
autoCorrect={false}
enablesReturnKeyAutomatically={true}
style={styles.searchInput}
onChangeText={this.search}
value={this.state.search}
onChangeText={this.handleSearchInputChanged}
clearButtonMode={'while-editing'}
/>
<TouchableHighlight underlayColor={colors.whiteColor} onPress={this.props.onClose} style={styles.doneButton}>
Expand All @@ -149,9 +164,12 @@ SearchableList.defaultProps = {
onPressRow: () => {},
onClose: () => {},
onSearchChanged: () => {},
search: '',
};

SearchableList.propTypes = {
search: PropTypes.string,
onSearchChanged: PropTypes.func,
onPressRow: PropTypes.func,
onClose: PropTypes.func,
items: PropTypes.arrayOf(
Expand Down
19 changes: 11 additions & 8 deletions src/TestRegistry.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-console */
import React from 'react';
import * as R from 'ramda';

Expand All @@ -7,7 +8,7 @@ import * as R from 'ramda';
export type RegisteredItemType = {|
name: string, // extracted from the component's name, or provided by user
title: ?string, // title used when scene is used several times with different test data
component: React$Element, // the actual element itself
component: React.Element<any>, // the actual element itself
type: 'scene' | 'component',
states?: Array<RegisteredItemType>,
wrapperStyle: ?Object,
Expand All @@ -28,7 +29,7 @@ const registeredItems: {|[string]: RegisteredItemType|} = {};
* Get the name of the component. Note only works in debug builds as production builds minify JS code and remove names
* Stateless components have .name, classes have .displayName.
*/
function getName(component: React$Element) {
function getName(component: React.Element<any>) {
if (!component) {
return '(no component)';
}
Expand All @@ -43,7 +44,7 @@ function getName(component: React$Element) {
* @param type - For Screens/Scenes that should be displayed full-screen, use type='scene'. To display the same component in different states on the same screen, use type='component'.
* @param options - for the test
*/
function addTest(component: React$Element, type: 'scene' | 'component', options: TestType = {}) {
function addTest(component: React.Element<any>, type: 'scene' | 'component', options: TestType = {}) {
if (!component || !component.type) {
return;
}
Expand All @@ -63,7 +64,7 @@ function addTest(component: React$Element, type: 'scene' | 'component', options:

if (existing) {
if (!existing.states) {
console.log(`Probably trying to register a component on something previously registered as a scene`);
console.log('Probably trying to register a component on something previously registered as a scene');
return;
}
existing.states = R.uniqBy(i => i.title, [...existing.states, itemDetails]); // remove dupes, based on title
Expand Down Expand Up @@ -112,9 +113,10 @@ function getTests(): Array<RegisteredItemType> {
* @param options - TestType instance with options for the test. Backwards-compatible with previous version.
* @param wrapperStyle - don't use this. Instead use the `wrapperStyle` property on `options`
*/
const addSceneTest = (component: React$Element, options: ?string | ?TestType, wrapperStyle: ?Object = {}) => {
const addSceneTest = (component: React.Element<any>, options: ?string | ?TestType, wrapperStyle: ?Object = {}) => {
if (R.is(Object, options)) {
return addTest(component, 'scene', options);
addTest(component, 'scene', options);
return;
}
// Backwards compatibility, where options is actually a string
addTest(component, 'scene', {title: options, wrapperStyle});
Expand All @@ -127,9 +129,10 @@ const addSceneTest = (component: React$Element, options: ?string | ?TestType, wr
* @param options - TestType instance with options for the test. Backwards-compatible with previous version.
* @param wrapperStyle - don't use this. Instead use the `wrapperStyle` property on `options`
*/
const addComponentTest = (component: React$Element, options: ?string | ?TestType, wrapperStyle: ?Object = {}) => {
const addComponentTest = (component: React.Element<any>, options: ?string | ?TestType, wrapperStyle: ?Object = {}) => {
if (R.is(Object, options)) {
return addTest(component, 'component', options);
addTest(component, 'component', options);
return;
}
addTest(component, 'component', {title: options, wrapperStyle});
};
Expand Down