-
-
Save DragonOsman/3c2b4a2b384cff1382b2c101e48ac60a to your computer and use it in GitHub Desktop.
React Calculator component code plus html markup and Sass
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from "react"; | |
import Keypad from "./Keypad"; | |
import Display from "./Display"; | |
import { create, all } from "mathjs"; | |
const App = props => { | |
const [currentValue, setCurrentValue] = useState("0"); | |
const [storedValue, setStoredValue] = useState(""); | |
const [reciprocalClicked, setReciprocalClicked] = useState(false); | |
const [percentageClicked, setPercentageClicked] = useState(false); | |
const [squareRootClicked, setSquareRootClicked] = useState(false); | |
const [equalsClicked, setEqualsClicked] = useState(false); | |
const [backSpaceClicked, setBackSpaceClicked] = useState(false); | |
const [input, setInput] = useState([]); | |
const operators = ["+", "-", "*", "/"]; | |
const handleNumberClick = event => { | |
const button = event.target; | |
let newInput = [...input]; | |
if (currentValue === "0" && button.textContent === "0") { | |
return null; | |
} else if ((currentValue === "0" && button.textContent !== "0") || | |
equalsClicked || reciprocalClicked || percentageClicked || squareRootClicked) { | |
setCurrentValue(button.textContent); | |
newInput.length = 0; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
// reset it to make sure other click handlers don't misunderstand | |
setReciprocalClicked(false); | |
setEqualsClicked(false); | |
setPercentageClicked(false); | |
setSquareRootClicked(false); | |
} else if (backSpaceClicked) { | |
if (currentValue === "0") { | |
setCurrentValue(button.textContent); | |
} else if (currentValue !== "0") { | |
setCurrentValue(`${currentValue}${button.textContent}`); | |
} | |
newInput[newInput.length - 1] = ""; | |
setBackSpaceClicked(false); | |
} | |
if (newInput.length > 0) { | |
if (operators.includes(newInput[newInput.length - 1])) { | |
setCurrentValue(button.textContent); | |
} else if (!isNaN(newInput[newInput.length - 1]) || newInput[newInput.length - 1] === ".") { | |
setCurrentValue(`${currentValue}${button.textContent}`); | |
} else if (newInput[newInput.length - 1].endsWith("^2)")) { | |
setCurrentValue(button.textContent); | |
newInput.length = 0; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
} | |
} | |
newInput = [...newInput, button.textContent]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handleEqualsClick = event => { | |
setEqualsClicked(true); | |
const config = { | |
epsilon: 1e-12, | |
matrix: "Matrix", | |
number: "number", | |
precision: 64, | |
predictable: false, | |
randomSeed: null | |
}; | |
const math = create(all, config); | |
const newInput = [...input, currentValue]; | |
setStoredValue(newInput.join("")); | |
const stored = storedValue; | |
try { | |
const calculatedValue = math.round(1000000000000 * math.evaluate(stored)) / 1000000000000; | |
setCurrentValue(`${calculatedValue}`); | |
} catch (err) { | |
console.log(`Error occurred: ${err}`); | |
} | |
setInput([]); | |
setStoredValue(`${stored}${event.target.textContent}`); | |
}; | |
const handleOperatorClick = event => { | |
const button = event.target; | |
let newInput = [...input]; | |
if (input.length > 0) { | |
// handle 2 or more operators clicked in a row | |
if (operators.includes(newInput[newInput.length - 1]) && button.textContent !== "-") { | |
// take the previously clicked operator(s) out of the input array | |
// and add in newly clicked one | |
newInput = newInput.filter(elem => !operators.includes(elem)); | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
} | |
} | |
// take the result of the previous evaluation for a new calculation | |
if (equalsClicked) { | |
// This means equals button was clicked (can be like this in other cases too but still) | |
// set input array and storedValue to equal the result from the | |
// previous calculation | |
newInput = [...newInput, currentValue]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
// reset to false to make sure other click handlers don't misunderstand | |
setEqualsClicked(false); | |
} else if (reciprocalClicked || percentageClicked || squareRootClicked) { | |
// We don't need to handle these specially, but we should still reset them here | |
// so that other click handlers don't think they were clicked when they weren't | |
setReciprocalClicked(false); | |
setPercentageClicked(false); | |
setSquareRootClicked(false); | |
} | |
newInput = [...newInput, button.textContent]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handlePercentageClick = () => { | |
setPercentageClicked(true); | |
let newInput = [...input]; | |
// remove value from currentValue by itsef from input array | |
// and leave it only inside parentheses of percentage operation | |
for (let i = 0; i < currentValue.length; i++) { | |
newInput.pop(); | |
setInput(newInput); | |
} | |
newInput = [...newInput, `(${currentValue}/100)`]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handleSquareClick = () => { | |
// remove value from currentValue by itsef from input array | |
// and leave it only inside parentheses of square operation | |
let newInput = [...input]; | |
for (let i = 0; i < currentValue.length; i++) { | |
newInput.pop(); | |
setInput(newInput); | |
} | |
newInput = [...newInput, `((${currentValue})^2)`]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handleSquareRootClick = () => { | |
let newInput = [...input]; | |
setSquareRootClicked(true); | |
// remove value from currentValue by itsef from input array | |
// and leave it only inside parentheses of square root operation | |
for (let i = 0; i < currentValue.length; i++) { | |
newInput.pop(); | |
setInput(newInput); | |
} | |
newInput = [...newInput, `sqrt(${currentValue})`]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handleClearClick = () => { | |
setCurrentValue("0"); | |
setStoredValue(""); | |
const newInput = [...input]; | |
newInput.length = 0; | |
setInput(newInput); | |
}; | |
const handleClearEntryClick = () => { | |
setCurrentValue("0"); | |
}; | |
const handleBackSpaceClick = () => { | |
setBackSpaceClicked(true); | |
if (currentValue.length === 1) { | |
setCurrentValue("0"); | |
} else if (currentValue.length > 1) { | |
// set currentValue to a string with the last element cut off | |
const newValue = currentValue.slice(0, currentValue.length - 1); | |
setCurrentValue(newValue); | |
} | |
}; | |
const handleReciprocalClick = () => { | |
setReciprocalClicked(true); | |
let newInput = [...input]; | |
// remove value from currentValue by itsef from input array | |
// and leave it only inside parentheses of reciprocal operation | |
for (let i = 0; i < currentValue.length; i++) { | |
newInput.pop(); | |
setInput(newInput); | |
} | |
newInput = [...newInput, `(1/${currentValue})`]; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
}; | |
const handleDecimalClick = event => { | |
const button = event.target; | |
let newInput = [...input]; | |
if (currentValue.includes(button.textContent)) { | |
return null; | |
} | |
setCurrentValue(currentValue.concat(button.textContent)); | |
let decimalCount = 0; | |
for (let i = 0; i < currentValue.length; i++) { | |
if (currentValue.charAt(i) === ".") { | |
decimalCount++; | |
} | |
} | |
if (decimalCount > 1) { | |
for (let i = currentValue.indexOf(".") + 1; i < currentValue.length; i++) { | |
if (currentValue.charAt(i) === ".") { | |
currentValue.replace(".", ""); | |
} | |
} | |
} | |
if (currentValue === "0" && !newInput.includes("0")) { | |
newInput = [...newInput, currentValue]; | |
setInput(newInput); | |
} | |
newInput = [...newInput, button.textContent]; | |
setInput(newInput); | |
}; | |
const handleSignSwitchClick = () => { | |
if (Math.sign(Number(currentValue)) === 1) { | |
setCurrentValue(`-${currentValue}`); | |
const newInput = [...input]; | |
newInput[newInput.length - 1] = `-${newInput[newInput.length - 1]}`; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
} else if (Math.sign(Number(currentValue)) === -1) { | |
const positiveNum = Math.abs(Number(currentValue)); | |
setCurrentValue(positiveNum.toString()); | |
const newInput = [...input]; | |
newInput[newInput.length - 1] = `${Math.abs(Number(newInput[newInput.length - 1]))}`; | |
setInput(newInput); | |
setStoredValue(newInput.join("")); | |
} | |
}; | |
const buttons = [{ | |
name: "percentage", | |
value: "%", | |
type: "function", | |
id: "percentage", | |
className: "function keypad-button", | |
clickHandler: handlePercentageClick | |
}, { | |
name: "clear-entry", | |
value: "CE", | |
type: "effect", | |
id: "clear-entry", | |
className: "effect keypad-button", | |
clickHandler: handleClearEntryClick | |
}, { | |
name: "clear", | |
value: "C", | |
type: "effect", | |
id: "clear", | |
className: "effect keypad-button", | |
clickHandler: handleClearClick | |
}, { | |
name: "backspace", | |
value: "\u232b", | |
type: "effect", | |
id: "backspace", | |
className: "effect keypad-button", | |
clickHandler: handleBackSpaceClick | |
}, { | |
name: "reciprocal-function", | |
value: "1/𝑥", | |
type: "function", | |
id: "reciprocal", | |
className: "function keypad-button", | |
clickHandler: handleReciprocalClick | |
}, { | |
name: "square-function", | |
value: "𝑥²", | |
type: "function", | |
id: "square", | |
className: "function keypad-button", | |
clickHandler: handleSquareClick | |
}, { | |
name: "square-root-function", | |
value: "²√𝑥", | |
type: "function", | |
id: "square-root", | |
className: "function keypad-button", | |
clickHandler: handleSquareRootClick | |
}, { | |
name: "divide", | |
value: "/", | |
type: "operator", | |
id: "divide", | |
className: "operator keypad-button", | |
clickHandler: handleOperatorClick | |
}, { | |
name: "number-button", | |
value: "7", | |
type: "number", | |
id: "seven", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "8", | |
type: "number", | |
id: "eight", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "9", | |
type: "number", | |
id: "nine", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "multiply", | |
value: "*", | |
type: "operator", | |
id: "multiply", | |
className: "operator keypad-button", | |
clickHandler: handleOperatorClick | |
}, { | |
name: "number-button", | |
value: "4", | |
type: "number", | |
id: "four", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "5", | |
type: "number", | |
id: "five", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "6", | |
type: "number", | |
id: "six", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "minus", | |
value: "-", | |
type: "operator", | |
id: "subtract", | |
className: "operator keypad-button", | |
clickHandler: handleOperatorClick | |
}, { | |
name: "number-button", | |
value: "1", | |
type: "number", | |
id: "one", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "2", | |
type: "number", | |
id: "two", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "number-button", | |
value: "3", | |
type: "number", | |
id: "three", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "add", | |
value: "+", | |
type: "operator", | |
id: "add", | |
className: "operator keypad-button", | |
clickHandler: handleOperatorClick | |
}, { | |
name: "sign-switch", | |
value: "±", | |
type: "effect", | |
id: "sign-switch", | |
className: "number-helper keypad-button", | |
clickHandler: handleSignSwitchClick | |
}, { | |
name: "number-button", | |
value: "0", | |
type: "number", | |
id: "zero", | |
className: "number keypad-button", | |
clickHandler: handleNumberClick | |
}, { | |
name: "decimal", | |
value: ".", | |
type: "effect", | |
id: "decimal", | |
className: "number-helper keypad-button", | |
clickHandler: handleDecimalClick | |
}, { | |
name: "equals", | |
value: "=", | |
type: "calculation-submit", | |
id: "equals", | |
className: "calculation-submit keypad-button", | |
clickHandler: handleEqualsClick | |
}]; | |
return ( | |
<React.Fragment> | |
<Display | |
storedValue={storedValue} | |
currentValue={currentValue} | |
/> | |
<div id="keypad"> | |
{buttons.map((object, index) => | |
<Keypad | |
key={index} | |
className={object.className} | |
id={object.id} | |
name={object.name} | |
value={object.value} | |
clickHandler={object.clickHandler} | |
/> | |
)} | |
</div> | |
</React.Fragment> | |
); | |
}; | |
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import PropTypes from "prop-types"; | |
const Display = props => { | |
return ( | |
<div id="screen"> | |
<div id="formula"> | |
<p className="stored-value">{props.storedValue}</p> | |
</div> | |
<div id="display"> | |
<p className="current-value">{props.currentValue}</p> | |
</div> | |
</div> | |
); | |
}; | |
Display.propTypes = { | |
storedValue: PropTypes.string.isRequired, | |
currentValue: PropTypes.string.isRequired | |
}; | |
export default Display; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en-us"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<meta name="theme-color" content="#000000" /> | |
<meta | |
name="description" | |
content="Web site created using create-react-app" | |
/> | |
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Open+Sans:400,600" /> | |
<link rel="shortcut icon" href="../favicon.ico" /> | |
<link rel="icon" type="image/gif" href="../animated_favicon1.gif" /> | |
<meta name="author" content="Osman Zakir" /> | |
<title>DragonOsman JavaScript Calculator Project</title> | |
<script src="https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js" async></script> | |
</head> | |
<body> | |
<main> | |
<noscript>You need to enable JavaScript to run this app.</noscript> | |
<div id="root"></div> | |
</main> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import ReactDOM from "react-dom"; | |
import App from "./js/components/App"; | |
import "./styles/style.css"; | |
const rootDiv = document.getElementById("root"); | |
ReactDOM.render(<App />, rootDiv); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import Button from "./Button"; | |
import PropTypes from "prop-types"; | |
const Keypad = props => { | |
return ( | |
<Button | |
className={props.className} | |
id={props.id} | |
name={props.name} | |
value={props.value} | |
clickHandler={props.clickHandler} | |
/> | |
); | |
}; | |
Keypad.propTypes = { | |
className: PropTypes.string.isRequired, | |
id: PropTypes.string.isRequired, | |
name: PropTypes.string.isRequired, | |
value: PropTypes.string.isRequired, | |
clickHandler: PropTypes.func.isRequired | |
}; | |
export default Keypad; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$display-height: 28px; | |
$formula-height: 20px; | |
$screen-height: calc($display-height + $formula-height); | |
*, | |
*::after, | |
*::before { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
html { | |
font-family: "Digital-7", "Open Sans", sans-serif; | |
src: url("digital-7.ttf") format("truetype"), | |
url("digital-7_1.woff") format("woff"); | |
} | |
body { | |
background-color: #000; | |
} | |
main { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
& #root { | |
width: 50%; | |
background-color: #800000; | |
border: 3px solid #800000; | |
& #screen { | |
background-color: #a9a9a9; | |
width: 100%; | |
height: $screen-height; | |
& #formula { | |
width: 100%; | |
font-size: 13px; | |
color: #000; | |
text-align: right; | |
background-color: #a9a9a9; | |
height: $formula-height; | |
} | |
& #display { | |
font-size: 20px; | |
width: 100%; | |
color: #000; | |
text-align: right; | |
background-color: #a9a9a9; | |
height: $display-height; | |
text-align: right; | |
bottom: 0; | |
} | |
} | |
& #keypad { | |
width: 100%; | |
button { | |
width: 25%; | |
height: 100px; | |
background-color: #808080; | |
font-weight: bold; | |
} | |
.number { | |
background-color: #fff; | |
} | |
#equals { | |
background-color: #a62c2b | |
} | |
#decimal, #sign-switch { | |
background-color: #fff; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment