From 05069253a13d15eb6089a51424ab278418696b46 Mon Sep 17 00:00:00 2001 From: Nicolas Gallagher Date: Fri, 9 Jun 2017 14:51:32 -0700 Subject: [PATCH] Add Calculator example app --- docs/storybook/demos/Calculator/Calculator.js | 441 ++++++++++++++++++ .../demos/Calculator/CalculatorExample.js | 14 + 2 files changed, 455 insertions(+) create mode 100644 docs/storybook/demos/Calculator/Calculator.js create mode 100644 docs/storybook/demos/Calculator/CalculatorExample.js diff --git a/docs/storybook/demos/Calculator/Calculator.js b/docs/storybook/demos/Calculator/Calculator.js new file mode 100644 index 00000000..a531d6be --- /dev/null +++ b/docs/storybook/demos/Calculator/Calculator.js @@ -0,0 +1,441 @@ +/* eslint-disable react/jsx-no-bind, react/prop-types */ + +// React Native version of https://codepen.io/mjijackson/pen/xOzyGX +import React from 'react'; +import { StyleSheet, Text, TouchableHighlight, View } from 'react-native'; + +/** + * KeyboardInput + */ +class KeyboardInput extends React.Component { + componentDidMount() { + document.addEventListener('keydown', this.handleKeyDown); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.handleKeyDown); + } + + render() { + return null; + } + + handleKeyDown = event => { + if (this.props.onKeyDown) { + this.props.onKeyDown(event); + } + }; +} + +/** + * CalculatorDisplay + */ +class CalculatorDisplay extends React.Component { + constructor(props, context) { + super(props, context); + this.state = { + scale: 1 + }; + } + + handleTextLayout = e => { + const { width, x } = e.nativeEvent.layout; + const maxWidth = width + x; + const newScale = maxWidth / width; + if (x < 0) { + this.setState({ scale: newScale }); + } else if (x > width) { + this.setState({ scale: 1 }); + } + }; + + render() { + const { value } = this.props; + const { scale } = this.state; + + const language = navigator.language || 'en-US'; + let formattedValue = parseFloat(value).toLocaleString(language, { + useGrouping: true, + maximumFractionDigits: 6 + }); + + const trailingZeros = value.match(/\.0*$/); + + if (trailingZeros) { + formattedValue += trailingZeros; + } + + return ( + + + + ); + } +} + +const calculatorDisplayStyles = StyleSheet.create({ + root: { + backgroundColor: '#1c191c', + flex: 1, + justifyContent: 'center' + }, + text: { + alignSelf: 'flex-end', + color: 'white', + //lineHeight: 130, + fontSize: '5.25em', + fontWeight: '100', + fontFamily: 'Roboto, sans-serif', + paddingHorizontal: 30, + //position: 'absolute', + right: 0, + transformOrigin: 'right' + } +}); + +/** + * CalculatorKey + */ +class CalculatorKey extends React.Component { + render() { + const { children, onPress, style, textStyle } = this.props; + + return ( + + + + ); + } +} + +const calculatorKeyStyles = StyleSheet.create({ + root: { + width: 80, + height: 80, + borderTopWidth: 1, + borderTopColor: '#777', + borderTopStyle: 'solid', + borderRightWidth: 1, + borderRightColor: '#666', + borderRightStyle: 'solid', + outline: 'none' + }, + text: { + fontWeight: '100', + fontFamily: 'Roboto, sans-serif', + lineHeight: 80, + textAlign: 'center' + } +}); + +const CalculatorOperations = { + '/': (prevValue, nextValue) => prevValue / nextValue, + '*': (prevValue, nextValue) => prevValue * nextValue, + '+': (prevValue, nextValue) => prevValue + nextValue, + '-': (prevValue, nextValue) => prevValue - nextValue, + '=': (prevValue, nextValue) => nextValue +}; + +/** + * Calculator + */ +class Calculator extends React.Component { + constructor(props, context) { + super(props, context); + + this.state = { + value: null, + displayValue: '0', + operator: null, + waitingForOperand: false + }; + } + + clearAll() { + this.setState({ + value: null, + displayValue: '0', + operator: null, + waitingForOperand: false + }); + } + + clearDisplay() { + this.setState({ + displayValue: '0' + }); + } + + clearLastChar() { + const { displayValue } = this.state; + + this.setState({ + displayValue: displayValue.substring(0, displayValue.length - 1) || '0' + }); + } + + toggleSign() { + const { displayValue } = this.state; + const newValue = parseFloat(displayValue) * -1; + + this.setState({ + displayValue: String(newValue) + }); + } + + inputPercent() { + const { displayValue } = this.state; + const currentValue = parseFloat(displayValue); + + if (currentValue === 0) return; + + const fixedDigits = displayValue.replace(/^-?\d*\.?/, ''); + const newValue = parseFloat(displayValue) / 100; + + this.setState({ + displayValue: String(newValue.toFixed(fixedDigits.length + 2)) + }); + } + + inputDot() { + const { displayValue } = this.state; + + if (!/\./.test(displayValue)) { + this.setState({ + displayValue: displayValue + '.', + waitingForOperand: false + }); + } + } + + inputDigit(digit) { + const { displayValue, waitingForOperand } = this.state; + + if (waitingForOperand) { + this.setState({ + displayValue: String(digit), + waitingForOperand: false + }); + } else { + this.setState({ + displayValue: displayValue === '0' ? String(digit) : displayValue + digit + }); + } + } + + performOperation(nextOperator) { + const { value, displayValue, operator } = this.state; + const inputValue = parseFloat(displayValue); + + if (value == null) { + this.setState({ + value: inputValue + }); + } else if (operator) { + const currentValue = value || 0; + const newValue = CalculatorOperations[operator](currentValue, inputValue); + + this.setState({ + value: newValue, + displayValue: String(newValue) + }); + } + + this.setState({ + waitingForOperand: true, + operator: nextOperator + }); + } + + handleKeyDown(event) { + let { key } = event; + + if (key === 'Enter') key = '='; + + if (/\d/.test(key)) { + event.preventDefault(); + this.inputDigit(parseInt(key, 10)); + } else if (key in CalculatorOperations) { + event.preventDefault(); + this.performOperation(key); + } else if (key === '.') { + event.preventDefault(); + this.inputDot(); + } else if (key === '%') { + event.preventDefault(); + this.inputPercent(); + } else if (key === 'Backspace') { + event.preventDefault(); + this.clearLastChar(); + } else if (key === 'Clear') { + event.preventDefault(); + + if (this.state.displayValue !== '0') { + this.clearDisplay(); + } else { + this.clearAll(); + } + } + } + + render() { + const { displayValue } = this.state; + + const clearDisplay = displayValue !== '0'; + const clearText = clearDisplay ? 'C' : 'AC'; + + return ( + + this.handleKeyDown(event)} /> + + + + + (clearDisplay ? this.clearDisplay() : this.clearAll())}> + {clearText} + + this.toggleSign()} style={calculatorStyles.keySign}> + ± + + this.inputPercent()} style={calculatorStyles.keyPercent}> + % + + + + this.inputDigit(0)} + style={calculatorStyles.key0} + textStyle={{ textAlign: 'left' }} + > + 0 + + this.inputDot()} + style={calculatorStyles.keyDot} + textStyle={calculatorStyles.keyDotText} + > + . + + this.inputDigit(1)}>1 + this.inputDigit(2)}>2 + this.inputDigit(3)}>3 + this.inputDigit(4)}>4 + this.inputDigit(5)}>5 + this.inputDigit(6)}>6 + this.inputDigit(7)}>7 + this.inputDigit(8)}>8 + this.inputDigit(9)}>9 + + + + this.performOperation('/')}>÷ + this.performOperation('*')}>× + this.performOperation('-')}>− + this.performOperation('+')}>+ + this.performOperation('=')}>= + + + + ); + } +} + +const DigitKey = props => + ; + +const FunctionKey = props => + ; + +const OperatorKey = props => + ; + +const calculatorStyles = StyleSheet.create({ + root: { + width: 320, + height: 520, + backgroundColor: 'black', + boxShadow: '0px 0px 20px 0px #aaa' + }, + keypad: { + height: 400, + flexDirection: 'row' + }, + inputKeys: { + width: 240 + }, + calculatorKeyText: { + fontFamily: 'Roboto, sans-serif', + fontSize: '1.75em', + fontWeight: '100' + }, + functionKeys: { + backgroundImage: 'linear-gradient(to bottom, rgba(202,202,204,1) 0%, rgba(196,194,204,1) 100%)', + flexDirection: 'row' + }, + functionKeyText: { + fontFamily: 'Roboto, sans-serif', + fontSize: '1.75em', + fontWeight: '100' + }, + digitKeys: { + backgroundColor: '#e0e0e7', + flexDirection: 'row', + flexWrap: 'wrap-reverse' + }, + digitKeyText: { + fontFamily: 'Roboto, sans-serif', + fontSize: '2em', + fontWeight: '100' + }, + operatorKeys: { + backgroundImage: 'linear-gradient(to bottom, rgba(252,156,23,1) 0%, rgba(247,126,27,1) 100%)' + }, + operatorKey: { + borderRightWidth: 0 + }, + operatorKeyText: { + color: 'white', + fontFamily: 'Roboto, sans-serif', + fontSize: '2.65em', + fontWeight: '100' + }, + keyMultiplyText: { + lineHeight: 50 + }, + key0: { + paddingLeft: 32, + width: 160 + }, + keyDot: { + overflow: 'hidden' + }, + keyDotText: { + fontFamily: 'Roboto, sans-serif', + fontSize: '4.375em', + fontWeight: '100', + marginTop: -10 + } +}); + +module.exports = Calculator; diff --git a/docs/storybook/demos/Calculator/CalculatorExample.js b/docs/storybook/demos/Calculator/CalculatorExample.js new file mode 100644 index 00000000..95938b50 --- /dev/null +++ b/docs/storybook/demos/Calculator/CalculatorExample.js @@ -0,0 +1,14 @@ +import React from 'react'; +import { storiesOf } from '@kadira/storybook'; +import Calculator from './Calculator'; +import { StyleSheet, View } from 'react-native'; + +const styles = StyleSheet.create({ + container: { alignItems: 'center', justifyContent: 'center', flex: 1 } +}); + +storiesOf('Example apps', module).add('Calculator', () => + + + +);