Skip to content

Commit

Permalink
Add EmptyStateView, NumberInput and SearchField from @shoutem/ui-addons
Browse files Browse the repository at this point in the history
  • Loading branch information
aesqe committed Jun 17, 2019
1 parent caf426d commit c8a1662
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 6 deletions.
74 changes: 74 additions & 0 deletions components/EmptyStateView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

import { connectStyle } from '@shoutem/theme';

import Icon from './Icon';
import View from './View';
import Text from './Text';
import Button from './Button';
import Subtitle from './Subtitle';

class EmptyStateView extends PureComponent {
static defaultProps = {
retryButtonTitle: 'TRY AGAIN',
icon: 'error',
}

constructor(props) {
super(props);

this.onRetry = this.onRetry.bind(this);
this.renderRetryButton = this.renderRetryButton.bind(this);
}

onRetry() {
this.props.onRetry();
}

renderRetryButton() {
const { retryButtonTitle } = this.props;

// Show retry button at the bottom only if
// there is a onRetry action passed.
return (
<View styleName="horizontal anchor-bottom">
<Button styleName="full-width" onPress={this.onRetry}>
<Text>{retryButtonTitle}</Text>
</Button>
</View>
);
}

render() {
const { icon, message, onRetry } = this.props;

return (
<View
{...this.props}
styleName="vertical flexible h-center v-center"
>
<View styleName="icon-placeholder">
<Icon name={icon} />
</View>

<Subtitle styleName="h-center">{message}</Subtitle>

{onRetry && this.renderRetryButton()}
</View>
);
}
}

EmptyStateView.propTypes = {
...EmptyStateView.propTypes,
onRetry: PropTypes.func,
message: PropTypes.string,
icon: PropTypes.string,
};

const StyledView = connectStyle('shoutem.ui.EmptyStateView')(EmptyStateView);

export {
StyledView as EmptyStateView,
};
141 changes: 141 additions & 0 deletions components/NumberInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';

import { connectStyle } from '@shoutem/theme';
import { connectAnimation } from '@shoutem/animation';

import Icon from './Icon';
import View from './View';
import Button from './Button';
import TextInput from './TextInput';

const { func, number, object, oneOfType, shape, string } = PropTypes;

/**
* A component for entering a numerical value with two helper buttons for increasing
* and decreasing the value.
*
* The current implementation allows only positive integers. It removes any character
* that is not a digit, if the user enters it directly or by pasting.
*
* The component allows empty values. The consumer needs to handle it as he sees fit.
*
* TODO: Future implementations should define how to handle validation in combination with
* focus and blur events.
*/
class NumberInput extends PureComponent {
static propTypes = {
...TextInput.propTypes,
// Maximum allowed value
max: number,
// Minimum allowed value
min: number,
// Called when the user changes the value by inputting it directly or with buttons
onChange: func.isRequired,
// Step used to increase or decrease value with corresponding buttons
step: number,
// Styles for component parts
style: shape({
button: object,
container: object,
icon: object,
input: object,
inputContainer: object,
}),
// Value of the input - can be empty
value: oneOfType([
number,
string,
]),
};

constructor(props) {
super(props);

this.decreaseValue = this.decreaseValue.bind(this);
this.increaseValue = this.increaseValue.bind(this);
this.onChangeText = this.onChangeText.bind(this);
}

onChangeText(text) {
const transformedText = text.replace(/[^0-9]/g, '');

this.updateValue(parseInt(transformedText, 10));
}

decreaseValue() {
const { step = 1, value = 0 } = this.props;

this.updateValue(value - step);
}

increaseValue() {
const { step = 1, value = 0 } = this.props;

this.updateValue(value + step);
}

updateValue(value) {
const { max, min, onChange } = this.props;

if (!_.isFinite(value)) {
return onChange('');
}

let newValue = _.isFinite(max) && value > max ? max : value;
newValue = _.isFinite(min) && newValue < min ? min : newValue;

return onChange(newValue);
}

render() {
const { style, value } = this.props;

return (
<View
style={style.container}
styleName="horizontal"
>
<Button
style={style.button}
styleName="secondary"
onPress={this.decreaseValue}
>
<Icon
name="minus-button"
style={style.icon}
/>
</Button>
<View
style={style.inputContainer}
styleName="horizontal"
>
<TextInput
keyboardType="numeric"
onChangeText={this.onChangeText}
style={style.input}
value={`${value}`}
/>
</View>
<Button
style={style.button}
styleName="secondary"
onPress={this.increaseValue}
>
<Icon
name="plus-button"
style={style.icon}
/>
</Button>
</View>
);
}
}

