UNPKG

react-native-screen-keyboard

Version:

On-screen keyboard with customisable keys and tactile / UI feedback 📱

482 lines (447 loc) • 11.4 kB
/** * @author Luke Brandon Farrell * @description Keyboard component with message and interactive keys */ import React, { Component } from "react"; import { View, Image, Text, StyleSheet, Platform, Vibration, } from "react-native"; import Ripple from "react-native-material-ripple"; import PropTypes from "prop-types"; import { TextInputPropTypes, ViewPropTypes, } from "deprecated-react-native-prop-types"; const backAsset = require("./back.png"); class VirtualKeyboard extends Component { /** * [ Built-in React method. ] * * Setup the component. Executes when the component is created * * @param {object} props * */ constructor(props) { super(props); this.state = { text: "", disabled: false, message: null, }; } /** * [ Built-in React method. ] * * Executed when the component is mounted to the screen. */ componentDidMount() { if (this.props.onRef) { this.props.onRef(this); } } /** * [ Built-in React method. ] * * Executed when the component is unmounted from the screen */ componentWillUnmount() { if (this.props.onRef) { this.props.onRef(undefined); } } /** * [ Built-in React method. ] * * Executed when the components props are updated. */ componentDidUpdate(prevProps, prevState) { if (prevState.text !== this.state.text) { if (this.props.onChange) this.props.onChange(this.state.text); } } /** * [ Built-in React method. ] * * Allows us to render JSX to the screen */ render() { /** Styles */ const { containerStyle, keyboardDefaultStyle, keyboardRowStyle } = styles; /** Props */ const { keyboard, keyboardCustomKeyImage, keyboardCustomBackKey = backAsset, // Style Props keyboardStyle, } = this.props; /** Variables */ // Keyboard configuration. The default contains a key // for each number 0 - 9 and a back button. const defaultKeyboard = keyboard ? keyboard : [ [1, 2, 3], [4, 5, 6], [7, 8, 9], [keyboardCustomKeyImage, 0, keyboardCustomBackKey], ]; return ( <View style={containerStyle}> {this.renderMessage()} <View style={[keyboardDefaultStyle, keyboardStyle]}> { // Maps each array of numbers in the keyboardValues array defaultKeyboard.map((row, r) => { return ( <View key={r} style={keyboardRowStyle}> { // Maps each number in row and creates key for that number row.map((element, k) => { return this.renderKey(element, r, k); }) } </View> ); }) } </View> </View> ); } /** * Renders the message * * @return {*} */ renderMessage() { // Styles const { messageDefaultStyle, messageTextDefaultStyle } = styles; // Props const { // Style Props messageStyle, messageTextStyle, // TestID Props messageTestID, messageTextTestID, } = this.props; // State const { message } = this.state; if (message) { return ( <View testID={`VirtualKeyboard-${messageTestID}`} style={[messageDefaultStyle, messageStyle]} > <Text testID={`VirtualKeyboard-${messageTextTestID}`} style={[messageTextDefaultStyle, messageTextStyle]} > {message} </Text> </View> ); } return null; } /** * Renders a key on the keyboard * * @param entity * @param row * @param column * * @return {jsx} */ renderKey(entity, row, column) { /** Styles */ const { keyContainerStyle, keyboardDisabledDefaultStyle, keyDefaultStyle, keyTextDefaultStyle, keyImageDefaultStyle, } = styles; /** Props */ const { keyDisabled, keyboardFunc, keyboardDisabledStyle, vibration, onKeyDown, onChange, onPressFunction, // Style Props keyStyle, keyCustomStyle, keyTextStyle, keyImageStyle, } = this.props; /** State */ const { disabled } = this.state; /** Variables */ const keyDown = (value) => { this.setState({ text: this.resolveKeyDownVirtualKeyboard(this.state.text, value), }); if (onKeyDown) onKeyDown(value); }; // Custom functions for the keyboard key const keyboardFuncSet = keyboardFunc ? keyboardFunc : [ [null, null, null], [null, null, null], [null, null, null], [() => keyDown("custom"), null, () => keyDown("back")], ]; const _keyStyle = keyCustomStyle && keyCustomStyle[row][column] ? keyCustomStyle[row][column] : keyStyle; const _keyDisabled = keyDisabled && keyDisabled[row][column]; // Decide what type of element is passed as the key let keyJsx; if (React.isValidElement(entity)) { keyJsx = entity; } else if (keyboardFuncSet[row][column]) { keyJsx = ( <Image style={[keyImageDefaultStyle, keyImageStyle]} source={entity} /> ); } else { keyJsx = ( <Text style={[keyTextDefaultStyle, keyTextStyle]}>{entity}</Text> ); } // We want to block keyboard interactions if it has been disabled. if (!disabled) { const onPress = () => { if (vibration) Vibration.vibrate(50); keyboardFuncSet[row][column] ? keyboardFuncSet[row][column]() : keyDown(entity); }; return ( <Ripple testID={`VirtualKeyboard-${entity}`} rippleColor={"#000"} key={column} onPress={onPressFunction === "onPress" ? onPress : undefined} onPressIn={ !onPressFunction || onPressFunction === "onPressIn" ? onPress : undefined } onPressOut={onPressFunction === "onPressOut" ? onPress : undefined} style={[keyContainerStyle, keyDefaultStyle, _keyStyle]} disabled={entity === null || _keyDisabled} > {keyJsx} </Ripple> ); } else { return ( <View testID={`VirtualKeyboard-${entity}-disabled`} key={column} style={[ keyContainerStyle, keyDefaultStyle, _keyStyle, keyboardDisabledDefaultStyle, keyboardDisabledStyle, ]} > {keyJsx} </View> ); } } /** * Resolves a key press on virtual keyboard * * @param string * @param char */ resolveKeyDownVirtualKeyboard(string = "", char) { const newString = string; switch (char) { case "back": { return newString.substring(0, newString.length - 1); } case "custom": if (this.props.onCustomKey) this.props.onCustomKey(string); return string; default: { return newString.concat(char); } } } /** * Function used to set the keyboard text */ setText(text) { this.setState({ text }); } /** * Function used to execute back/delete */ back() { this.setState({ text: this.resolveKeyDownVirtualKeyboard(this.state.text, "back"), }); } /** * Function used to display a message above the keyboard * * @param message */ displayMessage(message) { this.setState( { message, }, () => { if (this.hideMessageTimeout) clearTimeout(this.hideMessageTimeout); this.hideMessageTimeout = setTimeout(() => { this.clearMessage(); }, this.props.keyboardMessageDisplayTime); } ); } /** * Function used to clear the message on the keyboard */ clearMessage() { this.setState({ message: null }); } /** * Function used to disable the keyboard */ disable() { this.setState({ disabled: true, }); } /** * Function used to enable the keyboard */ enable() { this.setState({ disabled: false, }); } } VirtualKeyboard.propTypes = { onRef: PropTypes.any, onKeyDown: PropTypes.func, onChange: PropTypes.func, onCustomKey: PropTypes.func, onPressFunction: PropTypes.oneOf(["onPress", "onPressIn", "onPressOut"]), keyboard: PropTypes.array, keyboardFunc: PropTypes.array, keyboardCustomKeyImage: PropTypes.number, keyboardCustomBackKey: PropTypes.element, keyboardMessageDisplayTime: PropTypes.number, keyDisabled: PropTypes.array, vibration: PropTypes.bool, // Style props keyboardStyle: ViewPropTypes.style, keyboardDisabledStyle: ViewPropTypes.style, keyStyle: ViewPropTypes.style, keyCustomStyle: ViewPropTypes.style, keyTextStyle: TextInputPropTypes.style, keyImageStyle: ViewPropTypes.style, messageStyle: ViewPropTypes.style, messageTextStyle: ViewPropTypes.style, // TestID props messageTestID: PropTypes.string, messageTextTestID: PropTypes.string, }; VirtualKeyboard.defaultProps = { // Keyboard functions. By default the text (number) in the // keyboard array will be passed via the keyDown callback. // Use this array to set custom functions for certain keys. keyboardFunc: null, keyboardMessageDisplayTime: 3000, onPressFunction: "onPressIn", vibration: false, messageTestID: "MessageContainer", messageTextTestID: "Message", }; const styles = StyleSheet.create({ containerStyle: { flex: null, width: "100%", flexDirection: "column", justifyContent: "flex-end", }, // Style applied to the keyboard. Must contain a height or // the keyboard will not be displayed. keyboardDefaultStyle: { height: 250, backgroundColor: "#FFF", }, keyboardRowStyle: { flex: 1, flexDirection: "row", }, keyContainerStyle: { flex: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", }, // Style applied to keyboard when it is disabled. keyboardDisabledDefaultStyle: { backgroundColor: "#FFF", }, // Style the individual keys keyDefaultStyle: { backgroundColor: "#FFF", borderRightColor: "#e8e8e8", borderRightWidth: 1, borderBottomColor: "#e8e8e8", borderBottomWidth: 1, }, // Style for the text inside a key keyTextDefaultStyle: { ...Platform.select({ ios: { fontFamily: "HelveticaNeue", }, android: { fontFamily: "Roboto", }, }), fontWeight: "400", fontSize: 25, textAlign: "center", color: "#222222", }, // Style for an image inside a key keyImageDefaultStyle: { width: 28, height: 28, }, messageDefaultStyle: { height: 30, width: "100%", justifyContent: "center", alignItems: "center", backgroundColor: "#e8e8e8", }, messageTextDefaultStyle: { color: "#222222", fontSize: 15, fontWeight: "bold", }, }); export default VirtualKeyboard;