diff --git a/__tests__/EditableCell.spec.js b/__tests__/EditableCell.spec.js deleted file mode 100644 index 3056744..0000000 --- a/__tests__/EditableCell.spec.js +++ /dev/null @@ -1,63 +0,0 @@ -jest.unmock('../EditableCell'); -jest.unmock('enzyme'); -jest.unmock('sinon'); - -import { EditableCell } from '../EditableCell'; -import { shallow } from 'enzyme'; -import sinon from 'sinon'; -import React from 'react'; -import { - TextInput, - View, -} from 'react-native'; - -describe('EditableCell', () => { - it('renders a View and TextInput with default props', () => { - const wrapper = shallow( - - ); - expect(wrapper.state('value')).toEqual('N/A'); - expect(wrapper.find(View).prop('style')[1]).toEqual({ flex: 1 }); - expect(wrapper.find(View).length).toBe(1); - expect(wrapper.find(TextInput).length).toBe(1); - }); - - it('takes a width argument and applies it to defaultStyles', () => { - const wrapper = shallow( - - ); - expect(wrapper.find(View).prop('style')[1]).toEqual({ flex: 1337 }); - }); - - it('sets state to value as string', () => { - const wrapper = shallow( - - ); - expect(wrapper.state('value')).toEqual('50'); - }); - - it('changes state with onChange event', () => { - const wrapper = shallow( - - ); - expect(wrapper.state('value')).toEqual('N/A'); - wrapper.find(TextInput).simulate('changeText', 'foo'); - expect(wrapper.state('value')).toEqual('foo'); - }); - - it('onEndEditing event triggers callback', () => { - const callback = sinon.spy(); - const obj = {}; - const wrapper = shallow( - - ); - wrapper.find(TextInput).simulate('endEditing'); - expect(callback.calledWithExactly(obj, 'foo')).toBe(true); - }); -}); diff --git a/src/EditableCell.js b/src/EditableCell.js deleted file mode 100644 index e02be40..0000000 --- a/src/EditableCell.js +++ /dev/null @@ -1,90 +0,0 @@ -/* @flow weak */ - -/** - * mSupply Mobile - * Sustainable Solutions (NZ) Ltd. 2016 - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { - StyleSheet, - TextInput, - View, - ViewPropTypes, -} from 'react-native'; - -export class EditableCell extends React.Component { - constructor(props) { - super(props); - this.state = { - value: 'N/A', - }; - this.componentWillMount = this.componentWillMount.bind(this); - this.onEndEditing = this.onEndEditing.bind(this); - } - - componentWillMount() { - this.setState({ - value: String(this.props.value), - }); - } - - componentWillReceiveProps(nextProps) { - if (nextProps.value !== this.state.value) { - this.setState({ - value: String(nextProps.value), - }); - } - } - - onEndEditing() { - // If the field is cleared, write null to property - const newValue = this.state.value === '' ? null : this.state.value; - this.props.onEndEditing(this.props.target, newValue); - } - - render() { - const { style, width, textStyle, refCallback, ...textInputProps } = this.props; - return ( - - this.setState({ value: text })} - onEndEditing={this.onEndEditing} - value={this.state.value} - /> - - ); - } -} - -EditableCell.propTypes = { - style: ViewPropTypes.style, - refCallback: PropTypes.func, - textStyle: TextInput.propTypes.style, - width: PropTypes.number, - onEndEditing: PropTypes.func, - target: PropTypes.object, - value: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), -}; - -EditableCell.defaultProps = { - width: 1, - value: 'N/A', -}; - -const defaultStyles = StyleSheet.create({ - cell: { - flex: 1, - justifyContent: 'center', - }, - text: { - right: -9, // This is to account for RN issue 1287, see https://github.com/facebook/react-native/issues/1287 - }, -}); diff --git a/src/TextInputCell.js b/src/TextInputCell.js new file mode 100644 index 0000000..8679569 --- /dev/null +++ b/src/TextInputCell.js @@ -0,0 +1,146 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { View, TextInput } from 'react-native' + +import Cell from './Cell' +import RefContext from './RefContext' + +import { getAdjustedStyle } from './utilities' + +/** + * Renders a cell that on press or focus contains a TextInput for + * editing values. + * + * @param {string|number} value The value to render in cell + * @param {string|number} rowKey Unique key associated to row cell is in + * @param {string|number} columnKey Unique key associated to column cell is in + * @param {bool} isDisabled If `true` will render a plain Cell element with no interaction + * @param {String} placeholderColour Placholder text colour + * @param {func} dispatch Reducer dispatch callback for handling actions + * @param {Object} viewStyle Style object for the wrapping View component + * @param {Object} textStyle Style object for the inner Text component + * @param {Object} textInputStyle Style object for TextInput component. + * @param {Number} width Optional flex property to inject into styles. + * @param {bool} debug Logs rendering of this component. + * @param {string} keyboardType Type of keyboard for the TextInput. + * @param {string} placeholder placeholder text + * @param {Object} cellTextStyle text style for the disabled Cell component. + * @param {Bool} isLastCell Indicator if this cell is last in a row, + * removing the borderRight, + * @param {func} editAction Action creator for handling editing of this cell. + * `(newValue, rowKey, columnKey) => {...}` + * @param {String} underlineColor Underline colour of TextInput on Android. + */ +const TextInputCell = React.memo( + ({ + value, + rowKey, + columnKey, + isDisabled, + placeholderColour, + editAction, + dispatch, + isLastCell, + width, + debug, + keyboardType, + placeholder, + viewStyle, + rowIndex, + textInputStyle, + cellTextStyle, + underlineColor, + }) => { + if (debug) console.log(`- TextInputCell: ${value}`) + + const usingPlaceholder = placeholder && !value + + const { focusNextCell, getRefIndex, getCellRef } = React.useContext( + RefContext + ) + const refIndex = getRefIndex(rowIndex, columnKey) + + const onEdit = newValue => dispatch(editAction(newValue, rowKey, columnKey)) + const focusNext = () => focusNextCell(refIndex) + + // Render a plain Cell if disabled. + if (isDisabled) { + return ( + + ) + } + + const internalViewStyle = getAdjustedStyle(viewStyle, width, isLastCell) + const internalTextStyle = getAdjustedStyle(textInputStyle, width) + + // Render a Cell with a textInput. + return ( + + + + ) + } +) + +TextInputCell.propTypes = { + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + columnKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) + .isRequired, + isDisabled: PropTypes.bool, + placeholderColour: PropTypes.string, + editAction: PropTypes.func.isRequired, + dispatch: PropTypes.func.isRequired, + cellTextStyle: PropTypes.object, + viewStyle: PropTypes.object, + width: PropTypes.number, + textInputStyle: PropTypes.object, + isLastCell: PropTypes.bool, + debug: PropTypes.bool, + placeholder: PropTypes.string, + rowIndex: PropTypes.number.isRequired, + underlineColor: PropTypes.string, + keyboardType: PropTypes.oneOf([ + 'default', + 'number-pad', + 'decimal-pad', + 'numeric', + 'email-address', + 'phone-pad', + ]), +} + +TextInputCell.defaultProps = { + value: '', + isDisabled: false, + viewStyle: {}, + cellTextStyle: {}, + textInputStyle: {}, + isLastCell: false, + width: 0, + debug: false, + keyboardType: 'numeric', + placeholder: '', + placeholderColour: '#CDCDCD', + underlineColor: '#CDCDCD', +} + +export default TextInputCell