Skip to content

Instantly share code, notes, and snippets.

@DanCouper
Created May 23, 2017 20:34
Show Gist options
  • Select an option

  • Save DanCouper/f91ef4eb821e655c970ed71bb8708f25 to your computer and use it in GitHub Desktop.

Select an option

Save DanCouper/f91ef4eb821e655c970ed71bb8708f25 to your computer and use it in GitHub Desktop.
React Calculator
<!--
Nothing here, the app will render on the Body.
Sorry.
-->

React Calculator

Calculator is an exercise in React, playing with custom data stores and PostCSS.

Click the RECALL button for a brief abstraction on a Flux store. The code for the store starts on line 177.

Works on mobile as well, though the UI is not as responsive as I would like. Any thoughts or suggestions would be great.

A Pen by Dan Couper on CodePen.

License.

const ee = new EventEmitter();
const App = React.createClass({
render() {
return (
<main className="react-calculator">
<InputField />
<TotalRecall />
<ButtonSetNumbers />
<ButtonSetFunctions />
<ButtonSetEquations />
</main>
)
}
});
const Button = React.createClass({
_handleClick() {
let text = this.props.text,
cb = this.props.clickHandler;
if (cb) {
cb.call(null, text);
}
},
render() {
return (
<button className={this.props.klass} onClick={this._handleClick}>
<span className="title">{this.props.text}</span>
</button>
);
}
});
const ContentEditable = React.createClass({
_handleClick() {
const cb = this.props.clickHandler;
if (cb) {
cb.call(this);
}
},
render() {
return (
<div className="editable-field" contentEditable={this.props.initEdit} spellcheck={this.props.spellCheck} onClick={this._handleClick}>
{this.props.text}
</div>
)
}
});
const InputField = React.createClass({
_updateField(newStr) {
newStr = newStr.split ? newStr.split(' ').reverse().join(' ') : newStr;
return this.setState({text: newStr});
},
getInitialState() {
this.props.text = this.props.text || '0';
return {text: this.props.text};
},
componentWillMount() {
ee.addListener('numberCruncher', this._updateField);
},
render() {
return <ContentEditable text={this.state.text} initEdit="false" spellcheck="false" clickHandler={this._clickBait} />
}
});
const TotalRecall = React.createClass({
_toggleMemories() {
this.setState({show: !this.state.show});
},
_recallMemory(memory) {
store.newInput = memory;
ee.emitEvent('toggle-memories');
},
getInitialState() {
return {show: false}
},
componentWillMount() {
ee.addListener('toggle-memories', this._toggleMemories);
},
render() {
let classNames = `memory-bank ${this.state.show ? 'visible' : ''}`;
return (
<section className={classNames}>
<Button text="+" clickHandler={this._toggleMemories} klass="toggle-close" />
{store.curMemories.map((mem) => {
return (
<Button klass="block memory transparent" text={mem} clickHandler={this._recallMemory} />
);
})}
</section>
)
}
});
const ButtonSetFunctions = React.createClass({
_showMemoryBank() {
ee.emitEvent('toggle-memories');
},
_clear() {
store.newInput = 0;
},
_contentClear() {
let curInput = String(store.curInput),
lessOne = curInput.substring(0, (curInput.length - 1));
return store.newInput = lessOne === '' ? 0 : lessOne;
},
render() {
return (
<section className="button-set--functions">
<Button klass="long-text" text="recall" clickHandler={this._showMemoryBank} />
<Button klass="long-text" text="clear" clickHandler={this._clear} />
<Button text="&larr;" clickHandler={this._contentClear} />
</section>
)
}
});
const ButtonSetEquations = React.createClass({
_eq(type) {
store.newInput = `${store.curInput} ${type} `;
},
_equate() {
store.newInput = eval(store.curInput);
},
render() {
return (
<section className="button-set--equations">
<Button text="+" clickHandler={this._eq} />
<Button text="-" clickHandler={this._eq} />
<Button text="*" clickHandler={this._eq} />
<Button text="/" clickHandler={this._eq} />
<Button text="=" clickHandler={this._equate} />
</section>
)
}
});
const ButtonSetNumbers = React.createClass({
_number(num) {
if (!store.curInput) {
return store.newInput = num;
}
return store.newInput = `${store.curInput}${num}`;
},
render() {
return (
<section className="button-set--numbers">
<Button text="1" clickHandler={this._number} />
<Button text="2" clickHandler={this._number} />
<Button text="3" clickHandler={this._number} />
<Button text="4" clickHandler={this._number} />
<Button text="5" clickHandler={this._number} />
<Button text="6" clickHandler={this._number} />
<Button text="7" clickHandler={this._number} />
<Button text="8" clickHandler={this._number} />
<Button text="9" clickHandler={this._number} />
<Button text="0" clickHandler={this._number} />
</section>
)
}
});
let store = {
input: 0,
memory: [],
get curInput() {
return this.input;
},
get curMemories() {
return this.memory.filter((m) => m !== undefined);
},
set commitMemory(input) {
this.memory.push(input);
},
set newInput(str) {
let curInput = str,
oldInput = this.curInput;
if (this.curMemories.indexOf(oldInput) === -1) {
this.commitMemory = oldInput;
}
this.input = curInput;
ee.emitEvent('numberCruncher', [this.curInput]);
}
};
React.render(
<App />,
document.querySelector('body')
);
<script src="//cdnjs.cloudflare.com/ajax/libs/react/0.13.0/react.min.js"></script>
<script src="https://cdn.jsdelivr.net/eventemitter/4.3.0/EventEmitter.min.js"></script>
@use cssnext;
@use postcss-nested;
:root {
/* color palette :: https://coolors.co/app/d63c6b-5cc8ff-efefef-292f36-d6d6d6 */
--white: #efefef;
--white-alpha: rgba(239, 239, 239, .64);
--grey: #d6d6d6;
--gray: var(--grey);
--black: #292f36;
--pure-black: #131313;
--pure-black-alpha: rgba(19, 19, 19, .64);
--pure-black-alpha-light: rgba(19, 19, 19, .24);
--pure-black-alpha-transparent: rgba(19, 19, 19, .12);
--primary: #d63c6b;
--primary-dk: #c41c4f;
--accent: #5cc8ff;
--accent-dk: #00a3f5;
--transition: 300ms;
--easing: cubic-bezier(1, 1, 1, 1);
--easing-bounce-in: cubic-bezier(0, 0, .43, 1.3);
--easing-bounce-out: cubic-bezier(.43, 1.3, 0, 0);
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
*,
*:before,
*:after {
box-sizing: border-box;
}
html,
body {
position: relative;
width: 100%;
height: 100%;
background-image: linear-gradient(to bottom right, var(--accent) 0%, var(--primary) 100%);
background-repeat: no-repeat;
background-attachment: fixed;
}
body {
padding-top: 20px;
user-select: none;
}
button {
outline: none;
border: 0;
padding: 1rem;
background-color: var(--black);
font-size: 1.25rem;
line-height: 1;
color: var(--accent);
transition: all var(--transition) var(--easing);
&:hover {
color: var(--accent-dk);
}
&.block {
width: 100%;
}
&.transparent {
background-color: transparent;
}
&.no-padding {
padding: 0;
}
&.long-text { font-size: 1rem; }
}
.react-calculator {
position: relative;
margin: 0 auto;
width: 320px;
box-shadow: 0 19px 38px var(--pure-black-alpha-light),
0 15px 12px var(--pure-black-alpha-transparent);
}
body,
.editable-field,
.memory-bank {
&::-webkit-scrollbar { width: .5rem; }
&::-webkit-scrollbar:horizontal { height: .5rem; }
&::-webkit-scrollbar-track,
&::-webkit-scrollbar:horizontal {
background-color: var(--pure-black);
}
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar:horizontal {
background-color: var(--primary);
}
s
&:hover {
&::-webkit-scrollbar-thumb,
&::-webkit-scrollbar:horizontal {
background-color: var(--primary-dk);
}
}
}
.editable-field {
position: relative;
width: 320px;
height: 80px;
z-index: 10;
outline: none;
box-shadow: 0 4px 2px -2px var(--pure-black-alpha);
padding: .5rem;
overflow-y: hidden;
overflow-x: scroll;
background-color: var(--pure-black-alpha);
font-size: 2rem;
line-height: 2;
color: var(--primary);
text-align: right;
direction: rtl;
white-space: nowrap;
}
.memory-bank {
position: absolute;
top: 80px;
left: 0;
z-index: 10;
width: 100%;
height: 0;
overflow: hidden;
padding: 0;
background-color: var(--pure-black-alpha);
color: var(--accent-dk);
transition: height 150ms var(--easing),
overflow 1ms var(--easing) 200ms,
padding 1ms var(--easing) 200ms;
.toggle-close {
position: absolute;
top: 5px;
right: 5px;
padding: 2px 5px;
.title {
display: inline-block;
transform: rotate(45deg);
}
}
&.visible {
height: calc(100% - 80px);
padding: 10px;
overflow-y: auto;
transition: height 200ms var(--easing),
padding 1ms var(--easing);
}
}
.button-set {
&--functions,
&--equations {
background-color: var(--black);
button {
display: inline-block;
width: 80px;
height: 80px;
vertical-align: top;
text-transform: uppercase;
font-variant: small-caps;
color: var(--primary);
&:hover {
color: var(--primary-dk);
}
}
}
&--numbers {
width: 240px;
float: left;
background-color: var(--black);
button {
display: block;
position: relative;
width: 80px;
height: 80px;
float: left;
background-color: var(--black);
transition: box-shadow var(--transition) var(--easing);
&:last-child {
width: 100%;
}
}
}
&--functions {
clear: left;
float: left;
width: 240px;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment