<main id="wrapper">
        
    </main>
@font-face {
    font-family: 'Seven Segment';
    font-style: normal;
    font-weight: normal;
    src: local('Seven Segment'), url('https://raw.githubusercontent.com/Kemystra/hello-world/main/Seven%20Segment.woff') format('woff');
}

* {
    box-sizing: border-box;
}

body {
    width: 100vw;
    height: 100vh;
    margin: 0;

    background-color: rgb(158, 189, 214);
}

#wrapper {
    width: 100%;
    height: 100%;
}

#calculator {
    width: 20rem;
    height: 30rem;
    padding: 0.5rem;

    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);

    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-template-rows: repeat(6, 1fr);
    grid-template-areas:
        "dsp dsp dsp dsp"
        "num num num ."
        "num num num ."
        "num num num ."
        "num num num ."
        "cls cls cls cls"
    ;

    background-color: rgb(43, 43, 43);

    button {
        border: 1px solid black;
        border-radius: 5px;
        background-color: gray;

        transition: background-color 0.1s ease-in;

        &:hover {
            background-color: rgb(182, 182, 182);
        }

        &:active {
            background-color: orange;
        }
    }
}

#screen {
    grid-area: dsp;
    overflow-wrap: anywhere;
    padding: 0.2rem;

    background-color: rgb(60, 60, 75);
    color: white;

    font-family: "Seven Segment", sans-serif;

    #formula {
        font-size: 1.5rem;
        margin: 0;
    }

    #display {
        color: orange;
        margin: 0;
    }
}

#clear {
    grid-area: cls;
    background-color: #ff3333 !important;
}

#equals {
    background-color: #333dff !important;
}

#numbers {
    grid-area: num;

    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(4, 1fr);
    grid-template-areas: ". . ." ". . ." ". . ." ". zero .";

    #zero {
        grid-area: zero;
    }
}
View Compiled
const EQUALS = "EQUALS";
const ADD = "ADD";
const SUBTRACT = "SUBTRACT";
const MULTIPLY = "MULTIPLY";
const DIVIDE = "DIVIDE";
const CLEAR = "CLEAR";
const NUMBER = "NUMBER";
const OPERATION = "OPERATION";
const DECIMAL = "DECIMAL";

const buttons=[{name:"one",value:1},{name:"two",value:2},
{name:"three",value:3},{name:"four",value:4},{name:"five",value:5},{name:"six",value:6},
{name:"seven",value:7},{name:"eight",value:8},{name:"nine",value:9},{name:"zero",value:0},
{name:ADD,value:"+"},{name:SUBTRACT,value:"-"},
{name:MULTIPLY,value:"*"},{name:DIVIDE,value:"/"}];

