22
loading...
This website collects cookies to deliver better user experience
import React, { useEffect, useState } from "react";
import Display from "./Display";
import Keypad from "./Keypad";
const Calculator = () => {
...
return (
<div id="calculator-view" className={"flex column jc-center ai-center"}>
<div id="viewport" className={"flex column jc-sp-between ai-center"}>
<Display value={screenValue} />
<Keypad actionToPerform={handleActionToPerform} allClear={isScreenClear} />
</div>
</div >
)
}
Note that the ellipsis (...) in this code snippet means that we'll be injecting some state and functionality for the component soon later.
For now, let's just focus on the structure of the components themselves.
import React from "react";
const Display = (props) => {
const { value } = props;
return (
<div id="display" className="flex">
<input type="text" tabIndex="-1" value={value} />
</div>
)
}
export default Display;
import React from "react";
import Button from "./Button";
const Keypad = (props) => {
const { actionToPerform, allClear } = props;
...
const handleClickButton = (value, keyType) => {
actionToPerform(value, keyType);
}
return(
<div id="keypad" className="flex row jc-sp-around">
<div className="grid">
{functionKeys.map(
functionKey =>
<Button key={functionKey.label} label={functionKey.label} value={functionKey.value}
buttonStyle="fx-key" onClick={handleClickButton} type="fx" />
)}
{numericKeys.map(
numericKey =>
<Button key={numericKey} label={numericKey} value={numericKey}
buttonStyle="numeric-key" onClick={handleClickButton} type="numeric" />
)}
{lastRowKeys.map(
lastRowKey =>
<Button key={lastRowKey.label} label={lastRowKey.label} value={lastRowKey.value}
buttonStyle={lastRowKey.buttonStyle} onClick={handleClickButton} type={lastRowKey.type} />
)}
</div>
<div className="flex column jc-sp-btw">
{operatorKeys.map(
operatorKey =>
<Button key={operatorKey.label} label={operatorKey.label} value={operatorKey.value}
buttonStyle="op-key" onClick={handleClickButton} type="operator" />
)}
</div>
</div>
)
}
export default Keypad;
const numericKeys = [7, 8, 9, 4, 5, 6, 1, 2, 3];
const operatorKeys = [
{ label: "÷", value: "/" },
{ label: "×", value: "x" },
{ label: "-", value: "-" },
{ label: "+", value: "+" },
{ label: "=", value: "=" }
];
const functionKeys = [
{ label: allClear ? "AC" : "C", value: allClear ? "AC" : "C" },
{ label: "±", value: "+/-" },
{ label: "%", value: "%" }
];
const lastRowKeys = [
{ label: "0", value: "0", type: "numeric", buttonStyle: "numeric-key special" },
{ label: "·", value: ".", type: "fx", buttonStyle: "numeric-key" }
];
import React from "react";
const Button = (props) => {
const { value, type, buttonStyle, label, onClick } = props;
const handleButtonClick = () => {
onClick(value, type);
}
return (
<button name={value} className={buttonStyle} onClick={handleButtonClick}>
{label}
</button>
);
};
export default Button;
//color variables
$white: #fff;
$black: #000;
$dark-gray: #333;
$medium-gray: #444;
$gray: #a5a5a5;
$light-gray: #c4c4c4;
$orange: #ff9d20;
$light-orange: #ffb657;
* {
font-family: "Source Sans Pro", sans-serif;
font-weight: 200;
color: $white;
}
.flex {
display: flex;
}
.row {
flex-flow: row nowrap;
}
.column {
flex-flow: column wrap;
}
.flex-end {
justify-content: flex-end;
}
.jc-sp-btw {
justify-content: space-between;
}
.jc-sp-around {
justify-content: space-around;
}
.jc-center {
justify-content: center;
}
.ai-center {
align-items: center;
}
.grid {
display: grid;
grid-template-columns: repeat(3, auto);
gap: 9px;
}
#calculator-view {
width: 385px;
height: 775px;
background-color: $black;
border-radius: 70px;
border: 10px solid $dark-gray;
#viewport {
width: 90%;
height: 70%;
#display {
width: 100%;
input {
border: none;
outline: none;
font-size: 6rem;
background-color: $black;
width: 100%;
text-align: right;
padding-right: 20px;
}
}
#keypad {
width: 97%;
button {
border: none;
border-radius: 50px;
width: 75px;
height: 75px;
font-size: 2rem;
cursor: pointer;
&.fx-key {
background-color: $gray;
color: $black;
&:hover {
background-color: $light-gray;
}
}
&.numeric-key {
background-color: $dark-gray;
&:hover {
background-color: $medium-gray;
}
}
&.op-key {
background-color: $orange;
font-size: 3rem;
&:hover {
background-color: $light-orange;
}
}
&.special {
width: 100%;
grid-column-start: 1;
grid-column-end: span 2;
text-align: left;
padding-left: 25px;
}
}
}
}
}
const Calculator = () => {
const [accValue, setAccValue] = useState(null);
const [screenValue, setScreenValue] = useState("0");
const [currentOperator, setCurrentOperator] = useState(null);
const [expectsOperand, setExpectsOperand] = useState(false);
...
}
export default Calculator;
accValue: the accumulated value in the calculator. It starts off as null because there's no accumulated value initially.
screenValue: the value that is going to be shown on screen. Initially, its value is "0". Note that we're defining it as a string, not a number. We'll talk about this later.
currentOperator: the ongoing operator. As well as accValue, it starts off as null for the same reason.
expectsOperand: a boolean that lets the calculator know if a new operand should be entered after pressing a button or if, on the contrary, a result, which is final itself, has been already calculated.
It will become true when an operator key is pressed, and false otherwise (only operations wait for a second operand. Neither numbers nor functions that apply to a single operand). It starts off as false, since the initial state itself is stable.
const handleActionToPerform = (value, keyType) => {
switch (keyType) {
case "fx": handleClickFunctionKey(value); break;
case "numeric": handleClickNumericKey(value); break;
case "operator": handleClickOperator(value); break;
}
}
const handleClickFunctionKey = value => {
switch (value) {
case "AC": allClear(); break;
case "C": clearScreen(); break;
case "+/-": reverseSign(); break;
case "%": percentage(); break;
case ".": addDecimalPoint(); break;
};
}
const allClear = () => {
setAccValue(null);
setScreenValue("0");
setCurrentOperator(null);
setExpectsOperand(false);
}
const clearScreen = () => {
setScreenValue("0");
}
const isScreenClear = screenValue === "0";
const reverseSign = () => {
setScreenValue(String(-parseFloat(screenValue)));
}
const percentage = () => {
setScreenValue(String(parseFloat(screenValue)/100));
};
const addDecimalPoint = () => {
if (expectsOperand) {
setScreenValue("0.");
}
else {
if (!screenValue.includes("."))
setScreenValue(screenValue + ".");
}
setExpectsOperand(false);
}
Note that this is the only one functionality that we're going to implement to be used through the keyboard, not number, operator or function keys. For those, we'll be using the keypad buttons, not the keyboard.
useEffect(() => {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown',handleKeyDown);
}, [screenValue]);
handleKeyDown
will be called and its code will be executed.const handleKeyDown = e => {
if (e.key === 'Backspace') {
e.preventDefault();
clearLastDigit();
}
}
const clearLastDigit = () => {
if (screenValue !== "0")
if (screenValue.length > 1)
setScreenValue("0");
else {
setScreenValue(screenValue.substring(0, screenValue.length - 1));
}
}
const handleClickNumericKey = value => {
if (expectsOperand) {
setScreenValue(String(value));
setExpectsOperand(false);
}
else
setScreenValue(screenValue === "0" ? String(value) : screenValue + value);
}
const handleClickOperator = operator => {
const inputValue = parseFloat(screenValue);
if (accValue === null) {
setAccValue(inputValue);
}
else {
if (currentOperator) {
const resultValue = operate(currentOperator, accValue, inputValue);
setAccValue(resultValue);
setScreenValue(String(resultValue));
}
}
setExpectsOperand(true);
setCurrentOperator(operator);
}
Bear in mind that the accumulated value is only stored when an operator is pressed, not when a numeric key is pressed.
const operate = (operator, accValue, inputValue) => {
switch (operator) {
case "+": return accValue + inputValue;
case "-": return accValue - inputValue;
case "x": return accValue * inputValue;
case "/": return accValue / inputValue;
case "=": return inputValue;
}
}