1- import React , { Fragment , useState } from 'react'
2- import ReactResizeDetector from 'react-resize-detector'
3-
4- // operators
5- const ADDITION = '+'
6- const SUBTRACTION = '-'
7- const MULTIPLICATION = 'x'
8- const DIVISION = '/'
9- const MODULUS = '%'
1+ import React , { useState , useEffect } from 'react'
2+ import './index.css'
3+ import { isArray } from 'util'
104
115const Calculator = ( ) => {
12- // the initial values object
13- const initialCalculation = {
14- left : '0' ,
15- operator : null ,
16- right : null
17- }
18-
19- // TODO after the equals button is pressed, new number presses should clear the answer
6+ // operator constants
7+ const ADDITION = '+'
8+ const SUBTRACTION = '-'
9+ const MULTIPLICATION = '*'
10+ const DIVISION = '/'
11+ const MODULUS = '%'
12+
13+ // listens for the keydown event for the given key and calls the callback function
14+ const onKeyDown = ( key , callback ) => {
15+ const onDown = ( e ) => {
16+ const eventKeyLower = e . key . toLowerCase ( )
17+ if ( isArray ( key ) ) {
18+ if ( key . find ( ( elem ) => elem . toLowerCase ( ) == eventKeyLower ) ) {
19+ callback ( )
20+ }
21+ } else if ( key . toLowerCase ( ) == eventKeyLower ) {
22+ callback ( )
23+ }
24+ }
2025
21- // TODO add keyboard input functionality
26+ useEffect ( ( ) => {
27+ window . addEventListener ( "keydown" , onDown )
28+ return ( ) => {
29+ window . removeEventListener ( "keydown" , onDown )
30+ }
31+ } , [ key ] )
32+ }
2233
23- // TODO css height match width - use padding-top and 0 height
34+ // Number Button (0-9)
35+ const NumberKey = ( { children :key } ) => {
36+ onKeyDown ( key , ( ) => onNumClick ( key ) )
37+ return (
38+ < td className = "button button--number" onClick = { ( ) => { onNumClick ( key ) } } > < div > { key } </ div > </ td >
39+ )
40+ }
2441
25- // TODO responsive font sizing - CSS @media only works based on viewport size, not container size
26- // - will need to use JS to watch the container width
42+ // Operator Button (plus, minus, divide, multiply, modulus)
43+ const OperatorKey = ( { children :key } ) => {
44+ onKeyDown ( key , ( ) => onOperatorClick ( key ) )
45+ return (
46+ < td className = "button button--operator" onClick = { ( ) => onOperatorClick ( key ) } > < div > { key } </ div > </ td >
47+ )
48+ }
2749
28- // TODO export CSS with component
50+ // Control Button (clear, delete, equals, decimal)
51+ const ControlKey = ( { children :value , onClick, listenKey, classModify, ...rest } ) => {
52+ onKeyDown ( listenKey || value , onClick )
53+ return (
54+ < td className = { "button button--control " + classModify } onClick = { onClick } { ...rest } > < div > { value } </ div > </ td >
55+ )
56+ }
2957
30- // create the calculation state
58+ // setup the state
59+ const initialCalculation = {
60+ left : null ,
61+ operator : null ,
62+ right : null ,
63+ answer : null
64+ }
3165 const [ calculation , setCalculation ] = useState ( initialCalculation )
3266
33- // destructure the state object into variables
34- const { left, operator, right } = calculation
35-
67+ // gets the left or right value based on if operator is set
3668 const getCurrentValue = ( ) => {
37- return operator ? right || '' : left
69+ return calculation . operator ? calculation . right : calculation . left
3870 }
3971
72+ // sets the left or right value based on if operator is set
4073 const setCurrentValue = ( value ) => {
4174 setCalculation ( {
4275 ...calculation ,
43- left : operator ? calculation . left : value ,
44- right : operator ? value : calculation . right
76+ answer : initialCalculation . answer ,
77+ left : calculation . operator ? calculation . left : value ,
78+ right : calculation . operator ? value : calculation . right
4579 } )
4680 }
4781
48- // handle a number being clicked
49- const onNumClick = e => {
50- // get the button value clicked
51- const buttonText = e . target . textContent
52-
53- // get the value to append
82+ // appends the number onto the current value
83+ const onNumClick = ( number ) => {
5484 let value = getCurrentValue ( )
55-
56- if ( value === '0' || isNaN ( value ) || ! isFinite ( value ) ) {
57- value = buttonText
58- } else {
59- value = value + buttonText
60- }
61-
62- setCurrentValue ( value )
85+ setCurrentValue ( ( value === null || value === '0' || isNaN ( value ) || ! isFinite ( value ) ) ? number : value + number )
6386 }
6487
65- const onDecimalClick = e => {
66- let value = getCurrentValue ( )
67- if ( value . match ( / \. / ) ) {
68- return
69- } else if ( isNaN ( value ) || ! isFinite ( value ) ) {
70- value = '0.'
71- } else {
72- value = value + '.'
73- }
88+ // handle an operator being clicked
89+ const onOperatorClick = ( operator ) => {
90+ setCalculation ( {
91+ ... calculation ,
92+ answer : initialCalculation . answer ,
93+ left : calculation . left || calculation . answer ,
94+ operator
95+ } )
96+ }
7497
75- setCurrentValue ( value )
98+ // clears the entire calculation
99+ const onClearClick = ( ) => {
100+ setCalculation ( {
101+ ...initialCalculation
102+ } )
76103 }
77104
78- const onOperatorClick = e => {
105+ // clears the current entry of the calculation
106+ const onClearEntryClick = ( ) => {
79107 setCalculation ( {
80108 ...calculation ,
81- operator : e . target . textContent
109+ answer : initialCalculation . answer ,
110+ left : calculation . operator ? calculation . left : initialCalculation . left ,
111+ right : initialCalculation . right
82112 } )
83113 }
84114
85- const onDeleteClick = e => {
115+ // backspace on the current entry of the calculation
116+ const onDeleteClick = ( ) => {
86117 let value = getCurrentValue ( )
118+ setCurrentValue ( value === null || value . length === 1 ? null : value . substring ( 0 , value . length - 1 ) )
119+ }
87120
88- if ( value . length === 1 ) {
89- value = '0'
90- } else {
91- value = value . substring ( 0 , value . length - 1 ) ;
92- }
93-
94- setCurrentValue ( value )
121+ // adds a decimal to the current entry of the calculation
122+ const onDecimalClick = ( ) => {
123+ let value = getCurrentValue ( )
124+ if ( value !== null && value . match ( / \. / ) ) return
125+ setCurrentValue ( ( value === null || value === '0' || isNaN ( value ) || ! isFinite ( value ) ) ? '0.' : value + '.' )
95126 }
96127
97- const onEqualsClick = e => {
98- if ( right === null ) {
128+ // sets the answer to the calculation as the new left entry value
129+ const onEqualsClick = ( ) => {
130+ // if no right value, dont calculate
131+ if ( calculation . right === null ) {
99132 return
100133 }
101134
102135 let value ;
103- const leftNumber = Number ( left )
104- const rightNumber = Number ( right )
105- switch ( operator ) {
136+ const leftNumber = Number ( calculation . left )
137+ const rightNumber = Number ( calculation . right )
138+ switch ( calculation . operator ) {
106139 case ADDITION :
107140 value = leftNumber + rightNumber
108141 break
@@ -124,46 +157,64 @@ const Calculator = () => {
124157
125158 setCalculation ( {
126159 ...calculation ,
127- left : value . toString ( ) ,
160+ left : null ,
128161 operator : null ,
129- right : null
130- } )
131- }
132-
133- const onClearEntryClick = e => {
134- setCalculation ( {
135- ...calculation ,
136- left : operator ? calculation . left : '0' ,
137- right : operator ? '0' : null
162+ right : null ,
163+ answer : value . toString ( )
138164 } )
139165 }
140166
141- const onClearClick = e => {
142- setCalculation ( {
143- ...initialCalculation
144- } )
145- }
146-
147- const getDisplayValue = ( ) => {
148- let text = right || left
149- return text . length > 16 ? Number ( text ) . toExponential ( ) . toString ( ) : text
150- }
167+ const getDisplayText = ( ) => {
168+ if ( calculation . answer ) {
169+ return calculation . answer
170+ }
151171
152- const onResize = ( width , height ) => {
153- console . log ( `width: ${ width } ` )
154- console . log ( `height: ${ height } ` )
155- console . log ( '------------------' )
172+ return `${ calculation . left || '0' } ${ calculation . operator || '' } ${ calculation . right || '' } `
156173 }
157174
158175 return (
159176 < div className = "calculator" >
160- < ReactResizeDetector handleWidth handleHeight onResize = { this . onResize } />
161177 < div className = "display" >
162- < p className = "display--calculation" > test
163- { operator && < span > { left . length > 16 ? Number ( left ) . toExponential ( ) : left } { operator } </ span > }
164- </ p >
165- < h4 className = "display--value" > { getDisplayValue ( ) } </ h4 >
178+ < div className = "display--inner" >
179+ < h4 className = "display--value" > { getDisplayText ( ) } </ h4 >
180+ </ div >
166181 </ div >
182+ < table className = "keypad" >
183+ < tbody >
184+ < tr >
185+ < ControlKey onClick = { onClearClick } listenKey = "escape" width = "25%" > C</ ControlKey >
186+ < ControlKey onClick = { onClearEntryClick } listenKey = "delete" width = "50%" colSpan = { 2 } > CE</ ControlKey >
187+ < OperatorKey width = "25%" > { MODULUS } </ OperatorKey >
188+ </ tr >
189+ < tr >
190+ < NumberKey > 1</ NumberKey >
191+ < NumberKey > 2</ NumberKey >
192+ < NumberKey > 3</ NumberKey >
193+ < OperatorKey > { DIVISION } </ OperatorKey >
194+ </ tr >
195+ < tr >
196+ < NumberKey > 4</ NumberKey >
197+ < NumberKey > 5</ NumberKey >
198+ < NumberKey > 6</ NumberKey >
199+ < OperatorKey > { MULTIPLICATION } </ OperatorKey >
200+ </ tr >
201+ < tr >
202+ < NumberKey > 7</ NumberKey >
203+ < NumberKey > 8</ NumberKey >
204+ < NumberKey > 9</ NumberKey >
205+ < OperatorKey > { SUBTRACTION } </ OperatorKey >
206+ </ tr >
207+ < tr >
208+ < ControlKey onClick = { onDeleteClick } listenKey = "backspace" > del</ ControlKey >
209+ < NumberKey > 0</ NumberKey >
210+ < ControlKey onClick = { onDecimalClick } > .</ ControlKey >
211+ < OperatorKey > { ADDITION } </ OperatorKey >
212+ </ tr >
213+ < tr >
214+ < ControlKey onClick = { onEqualsClick } listenKey = { [ '=' , 'Enter' ] } classModify = "button--control-equals" colSpan = { 4 } > =</ ControlKey >
215+ </ tr >
216+ </ tbody >
217+ </ table >
167218 </ div >
168219 )
169220}
0 commit comments