// React Code
class Calculator extends React.Component {
    constructor(props) {
        super(props);

        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(event) {
        const actionCreators = {
            "NUMBER": this.props.pushNumber,
            "DECIMAL": this.props.pushDecimal,
            "OPERATION": this.props.pushOps,
            "EQUALS": this.props.calcExpression,
            "CLEAR": this.props.clearDisplay
        };

        actionCreators[event.target.className](event.target.innerHTML);
    }

    render() {
        const thisClass = this;
        return (
            <div id="calculator">
                <Display currOperation={this.props.currOps} totalOperation={this.props.totalOps} />
                <div id="numbers">
                    {buttons.slice(0, 10).map(type => <Button id={type.name.toLowerCase()} content={type.value} 
                    handleClick={thisClass.handleClick} className={NUMBER} />)}
                    <Button id="equals" handleClick={thisClass.handleClick} className={EQUALS} content="="/>
                    <Button id="decimal" handleClick={thisClass.handleClick} className={DECIMAL} content="."/>
                </div>
                {buttons.slice(10).map(type => <Button id={type.name.toLowerCase()} content={type.value} 
                handleClick={thisClass.handleClick} className={OPERATION} />)}
                <Button id="clear" handleClick={thisClass.handleClick} className={CLEAR} content="CLEAR"/>
            </div>
        );
    }
};

const Button = (props) => {
    return (
        <button id={props.id} onClick={props.handleClick} className={props.className} >{props.content}</button>
    );
}

const Display = (props) => {
    return (
        <div id="screen">
            <p id="formula">{props.totalOperation}</p>
            <h1 id="display">{props.currOperation}</h1>
        </div>
    );
}

// Redux code
const initialState = {
    currOps: "0",
    totalOps: "",
    lastAction: NUMBER
}

let decimalPlaced = false;

const calcReducer = (state = initialState, action) => {
    // What happens when switching actions, the most tedious part
    if(state.lastAction !== action.type) {
        // Special case after equal button is pressed
        if(state.lastAction === "EQUALS") {
            switch (action.type) {
                // Push result from previous calc. to the main formula, and add operator as the currOps.
                // Other operation after this is handled normally
                case OPERATION:
                    return {
                        currOps: action.payload,
                        totalOps: state.currOps,
                        lastAction: action.type
                    };

                case NUMBER:
                    decimalPlaced = false;
                    return {
                        currOps: action.payload,
                        totalOps: action.payload,
                        lastAction: action.type
                    };
            }
        }

        if(action.type === NUMBER) {
            decimalPlaced = false;
            return {
                currOps: action.payload,
                totalOps: state.totalOps + state.currOps + action.payload,
                lastAction: NUMBER
            }
        }
        else if(action.type === OPERATION) {
            return {
                currOps: action.payload,
                totalOps: state.totalOps,
                lastAction: OPERATION
            };
        }
    }

    switch (action.type) {
        case NUMBER:
            // If zero is pressed, avoid multiple zeros
            if(state.currOps === "0") {
                if(action.payload === "0") {
                    return state
                }
                else {
                    // Overwrite zero when other number is pressed
                    return {
                        currOps: action.payload,
                        totalOps: action.payload,
                        lastAction: NUMBER
                    };
                }
            }
            // Default behaviour for any number
            return {
                currOps: state.currOps + action.payload,
                totalOps: state.totalOps + action.payload,
                lastAction: NUMBER
            };

        case DECIMAL:
            // Check if there's already a decimal point
            if(!decimalPlaced) {
                // Avoid multiple decimal points in a number
                decimalPlaced = true;
                return {
                    currOps: state.currOps + ".",
                    totalOps: state.totalOps + ".",
                    lastAction: NUMBER
                };
            }
            return state;

        case OPERATION:
            // Special case for negative sign
            if(action.payload === "-") {
                if(state.currOps.includes("-")) {
                    return state;
                }

                return {
                    currOps: state.currOps + "-",
                    totalOps: state.totalOps,
                    lastAction: OPERATION
                };
            }
            
            // Overwrite previous operator
            return {
                currOps: action.payload,
                totalOps: state.totalOps,
                lastAction: OPERATION
            };

        case EQUALS:
            // From math.js library, evaluate use formula execution
            let ans = math.evaluate(state.totalOps);
            return {
                currOps: ans,
                totalOps: state.totalOps + "=" + ans,
                lastAction: EQUALS
            };

        case CLEAR:
            decimalPlaced = false;
            return {
                currOps: "0",
                totalOps: "",
                lastAction: NUMBER
            }

        default:
            return state;
    }
}

const store = Redux.createStore(calcReducer);
const Provider = ReactRedux.Provider;

const pushNumberAction = (number) => ({
    type: NUMBER,
    payload: number
});

const pushDecimalAction = () => ({
    type: DECIMAL
});

const pushOpsAction = (ops) => ({
    type: OPERATION,
    payload: ops
});

const calcExpressionAction = () => ({
    type: EQUALS
});

const clearDisplayAction = () => ({
    type: CLEAR
});

const mapDispatchToProps = (dispatch) => ({
    pushNumber: number => {
        dispatch(pushNumberAction(number));
    },

    pushDecimal: () => {
        dispatch(pushDecimalAction());
    },

    pushOps: ops => {
        dispatch(pushOpsAction(ops));
    },

    calcExpression: () => {
        dispatch(calcExpressionAction());
    },

    clearDisplay: () => {
        dispatch(clearDisplayAction());
    }
});

const mapStateToProps = state => ({
    totalOps: state.totalOps,
    currOps: state.currOps
});

const connect = ReactRedux.connect
const ConnectedCalc = connect(mapStateToProps, mapDispatchToProps)(Calculator);

// Main renderer
window.onload = () => {
    ReactDOM.render(
        <Provider store={store}>
            <ConnectedCalc />
        </Provider>
    , document.getElementById("wrapper"));
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/redux/4.2.0/redux.min.js
  4. https://cdnjs.cloudflare.com/ajax/libs/react-redux/8.0.1/react-redux.min.js
  5. https://cdnjs.cloudflare.com/ajax/libs/mathjs/10.5.2/math.js
  6. https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js