A react calculator
A Pen by jessechumo on CodePen.
A react calculator
A Pen by jessechumo on CodePen.
<div id="challenge"></div> |
const isOperator = /[x/+‑]/, | |
endsWithOperator = /[-x+/]$/, | |
endsWithNegativeSign = /[x/+][-]$/; | |
class Keypad extends React.Component { | |
constructor(props) { | |
super(props); | |
} | |
render() { | |
return ( | |
<div className="keypad"> | |
<div className="top"> | |
<button | |
id="clear" | |
value="AC" | |
onClick={this.props.clickedClear} | |
class="AC-0 pad" | |
> | |
AC | |
</button> | |
<button | |
id="divide" | |
value="/" | |
onClick={this.props.clickedOperator} | |
class="number-operator pad op" | |
> | |
/ | |
</button> | |
<button | |
id="multiply" | |
value="x" | |
onClick={this.props.clickedOperator} | |
class="number-operator pad op" | |
> | |
x | |
</button> | |
</div> | |
<div className="middle"> | |
<div> | |
<button | |
id="seven" | |
value={7} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
7 | |
</button> | |
<button | |
id="eight" | |
value={8} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
8 | |
</button> | |
<button | |
id="nine" | |
value={9} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
9 | |
</button> | |
<button | |
id="subtract" | |
value="-" | |
onClick={this.props.clickedOperator} | |
class="number-operator pad op" | |
> | |
- | |
</button> | |
</div> | |
<div> | |
<button | |
id="four" | |
value={4} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
4 | |
</button> | |
<button | |
id="five" | |
value={5} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
5 | |
</button> | |
<button | |
id="six" | |
value={6} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
6 | |
</button> | |
<button | |
id="add" | |
value="+" | |
onClick={this.props.clickedOperator} | |
class="number-operator pad op" | |
> | |
+ | |
</button> | |
</div> | |
</div> | |
<div className="bottom"> | |
<div className="bottom-left"> | |
<div className="one-two-three"> | |
<button | |
id="one" | |
value={1} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
1 | |
</button> | |
<button | |
id="two" | |
value={2} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
2 | |
</button> | |
<button | |
id="three" | |
value={3} | |
onClick={this.props.clickedNumber} | |
class="number-operator pad" | |
> | |
3 | |
</button> | |
</div> | |
<div className="zero-decimal"> | |
<button | |
id="zero" | |
value={0} | |
onClick={this.props.clickedNumber} | |
class="AC-0 pad" | |
> | |
0 | |
</button> | |
<button | |
id="decimal" | |
value="." | |
onClick={this.props.clickedDecimal} | |
class="number-operator pad" | |
> | |
. | |
</button> | |
</div> | |
</div> | |
<div className="bottom-right"> | |
<button | |
id="equals" | |
value="=" | |
onClick={this.props.clickedEquals} | |
class="equals pad" | |
> | |
= | |
</button> | |
</div> | |
</div> | |
</div> | |
); | |
} | |
} | |
class App extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
currentValue: "0", // 0 or an operator or a value | |
prevValue: "", | |
formula: "", // a string of the expression | |
currentSign: "pos" | |
}; | |
// bindings | |
this.limitWarning = this.limitWarning.bind(this); | |
this.clickedNumber = this.clickedNumber.bind(this); | |
this.clickedOperator = this.clickedOperator.bind(this); | |
this.clickedDecimal = this.clickedDecimal.bind(this); | |
this.clickedEquals = this.clickedEquals.bind(this); | |
this.clickedClear = this.clickedClear.bind(this); | |
} | |
// methods | |
limitWarning() { | |
this.setState({ | |
currentValue: "Digit Limit Reached", | |
prevValue: "Digit Limit Reached" | |
}); | |
} | |
clickedClear(e) { | |
this.setState({ | |
currentValue: "0", | |
prevValue: "", | |
formula: "" | |
}); | |
} | |
clickedNumber(e) { | |
if (!this.state.currentValue.includes("Limit")) { | |
const { currentValue, formula, evaluated } = this.state; | |
const val = e.target.value; | |
this.setState({ evaluated: false }); | |
if (currentValue.length > 21) { | |
this.limitWarning(); | |
} else if (evaluated) { | |
this.setState({ | |
currentValue: val, | |
formula: val !== "0" ? val : "" | |
}); | |
} else { | |
this.setState({ | |
currentValue: | |
currentValue === "0" || isOperator.test(currentValue) | |
? val | |
: currentValue + val, | |
formula: | |
currentValue === "0" && val === "0" | |
? formula | |
: /([^.0-9]0)$/.test(formula) | |
? formula.slice(0, -1) + val | |
: formula + val | |
}); | |
} | |
} | |
} | |
clickedOperator(e) { | |
if (!this.state.currentValue.includes('Limit')) { | |
const val = e.target.value; | |
const { formula, prevValue, evaluated } = this.state; | |
this.setState({ currentValue: val, evaluated: false }); | |
if (evaluated) { | |
this.setState({ formula: prevValue + val }); | |
} else if (!endsWithOperator.test(formula)) { | |
this.setState({ | |
prevValue: formula, | |
formula: formula + val | |
}); | |
} else if (!endsWithNegativeSign.test(formula)) { | |
this.setState({ | |
formula: (endsWithNegativeSign.test(formula + val)) ? (formula + val) : (prevValue + val) | |
}); | |
} else if (val !== "-") { | |
this.setState({ | |
formula: prevValue + val | |
}); | |
} | |
} | |
} | |
clickedDecimal() { | |
if (this.state.evaluated === true) { | |
this.setState({ | |
currentValue: "0.", | |
formula: "0." | |
}); | |
} else if ( | |
!this.state.currentValue.includes(".") && | |
!this.state.currentValue.includes("Limit") | |
) { | |
this.setState({ evaluated: false }); | |
if(this.state.currentValue.length > 21) { | |
this.limitWarning; | |
} else if ( | |
endsWithOperator.test(this.state.formula) || | |
(this.state.currentValue === '0' && this.state.formula === '') | |
) { | |
this.setState({ | |
currentValue: '0.', | |
formula: this.state.formula + '0.' | |
}); | |
} else { | |
this.setState({ | |
currentValue: this.state.formula.match(/(-?\d+\.?\d*)$/)[0] + '.', | |
formula: this.state.formula + '.' | |
}); | |
} | |
} | |
} | |
clickedEquals(e) { | |
if (!this.state.currentValue.includes("Limit")) { | |
if(this.state.formula !== "") { | |
let expression = this.state.formula; | |
while (endsWithOperator.test(expression)) { | |
expression = expression.slice(0, -1); | |
} | |
expression = expression.replace(/x/g, "*").replace(/‑/g, "-"); | |
let answer = Math.round(1000000000000 * eval(expression)) / 1000000000000; | |
this.setState({ | |
currentValue: answer.toString(), | |
formula: | |
expression.replace(/\*/g, "⋅").replace(/-/g, "‑") + "=" + answer, | |
prevValue: answer, | |
evaluated: true | |
}); | |
} | |
} | |
} | |
render() { | |
return ( | |
<div> | |
<div id="calculator"> | |
<div className="display"> | |
<div id="formula">{this.state.formula.replace(/x/g, "⋅")}</div> | |
<div id="display">{this.state.currentValue}</div> | |
</div> | |
<Keypad | |
clickedNumber={this.clickedNumber} | |
clickedOperator={this.clickedOperator} | |
clickedClear={this.clickedClear} | |
clickedDecimal={this.clickedDecimal} | |
clickedEquals={this.clickedEquals} | |
/> | |
</div> | |
<div id="zeek"> | |
<a href="https://codepen.io/odkpatrick" target="_blank">zeek</a> | |
</div> | |
</div> | |
); | |
} | |
} | |
ReactDOM.render(<App />, document.getElementById("challenge")); |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js"></script> | |
<script src="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js"></script> |
@import 'https://fonts.googleapis.com/css?family=Share+Tech+Mono'; | |
* { | |
border-box: content-box; | |
margin: 0; | |
padding: 0; | |
font-family: 'Share Tech Mono', monospace;; | |
} | |
#challenge { | |
height: 100vh; | |
display: flex; | |
justify-content: top; | |
align-items: center; | |
} | |
#calculator { | |
width: 400px; | |
background-color: #000; | |
padding: 2px; | |
font-size: 32px; | |
} | |
.display { | |
text-align: right; | |
margin-bottom: 15px; | |
padding: 10px 15px; | |
color: #dedede; | |
} | |
#formula, | |
#display { | |
height: 20px; | |
color: #fff; | |
padding: 1px solid #000; | |
} | |
.keypad { | |
display: flex; | |
flex-direction: column; | |
border: 1px solid #000; | |
border-width: 1px; | |
} | |
.top, | |
.middle, | |
.bottom, | |
.bottom-left { | |
width: 100%; | |
display: grid; | |
} | |
.top { | |
grid-template-columns: 2fr 1fr 1fr; | |
} | |
.middle { | |
grid-template-rows: 1fr 1fr; | |
} | |
.middle div { | |
display: grid; | |
grid-template-columns: 1fr 1fr 1fr 1fr; | |
} | |
.bottom { | |
grid-template-columns: 3fr 1fr; | |
} | |
.bottom-left { | |
grid-template-rows: 1fr 1fr; | |
} | |
.one-two-three { | |
display: grid; | |
grid-template-columns: 1fr 1fr 1fr; | |
} | |
.zero-decimal { | |
display: grid; | |
grid-template-columns: 2fr 1fr; | |
} | |
.AC-0, | |
.number-operator, | |
.equals { | |
text-align: center; | |
border: 1px solid #000; | |
} | |
.pad { | |
height: 65px; | |
font-size: 20px; | |
} | |
.equals { | |
width: 100%; | |
height: 100%; | |
} | |
.pad:hover { | |
border: 1px solid #FFF; | |
} | |
.number-operator, #zero { | |
background-color: #ffff; | |
} | |
.op { | |
background-color: #F9E79F; | |
} | |
#equals { | |
background-color: #F9E79F; | |
} | |
#clear { | |
background-color: #F9E79F; | |
} | |
a { | |
display: block; | |
text-align: right; | |
padding: 5px; | |
color: green; | |
text-decoration: none; | |
} | |
a:hover { | |
text-decoration: none; | |
} |