const AnimatedNumberInput = connectAnimation(NumberInput);
const StyledNumberInput = connectStyle('shoutem.ui.NumberInput')(AnimatedNumberInput);

export {
StyledNumberInput as NumberInput,
};
90 changes: 90 additions & 0 deletions components/SearchField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';

import { connectStyle } from '@shoutem/theme';
import { connectAnimation } from '@shoutem/animation';

import Icon from './Icon';
import View from './View';
import Button from './Button';
import TextInput from './TextInput';

const { func, object, shape, string } = PropTypes;

const ClearButton = ({ style, onPress }) => (
<Button
styleName="clear tight"
onPress={onPress}
>
<Icon
name="clear-text"
style={style.clearIcon}
/>
</Button>
);

ClearButton.propTypes = {
style: object,
onPress: func,
};

/**
* A component that allows the user to enter a search query.
* It has a search icon, placeholder and a button that clears the current query.
*
*/
class SearchField extends PureComponent {
static propTypes = {
// A placeholder for input when no value is entered
placeholder: string,
// Called with the new value on text change
onChangeText: func,
// Styles for container and search icon
style: shape({
clearIcon: object,
container: object,
input: object,
searchIcon: object,
}),
// Value to render as text in search input
value: string,
};

render() {
const { onChangeText, placeholder, style, value, ...rest } = this.props;

return (
<View
style={style.container}
styleName="horizontal sm-gutter-horizontal v-center"
>
<Icon
name="search"
style={style.searchIcon}
/>
<TextInput
{...rest}
autoCapitalize="none"
autoCorrect={false}
onChangeText={onChangeText}
placeholder={placeholder}
style={style.input}
value={value}
/>
{value && (
<ClearButton
onPress={() => onChangeText('')}
style={style}
/>
)}
</View>
);
}
}

const AnimatedSearchField = connectAnimation(SearchField);
const StyledSearchField = connectStyle('shoutem.ui.SearchField')(AnimatedSearchField);

export {
StyledSearchField as SearchField,
};
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export { Tile } from './components/Tile';

export { Lightbox } from './components/Lightbox';

export { EmptyStateView } from './components/EmptyStateView';
export { NumberInput } from './components/NumberInput';
export { SearchField } from './components/SearchField';

export { Examples } from './examples/components';

export { Device } from './helpers';
Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@shoutem/ui",
"version": "0.23.20",
"version": "0.24.0",
"description": "Styleable set of components for React Native applications",
"dependencies": {
"@shoutem/animation": "~0.12.3",
Expand All @@ -12,16 +12,16 @@
"htmlparser2": "~3.9.0",
"lodash": "~4.17.4",
"qs": "~4.0.0",
"react-native-render-html": "~4.1.2",
"prop-types": "^15.5.10",
"react-native-lightbox": "shoutem/react-native-lightbox#v0.7.2",
"react-native-linear-gradient": "~2.4.0",
"react-native-navigation-experimental-compat": "shoutem/react-native-navigation-experimental-compat#fbce4b9e478f808634a98dac48901ec6ed4ee9fd",
"react-native-photo-view": "shoutem/react-native-photo-view#2f650beba07a263876c54f81a34b5ac2f9b805ce",
"react-native-render-html": "~4.1.2",
"react-native-transformable-image": "shoutem/react-native-transformable-image#v0.0.20",
"react-native-vector-icons": "~4.3.0",
"react-native-photo-view": "shoutem/react-native-photo-view#2f650beba07a263876c54f81a34b5ac2f9b805ce",
"react-native-navigation-experimental-compat": "shoutem/react-native-navigation-experimental-compat#fbce4b9e478f808634a98dac48901ec6ed4ee9fd",
"stream": "0.0.2",
"tinycolor2": "~1.3.0",
"prop-types": "^15.5.10"
"tinycolor2": "~1.3.0"
},
"peerDependencies": {
"react": "^16.0.0",
Expand Down

0 comments on commit c8a1662

Please sign in to comment.