├── .env
├── src
├── static
│ ├── robots.txt
│ ├── favicon.ico
│ └── humans.txt
├── styles
│ ├── components
│ │ ├── App.js
│ │ ├── ThemeSelector.js
│ │ ├── Author.js
│ │ ├── IndexSidebar.js
│ │ ├── Calculate.js
│ │ ├── all.js
│ │ ├── Button.js
│ │ ├── Calculation.js
│ │ ├── CalculationsList.js
│ │ ├── _mediaQueries.js
│ │ └── Index.js
│ ├── themes
│ │ ├── dark.js
│ │ ├── light.js
│ │ ├── base.js
│ │ └── createTheme.js
│ ├── index.js
│ ├── layout
│ │ └── base.js
│ ├── variables.js
│ └── globalStyles.js
├── containers
│ ├── DevToolsWindow.js
│ ├── DevTools.js
│ ├── App.js
│ ├── ThemeManager.js
│ ├── Root.js
│ ├── IndexSidebar.js
│ └── Index.js
├── routes
│ └── index.js
├── redux
│ ├── rootReducer.js
│ ├── modules
│ │ ├── events.js
│ │ ├── settings.js
│ │ ├── themes.js
│ │ ├── keys.js
│ │ └── calculations.js
│ ├── configureStore.js
│ ├── utils
│ │ └── createDevToolsWindow.js
│ ├── middleware
│ │ ├── toggleButton.js
│ │ └── handleEvents.js
│ └── selectors.js
├── initialState.js
├── keyboardLayouts
│ └── basicArithmetic.js
├── index.html
├── main.js
├── components
│ ├── Button.js
│ ├── Author.js
│ ├── ThemeSelector.js
│ ├── CalculationsList.js
│ ├── Calculation.js
│ ├── Calculate.js
│ └── Flex.js
└── helpers
│ └── pureFunctions.js
├── jsconfig.json
├── CHANGELOG.md
├── .eslintignore
├── .gitignore
├── bin
├── karma.js
├── server.js
└── compile.js
├── nodemon.json
├── .babelrc
├── tests
├── test-helpers
│ ├── createCalculation.js
│ ├── render.js
│ └── shouldComponentUpdate.js
├── .eslintrc
├── framework.spec.js
├── redux
│ ├── modules
│ │ ├── events.spec.js
│ │ ├── settings.spec.js
│ │ ├── themes.spec.js
│ │ ├── keys.spec.js
│ │ └── calculations.spec.js
│ ├── middleware
│ │ ├── toggleButton.spec.js
│ │ └── handleEvents.spec.js
│ └── selectors.spec.js
├── test-bundler.js
├── containers
│ ├── ThemeManager.spec.js
│ ├── App.spec.js
│ ├── IndexSidebar.spec.js
│ └── Index.spec.js
├── components
│ ├── ThemeSelector.spec.js
│ ├── Author.spec.js
│ ├── Button.spec.js
│ ├── CalculationsList.spec.js
│ ├── Calculation.spec.js
│ └── Calculate.spec.js
└── helpers
│ └── pureFunctions.spec.js
├── .travis.yml
├── config
├── _development.js
├── _production.js
├── index.js
└── _base.js
├── gulpfile.babel.js
├── server
├── lib
│ └── apply-express-middleware.js
├── middleware
│ ├── webpack-hmr.js
│ └── webpack-dev.js
└── main.js
├── .codeclimate.yml
├── .editorconfig
├── LICENSE
├── TODO.md
├── README.md
├── .eslintrc
└── package.json
/.env:
--------------------------------------------------------------------------------
1 | DEBUG=app:*
2 | BLUEBIRD_WARNINGS=0
3 |
--------------------------------------------------------------------------------
/src/static/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow:
3 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | 0.1.0
5 | -----
6 |
7 | #### Not there yet
8 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage/**
2 | node_modules/**
3 | dist/**
4 | *.spec.js
5 | src/index.html
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | *.log
3 |
4 | node_modules
5 |
6 | dist
7 | coverage
8 | .publish
9 |
--------------------------------------------------------------------------------
/src/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/panayi/calculator/HEAD/src/static/favicon.ico
--------------------------------------------------------------------------------
/bin/karma.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 |
3 | module.exports = require('../build/karma.conf')
4 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "verbose": false,
3 | "ignore": ["dist", "coverage", "tests", "src"]
4 | }
5 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react", "stage-0"],
3 | "plugins": ["typecheck", "transform-runtime", "add-module-exports"]
4 | }
5 |
--------------------------------------------------------------------------------
/tests/test-helpers/createCalculation.js:
--------------------------------------------------------------------------------
1 | export default function (input, output, isError = false) {
2 | return {
3 | input,
4 | output,
5 | isError
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/styles/components/App.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.app__sidebar': {
4 | width: variables.layout.sidebarWidth
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/styles/components/ThemeSelector.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.theme-selector__icon': {
4 | fontSize: variables.fontSizes.button
5 | }
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/containers/DevToolsWindow.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createDevTools } from 'redux-devtools'
3 | import LogMonitor from 'redux-devtools-log-monitor'
4 |
5 | export default createDevTools(
6 |
7 | )
8 |
--------------------------------------------------------------------------------
/src/styles/themes/dark.js:
--------------------------------------------------------------------------------
1 | export default {
2 | primaryColor: '#16a085',
3 | accentColor: '#FAAB22',
4 | accent2Color: '#EE213D',
5 | canvasColor: '#27313E',
6 | primaryContrastColor: 'white',
7 | accentContrastColor: 'white'
8 | }
9 |
--------------------------------------------------------------------------------
/src/styles/themes/light.js:
--------------------------------------------------------------------------------
1 | export default {
2 | primaryColor: '#0A6987',
3 | accentColor: '#DA1415',
4 | accent2Color: '#363B40',
5 | canvasColor: '#DADADA',
6 | primaryContrastColor: 'white',
7 | accentContrastColor: 'white'
8 | }
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "5.0"
5 |
6 | cache:
7 | directories:
8 | - node_modules
9 |
10 | install:
11 | - npm install
12 |
13 | script:
14 | - npm run lint
15 | - npm run test
16 | - gulp deploy
17 |
--------------------------------------------------------------------------------
/src/static/humans.txt:
--------------------------------------------------------------------------------
1 | /* TEAM */
2 | Developer: Panagiotis Panagi
3 | Twitter: @ppanagi
4 | From: Limassol, Cyprus
5 |
6 | /* SITE */
7 | Last update: 2015/01/13
8 | Language: English
9 | Doctype: HTML5
10 | Components: React, Redux, Ramda
11 | IDE: Atom
12 |
--------------------------------------------------------------------------------
/config/_development.js:
--------------------------------------------------------------------------------
1 | // We use an explicit public path when the assets are served by webpack
2 | // to fix this issue:
3 | // http://stackoverflow.com/a/34133809/359104
4 | export default (config) => ({
5 | compiler_public_path: `http://${config.server_host}:${config.server_port}/`
6 | })
7 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.eslintrc",
3 | "env": {
4 | "mocha": true
5 | },
6 | "globals": {
7 | "$": false,
8 | "expect": false,
9 | "should": false,
10 | "sinon": false,
11 | "render": false,
12 | "shallowRender": false
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/styles/components/Author.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.author__github': {
4 | minWidth: '16px',
5 | minHeight: '16px'
6 | },
7 | '.author__tweet': {
8 | minWidth: '68px',
9 | minHeight: '29px'
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/config/_production.js:
--------------------------------------------------------------------------------
1 | /* eslint key-spacing:0 */
2 | export default (config) => ({
3 | compiler_fail_on_warning: false,
4 | compiler_hash_type: 'chunkhash',
5 | compiler_devtool: null,
6 | compiler_stats: {
7 | chunks: true,
8 | chunkModules: true,
9 | colors: true
10 | }
11 | })
12 |
--------------------------------------------------------------------------------
/src/styles/components/IndexSidebar.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.index-sidebar__logo': {
4 | margin: 0,
5 | fontSize: '224px',
6 | lineHeight: '176px',
7 | color: variables.colors.logo,
8 | userSelect: 'none'
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/bin/server.js:
--------------------------------------------------------------------------------
1 | require('babel-register')
2 |
3 | const config = require('../config')
4 | const server = require('../server/main')
5 | const debug = require('debug')('app:bin:server')
6 |
7 | const port = config.server_port
8 |
9 | server.listen(port)
10 | debug('Server is now running at localhost:' + port + '.')
11 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | import connect from 'gulp-connect'
2 | import ghPages from 'gulp-gh-pages'
3 | import gulp from 'gulp'
4 |
5 | gulp.task('deploy', () =>
6 | gulp.src('./dist/**/*')
7 | .pipe(ghPages())
8 | )
9 |
10 | gulp.task('serve-dist', () =>
11 | connect.server({
12 | root: 'dist',
13 | port: 8001,
14 | livereload: false
15 | })
16 | )
17 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route, IndexRoute } from 'react-router'
3 | import App from 'containers/App'
4 | import Index from 'containers/Index'
5 | import IndexSidebar from 'containers/IndexSidebar'
6 |
7 | export default (
8 |
9 |
10 |
11 | )
12 |
--------------------------------------------------------------------------------
/src/styles/index.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import globalStyles from './globalStyles'
3 | import components from './components/all'
4 | import _variables from './variables'
5 |
6 | export default(themeName) => {
7 | const variables = _variables(themeName)
8 | return R.reduce(
9 | (styles, component) => R.merge(styles, component(variables)),
10 | {},
11 | R.append(globalStyles, components)
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 | import { routeReducer } from 'redux-simple-router'
3 | import calculations from './modules/calculations'
4 | import keys from './modules/keys'
5 | import settings from './modules/settings'
6 | import themes from './modules/themes'
7 |
8 | export default combineReducers({
9 | calculations,
10 | keys,
11 | router: routeReducer,
12 | settings,
13 | themes
14 | })
15 |
--------------------------------------------------------------------------------
/src/containers/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { createDevTools } from 'redux-devtools'
3 | import LogMonitor from 'redux-devtools-log-monitor'
4 | import DockMonitor from 'redux-devtools-dock-monitor'
5 |
6 | export default createDevTools(
7 |
12 |
13 |
14 | )
15 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | import _debug from 'debug'
2 |
3 | const debug = _debug('app:config')
4 | debug('Create configuration.')
5 | import base from './_base'
6 |
7 | debug(`Apply environment overrides for NODE_ENV "${base.env}".`)
8 | let overrides
9 | try {
10 | overrides = require(`./_${base.env}`)(base)
11 | } catch (error) {
12 | debug(
13 | `No configuration overrides found for NODE_ENV "${base.env}"`
14 | )
15 | }
16 |
17 | export default Object.assign({}, base, overrides)
18 |
--------------------------------------------------------------------------------
/server/lib/apply-express-middleware.js:
--------------------------------------------------------------------------------
1 | // Based on:
2 | // https://github.com/dayAlone/koa-webpack-hot-middleware/blob/master/index.js
3 | export default function applyExpressMiddleware(fn, req, res) {
4 | const originalEnd = res.end
5 |
6 | return new Promise((resolve, reject) => {
7 | res.end = function () {
8 | originalEnd.apply(this, arguments)
9 | resolve(false)
10 | }
11 | fn(req, res, function () {
12 | resolve(true)
13 | })
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/layout/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | gutters: {
3 | xlarge: 50,
4 | large: 40,
5 | base: 20,
6 | small: 10,
7 | xsmall: 6,
8 | tiny: 3
9 | },
10 | screens: {
11 | mediumWidth: '(max-width: 992px) and (min-width: 646px)',
12 | smallWidth: '@media (max-width: 645px)',
13 | mediumHeight: '(max-height: 800px) and (min-height: 446px)',
14 | smallHeight: '(max-height: 445px)'
15 | },
16 | layout: {
17 | sidebarWidth: '270px'
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/styles/variables.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda'
2 | import baseThemeVariables from './themes/base'
3 | import baseLayoutVariables from './layout/base'
4 | import createTheme from './themes/createTheme'
5 |
6 | export default function (themeName) {
7 | const themeVariables = themeName
8 | ? createTheme(require('styles/themes/' + themeName).default)
9 | : {}
10 |
11 | return R.mergeAll([
12 | baseLayoutVariables,
13 | baseThemeVariables,
14 | themeVariables
15 | ])
16 | }
17 |
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | ---
2 | engines:
3 | duplication:
4 | enabled: true
5 | config:
6 | languages:
7 | - ruby
8 | - javascript
9 | - python
10 | - php
11 | eslint:
12 | enabled: true
13 | fixme:
14 | enabled: true
15 | ratings:
16 | paths:
17 | - "**.inc"
18 | - "**.js"
19 | - "**.jsx"
20 | - "**.module"
21 | - "**.php"
22 | - "**.py"
23 | - "**.rb"
24 | exclude_paths:
25 | - bin/
26 | - build/
27 | - config/
28 | - server/
29 | - tests/
30 |
--------------------------------------------------------------------------------
/src/initialState.js:
--------------------------------------------------------------------------------
1 | import basicArithmetic from 'keyboardLayouts/basicArithmetic'
2 |
3 | export default {
4 | keys: basicArithmetic,
5 | settings: {
6 | authorName: 'Panagiotis Panagi',
7 | authorUrl: 'https://github.com/panayi',
8 | repoUrl: 'https://github.com/panayi/calculator',
9 | tweetText: '3R Calculator built with React, Redux and Ramda',
10 | tweetVia: 'ppanagi'
11 | },
12 | themes: [
13 | { name: 'dark', active: true },
14 | { name: 'light', active: false }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/tests/test-helpers/render.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import TestUtils from 'react-addons-test-utils'
3 |
4 | function _shallowRender(component) {
5 | const renderer = TestUtils.createRenderer()
6 | renderer.render(component)
7 | return renderer.getRenderOutput()
8 | }
9 |
10 | export function render(Component, props = {}) {
11 | return TestUtils.renderIntoDocument()
12 | }
13 |
14 | export function shallowRender(Component, props = {}) {
15 | return _shallowRender()
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles/components/Calculate.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.calculate': {
4 | position: 'relative'
5 | },
6 | '.calculate__input': {
7 | background: 'none',
8 | border: 'none',
9 | boxShadow: 'none',
10 | outline: 'none',
11 | fontSize: '20px',
12 | padding: '11px 0 17px 0',
13 | width: '100%',
14 | color: variables.colors.accent
15 | },
16 | '.calculate__output': {
17 | position: 'absolute',
18 | bottom: '2px',
19 | fontSize: variables.fontSizes.small
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/redux/modules/events.js:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions'
2 |
3 | // ------------------------------------
4 | // Constants
5 | // ------------------------------------
6 | export const actionTypes = {
7 | KEY_CLICKED: 'KEY_CLICKED',
8 | KEY_PRESSED: 'KEY_PRESSED'
9 | }
10 |
11 | // ------------------------------------
12 | // Actions
13 | // ------------------------------------
14 |
15 | // keyClicked :: Key -> Action
16 | export const keyClicked = createAction(actionTypes.KEY_CLICKED)
17 |
18 | // keyPressed :: Event -> Action
19 | export const keyPressed = createAction(actionTypes.KEY_PRESSED)
20 |
--------------------------------------------------------------------------------
/tests/framework.spec.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert'
2 |
3 | describe('(Framework) Karma Plugins', function () {
4 | it('Should expose "expect" globally.', function () {
5 | assert.ok(expect)
6 | })
7 |
8 | it('Should expose "should" globally.', function () {
9 | assert.ok(should)
10 | })
11 |
12 | it('Should have chai-as-promised helpers.', function () {
13 | const pass = new Promise(res => res('test'))
14 | const fail = new Promise((res, rej) => rej())
15 |
16 | return Promise.all([
17 | expect(pass).to.be.fulfilled,
18 | expect(fail).to.not.be.fulfilled
19 | ])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/src/styles/components/all.js:
--------------------------------------------------------------------------------
1 | import mediaQueries from './_mediaQueries'
2 | import App from './App'
3 | import Author from './Author'
4 | import Button from './Button'
5 | import Calculate from './Calculate'
6 | import Calculation from './Calculation'
7 | import CalculationsList from './CalculationsList'
8 | import Index from './Index'
9 | import IndexSidebar from './IndexSidebar'
10 | import ThemeSelector from './ThemeSelector'
11 |
12 | export default [
13 | mediaQueries,
14 | App,
15 | Author,
16 | Button,
17 | Calculate,
18 | Calculation,
19 | CalculationsList,
20 | Index,
21 | IndexSidebar,
22 | ThemeSelector
23 | ]
24 |
--------------------------------------------------------------------------------
/server/middleware/webpack-hmr.js:
--------------------------------------------------------------------------------
1 | import WebpackHotMiddleware from 'webpack-hot-middleware'
2 | import applyExpressMiddleware from '../lib/apply-express-middleware'
3 | import _debug from 'debug'
4 |
5 | const debug = _debug('app:server:webpack-hmr')
6 |
7 | export default function (compiler, opts) {
8 | debug('Enable Webpack Hot Module Replacement (HMR).')
9 |
10 | const middleware = WebpackHotMiddleware(compiler, opts)
11 | return async function koaWebpackHMR(ctx, next) {
12 | let hasNext = await applyExpressMiddleware(middleware, ctx.req, ctx.res) // eslint-disable-line prefer-const, max-len
13 |
14 | if (hasNext && next) {
15 | await next()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/src/styles/components/Button.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.button': {
4 | fontSize: variables.fontSizes.button,
5 | width: '56.5px',
6 | margin: `0 ${variables.gutters.xsmall}px ${variables.gutters.xsmall}px 0`,
7 | padding: '1px 0',
8 | textAlign: 'center',
9 | display: 'inline-block',
10 | cursor: 'pointer',
11 | userSelect: 'none',
12 | borderBottom: `5px solid ${variables.colors.canvasDarker}`,
13 | backgroundColor: variables.colors.canvasDark
14 | },
15 | '.button--active': {
16 | color: variables.colors.accent,
17 | backgroundColor: variables.colors.canvasDarker
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/redux/modules/events.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import { keyPressed, actionTypes } from 'redux/modules/events'
3 |
4 | describe('(Redux Module) events', function () {
5 | let event
6 |
7 | describe('actions', function () {
8 | beforeEach(function () {
9 | event = $.Event('keypress', {
10 | which: 100,
11 | keyCode: 100
12 | })
13 | })
14 |
15 | it('should create an action for handling keyPress event', function () {
16 | const expectedAction = {
17 | type: actionTypes.KEY_PRESSED,
18 | payload: event
19 | }
20 | expect(keyPressed(event)).to.deep.equal(expectedAction)
21 | })
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/tests/test-helpers/shouldComponentUpdate.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import R from 'ramda'
3 |
4 | const assert = function (result) {
5 | return {
6 | is: {
7 | true() {
8 | expect(result).to.be.true
9 | },
10 |
11 | false() {
12 | expect(result).to.be.false
13 | }
14 | }
15 | }
16 | }
17 |
18 | export function shouldUpdate(component, changedProps) {
19 | const nextProps = R.merge(component.props, changedProps)
20 | return assert(component.shouldComponentUpdate(nextProps))
21 | }
22 |
23 | export function shouldIgnoreOtherProps(component, nextProps) {
24 | expect(component.shouldComponentUpdate(nextProps)).to.be.false
25 | }
26 |
--------------------------------------------------------------------------------
/tests/test-bundler.js:
--------------------------------------------------------------------------------
1 | // require all `tests/test-helpers/**/*.js`
2 | const helpersContext = require.context('./test-helpers/', true, /\.js$/)
3 | helpersContext.keys().forEach(helpersContext)
4 |
5 | // require all `tests/**/*.spec.js`
6 | const testsContext = require.context('./', true, /\.spec\.js$/)
7 | testsContext.keys().forEach(testsContext)
8 |
9 | // require all `src/**/*.js` except for `main.js` (for isparta reporting)
10 | const componentsContext = require.context('../src/', true, /^((?!main).)*\.js$/)
11 |
12 | componentsContext.keys().forEach(componentsContext)
13 |
14 | // global.navigator = {userAgent: 'Mozilla/5.0 (Windows NT 6.1; WOW64)
15 | // AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2454.85 Safari/537.36'};
16 |
--------------------------------------------------------------------------------
/src/redux/modules/settings.js:
--------------------------------------------------------------------------------
1 | import { handleActions, createAction } from 'redux-actions'
2 | import R from 'ramda'
3 |
4 | // ------------------------------------
5 | // Constants
6 | // ------------------------------------
7 | export const actionTypes = {
8 | SET_SETTING: 'SET_SETTING'
9 | }
10 |
11 | // ------------------------------------
12 | // Actions
13 | // ------------------------------------
14 |
15 | // setSetting :: Object -> Action
16 | export const setSetting = createAction(actionTypes.SET_SETTING)
17 |
18 | // ------------------------------------
19 | // Reducer
20 | // ------------------------------------
21 | export default handleActions({
22 | [actionTypes.SET_SETTING]: (state, { payload }) => R.merge(state, payload)
23 | }, {})
24 |
--------------------------------------------------------------------------------
/tests/redux/modules/settings.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | import R from 'ramda'
3 | import reducer, { actionTypes } from 'redux/modules/settings'
4 |
5 | describe('(Redux Module) settings', function () {
6 | describe('reducer', function () {
7 | it('should return the initial state', function () {
8 | expect(reducer(undefined, {})).to.deep.equal({})
9 | })
10 |
11 | it('should handle SET_SETTING', function () {
12 | const initialState = { initialKey: 'foo' }
13 | const setting = { bar: 'baz' }
14 |
15 | expect(reducer(initialState, {
16 | type: actionTypes.SET_SETTING,
17 | payload: setting
18 | })).to.deep.equal(R.merge(initialState, setting))
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/src/keyboardLayouts/basicArithmetic.js:
--------------------------------------------------------------------------------
1 | export default [
2 | { keyCode: 40, display: '(' },
3 | { keyCode: 41, display: ')' },
4 | { keyCode: 94, display: '^' },
5 | { keyCode: 55, display: '7' },
6 | { keyCode: 56, display: '8' },
7 | { keyCode: 57, display: '9' },
8 | { keyCode: 47, display: '÷' },
9 | { keyCode: 52, display: '4' },
10 | { keyCode: 53, display: '5' },
11 | { keyCode: 54, display: '6' },
12 | { keyCode: 42, display: '×' },
13 | { keyCode: 49, display: '1' },
14 | { keyCode: 50, display: '2' },
15 | { keyCode: 51, display: '3' },
16 | { keyCode: 45, display: '−' },
17 | { keyCode: 48, display: '0' },
18 | { keyCode: 46, display: '.' },
19 | { keyCode: 13, display: '=' },
20 | { keyCode: 43, display: '+' }
21 | ]
22 |
--------------------------------------------------------------------------------
/src/styles/components/Calculation.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.calculation__output': {
4 | fontSize: variables.fontSizes.xlarge,
5 | color: variables.colors.accent
6 | },
7 | '.calculation__input': {
8 | fontSize: variables.fontSizes.small,
9 | marginTop: - variables.gutters.tiny
10 | },
11 | '.calculation__pointer': {
12 | textAlign: 'right',
13 | width: `${variables.gutters.xlarge}px`,
14 | cursor: 'pointer',
15 | color: variables.colors.fadedText,
16 | userSelect: 'none'
17 | },
18 | '.calculation__pointer span': {
19 | padding: `${variables.gutters.tiny}px`
20 | },
21 | '.calculation__pointer:hover': {
22 | color: variables.colors.text
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/styles/components/CalculationsList.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | '.calculations-list__content': {
4 | overflow: 'auto',
5 | marginTop: `${- variables.gutters.small}px`
6 | },
7 | '.calculations-list__overlay': {
8 | position: 'absolute',
9 | zIndex: 0,
10 | top: 0,
11 | bottom: 0,
12 | left: 0,
13 | right: 0,
14 | display: 'table-cell',
15 | width: '50%',
16 | height: '7.5vw',
17 | margin: 'auto',
18 | paddingLeft: `${variables.gutters.xlarge}px`,
19 | fontSize: '6vw',
20 | textAlign: 'center',
21 | color: variables.colors.fadedText,
22 | userSelect: 'none'
23 | },
24 | '.calculations-list__3r': {
25 | display: 'none',
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/styles/themes/base.js:
--------------------------------------------------------------------------------
1 | export default {
2 | colors: {
3 |
4 | },
5 | fontFamilies: {
6 | text: '\'Lato\', \'Helvetica\', \'Arial\', sans-serif',
7 | header: '\'Lobster\', sans-serif'
8 | },
9 | fontSizes: {
10 | base: '14px',
11 | large: '18px',
12 | small: '12px',
13 | xlarge: '22px',
14 | button: '37px'
15 | },
16 | gutters: {
17 | xlarge: 50,
18 | large: 40,
19 | base: 20,
20 | small: 10,
21 | xsmall: 6,
22 | tiny: 3
23 | },
24 | layout: {
25 | sidebarWidth: '270px'
26 | },
27 | screens: {
28 | mediumWidth: '(max-width: 992px) and (min-width: 646px)',
29 | smallWidth: '(max-width: 645px)',
30 | mediumHeight: '(max-height: 800px) and (min-height: 446px)',
31 | smallHeight: '(max-height: 445px)'
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/styles/globalStyles.js:
--------------------------------------------------------------------------------
1 | export default function (variables) {
2 | return {
3 | body: {
4 | fontFamily: variables.fontFamilies.text,
5 | fontSize: variables.fontSizes.base,
6 | backgroundColor: variables.colors.canvas,
7 | color: variables.colors.text
8 | },
9 |
10 | 'h1, h2': {
11 | fontFamily: variables.fontFamilies.header,
12 | fontWeight: 'normal'
13 | },
14 |
15 | h1: {
16 | fontSize: variables.fontSizes.xlarge
17 | },
18 |
19 | h2: {
20 | fontSize: variables.fontSizes.large
21 | },
22 |
23 | a: {
24 | color: variables.colors.text,
25 | textDecoration: 'none'
26 | },
27 |
28 | 'a:hover': {
29 | color: variables.colors.accent
30 | },
31 |
32 | small: {
33 | fontSize: variables.fontSizes.small
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react'
2 | import { propsChanged } from 'helpers/pureFunctions'
3 | import Flex from 'components/Flex'
4 |
5 | export default class App extends Component {
6 | static propTypes = {
7 | main: PropTypes.element.isRequired,
8 | sidebar: PropTypes.element,
9 | };
10 |
11 | shouldComponentUpdate(nextProps) {
12 | return propsChanged(['main', 'sidebar'], this.props, nextProps)
13 | }
14 |
15 | render() {
16 | const { main, sidebar } = this.props
17 |
18 | return (
19 |
20 |
21 | {sidebar}
22 |
23 |
24 | {main}
25 |
26 |
27 | )
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3R Calculator | React, Redux, Ramda
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |