├── src ├── styles │ ├── _bootstrap.scss │ ├── components │ │ ├── _header.scss │ │ ├── _controls.scss │ │ └── _display.scss │ ├── _base.scss │ ├── _settings.scss │ └── app.scss ├── favicon.ico ├── index.js ├── components │ ├── App.js │ ├── Header.js │ └── Timer │ │ ├── Display.js │ │ ├── Controls.js │ │ └── Timer.js ├── lib │ └── Time.js └── index.html ├── .gitignore ├── .eslintrc ├── package.json ├── webpack.config.js └── README.md /src/styles/_bootstrap.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | cursor: pointer; 3 | } -------------------------------------------------------------------------------- /src/styles/components/_header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 100px; 3 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drminnaar/react-timer-basic/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/styles/_base.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: $smoke; 3 | font-family: $font-family; 4 | } -------------------------------------------------------------------------------- /src/styles/_settings.scss: -------------------------------------------------------------------------------- 1 | $font-family: 'Open Sans', Helvetica, Arial, sans-serif; 2 | $smoke: whitesmoke; -------------------------------------------------------------------------------- /src/styles/components/_controls.scss: -------------------------------------------------------------------------------- 1 | .controls { 2 | width: 300px; 3 | } 4 | 5 | .controls.btn-group > button { 6 | height: 80px; 7 | width: 100%; 8 | } -------------------------------------------------------------------------------- /src/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import '_settings.scss'; 2 | @import '_base.scss'; 3 | @import '_bootstrap.scss'; 4 | @import './components/_header.scss'; 5 | @import './components/_controls.scss'; 6 | @import './components/_display.scss'; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import AppComponent from './components/App'; 4 | import './styles/app.scss'; 5 | 6 | ReactDOM.render(, document.getElementById('app')); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | /public 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | /.vscode/**/* 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Header from './Header'; 3 | import Timer from './Timer/Timer'; 4 | 5 | export default class App extends Component { 6 | 7 | constructor(){ 8 | super(); 9 | 10 | this.state = { 11 | title: 'React Timer' 12 | }; 13 | } 14 | 15 | render() { 16 | return ( 17 |
18 |
19 |
20 | 21 |
22 |
23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Header = (props) => ( 5 | 13 | ); 14 | 15 | Header.defaultProps = { 16 | title: 'Title' 17 | }; 18 | 19 | Header.propTypes = { 20 | title: PropTypes.string 21 | }; 22 | 23 | export default Header; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "commonjs": true, 6 | "es6": true, 7 | "node": true 8 | }, 9 | "extends": [ "eslint:recommended", "plugin:react/recommended" ], 10 | "parserOptions": { 11 | "ecmaVersion": 6, 12 | "ecmaFeatures": { 13 | "jsx": true 14 | }, 15 | "sourceType": "module" 16 | }, 17 | "plugins": [ 18 | "react" 19 | ], 20 | "rules": { 21 | "no-console": "off", 22 | "indent": [ 23 | "error", 24 | 4 25 | ], 26 | "quotes": [ 27 | "error", 28 | "single" 29 | ], 30 | "semi": [ 31 | "error", 32 | "always" 33 | ] 34 | } 35 | } -------------------------------------------------------------------------------- /src/styles/components/_display.scss: -------------------------------------------------------------------------------- 1 | $display-background-color: rgb(240, 240, 240); 2 | $display-border-color: rgb(221, 220, 220); 3 | $display-color: rgb(38, 203, 245); 4 | 5 | .display { 6 | align-items: center; 7 | background-color: $display-background-color; 8 | border: 10px solid $display-border-color; 9 | border-radius: 50%; 10 | display: flex; 11 | flex-direction: column; 12 | height: 300px; 13 | margin: auto; 14 | overflow: hidden; 15 | text-align: center; 16 | width: 300px; 17 | } 18 | 19 | .display-time { 20 | border: none; 21 | background-color: $display-background-color; 22 | color: $display-color; 23 | font-size: 46px; 24 | font-weight: 600; 25 | outline-width: 0; 26 | text-align: center; 27 | vertical-align: middle; 28 | width: 300px; 29 | } -------------------------------------------------------------------------------- /src/lib/Time.js: -------------------------------------------------------------------------------- 1 | export default class Time { 2 | 3 | getTime(timeInMilliseconds) { 4 | 5 | let time = timeInMilliseconds; 6 | 7 | const hours = this.formatUnitOfTime(Math.floor( time / (60 * 60 * 1000))); 8 | 9 | time = time % (60 * 60 * 1000); 10 | const minutes = this.formatUnitOfTime(Math.floor( time / (60 * 1000))); 11 | 12 | time = time % (60 * 1000); 13 | const seconds = this.formatUnitOfTime(Math.floor( time / 1000 )); 14 | 15 | const milliseconds = this.formatUnitOfTime(time % 1000); 16 | 17 | return `${hours}:${minutes}:${seconds}:${milliseconds}`; 18 | } 19 | 20 | formatUnitOfTime(unitOfTime) { 21 | return unitOfTime < 10 ? `0${unitOfTime}`.substring(0,2) : unitOfTime.toString().substring(0,2); 22 | } 23 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Timer 9 | 10 | 11 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-timer-basic", 3 | "version": "1.0.0", 4 | "description": "A basic timer that will start a countdown based on an input of time in seconds", 5 | "scripts": { 6 | "build": "webpack -d", 7 | "build:watch": "webpack --watch", 8 | "lint": "eslint .; exit 0", 9 | "lint:fix": "eslint . --fix", 10 | "serve": "webpack -p && live-server ./public", 11 | "serve:dev": "webpack-dev-server --open", 12 | "start": "npm run serve", 13 | "test": "echo \"No tests available\" && exit 0" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/drminnaar/react-timer-basic.git" 18 | }, 19 | "license": "MIT", 20 | "babel": { 21 | "presets": [ 22 | "env", 23 | "react" 24 | ] 25 | }, 26 | "devDependencies": { 27 | "babel-cli": "^6.26.0", 28 | "babel-core": "^6.26.0", 29 | "babel-eslint": "^8.2.1", 30 | "babel-loader": "^7.1.2", 31 | "babel-preset-env": "^1.6.1", 32 | "babel-preset-react": "^6.24.1", 33 | "clean-webpack-plugin": "^0.1.18", 34 | "css-loader": "^0.28.9", 35 | "eslint": "^4.17.0", 36 | "eslint-loader": "^1.9.0", 37 | "eslint-plugin-react": "^7.6.1", 38 | "file-loader": "^1.1.6", 39 | "html-webpack-plugin": "^2.30.1", 40 | "live-server": "^1.2.0", 41 | "node-sass": "^4.7.2", 42 | "sass-loader": "^6.0.6", 43 | "style-loader": "^0.20.1", 44 | "url-loader": "^0.6.2", 45 | "webpack": "^3.10.0", 46 | "webpack-dev-server": "^2.11.1" 47 | }, 48 | "dependencies": { 49 | "prop-types": "^15.6.0", 50 | "react": "^16.2.0", 51 | "react-dom": "^16.2.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const CleanWebPackPlugin = require('clean-webpack-plugin'); 3 | const HtmlWebPackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'bundle.js', 9 | path: path.resolve(__dirname, 'public') 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | enforce: 'pre', 15 | test: /\.js$/, 16 | loader: 'eslint-loader', 17 | options: { 18 | failOnWarning: true, 19 | failOnerror: true 20 | }, 21 | exclude: /node_modules/ 22 | }, 23 | { 24 | test: /\.js$/, 25 | loader: 'babel-loader', 26 | exclude: /node_modules/ 27 | }, 28 | { 29 | test: /\.s?css$/, 30 | use: [ 'style-loader', 'css-loader', 'sass-loader' ], 31 | exclude: /node_modules/ 32 | }, 33 | { 34 | test: /\.svg$/, 35 | loader: 'url-loader', 36 | exclude: /node_modules/ 37 | } 38 | ] 39 | }, 40 | plugins: [ 41 | new CleanWebPackPlugin([ 'public' ], { root: path.resolve(__dirname)}), 42 | new HtmlWebPackPlugin({ 43 | template: './src/index.html', 44 | favicon: './src/favicon.ico', 45 | inject: false 46 | }) 47 | ], 48 | devtool: 'cheap-module-eval-source-map', 49 | devServer: { 50 | contentBase: path.resolve(__dirname, 'public'), 51 | compress: true, 52 | port: 9000 53 | } 54 | }; -------------------------------------------------------------------------------- /src/components/Timer/Display.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Time from '../../lib/Time'; 4 | 5 | const Display = (props) => { 6 | 7 | const time = new Time(); 8 | 9 | const onChange = (e) => { 10 | props.onSecondsChanged(e.target.value); 11 | }; 12 | 13 | const runningDisplayStyle = { 14 | position: 'absolute', 15 | top: 100, 16 | color: props.time <= 10000 ? '#FE5C5C' : '' 17 | }; 18 | 19 | return ( 20 |
21 | { 22 | props.status === 'started' 23 | &&
24 | {time.getTime(props.time)} 25 |
26 | } 27 | { 28 | props.status !== 'started' && 29 |
30 |
31 | {time.getTime(props.time)} 32 |
33 | 36 |
37 | } 38 |
39 | {props.children} 40 |
41 |
42 | ); 43 | }; 44 | 45 | Display.defaultProps = { 46 | seconds: 0, 47 | status: null, 48 | time: 0, 49 | onSecondsChanged: (e) => console.log(e.target.value) 50 | }; 51 | 52 | Display.propTypes = { 53 | children: PropTypes.element, 54 | seconds: PropTypes.number.isRequired, 55 | status: PropTypes.string, 56 | time: PropTypes.number, 57 | onSecondsChanged: PropTypes.func 58 | }; 59 | 60 | export default Display; -------------------------------------------------------------------------------- /src/components/Timer/Controls.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Controls = (props) => ( 5 |
6 |
7 |
8 |
9 | { 10 | props.status !== 'started' && 11 | 18 | } 19 | 20 | { 21 | props.status === 'started' && 22 | 28 | } 29 | 30 | 36 |
37 |
38 |
39 |
40 | ); 41 | 42 | Controls.defaultProps = { 43 | startTimer: () => alert('startTimer'), 44 | stopTimer: () => alert('stopTimer'), 45 | resetTimer: () => alert('resetTimer'), 46 | status: null, 47 | canStart: false, 48 | }; 49 | 50 | Controls.propTypes = { 51 | startTimer: PropTypes.func, 52 | stopTimer: PropTypes.func, 53 | resetTimer: PropTypes.func, 54 | status: PropTypes.string, 55 | canStart: PropTypes.bool 56 | }; 57 | 58 | export default Controls; -------------------------------------------------------------------------------- /src/components/Timer/Timer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Display from './Display'; 3 | import Controls from './Controls'; 4 | 5 | class Timer extends Component { 6 | 7 | constructor() { 8 | super(); 9 | 10 | this.state = { 11 | seconds: 0, 12 | time: 0, 13 | status: null 14 | }; 15 | 16 | this.startTimer = this.startTimer.bind(this); 17 | this.stopTimer = this.stopTimer.bind(this); 18 | this.resetTimer = this.resetTimer.bind(this); 19 | this.onSecondsChanged = this.onSecondsChanged.bind(this); 20 | } 21 | 22 | componentWillUnmount() { 23 | clearInterval(this.interval); 24 | } 25 | 26 | onSecondsChanged(seconds) { 27 | seconds = parseInt(seconds); 28 | 29 | if (seconds && typeof seconds === 'number') { 30 | if (seconds <= 359999) { 31 | this.setState(() => ({ seconds: seconds, time: seconds * 1000 })); 32 | } 33 | } else { 34 | this.setState(() => ({ seconds: 0, time: 0 })); 35 | } 36 | } 37 | 38 | startTimer() { 39 | if (this.state.status !== 'started') { 40 | this.interval = setInterval(() => { 41 | if (this.state.time !== 0) { 42 | this.setState(prevState => ({ time: prevState.time - 10 })); 43 | } else { 44 | this.setState(() => ({ seconds: 0, status: null, time: 0 })); 45 | 46 | clearInterval(this.interval); 47 | } 48 | }, 10); 49 | 50 | this.setState(() => ({ status: 'started' })); 51 | } 52 | } 53 | 54 | stopTimer() { 55 | if (this.state.status && this.state.status === 'started') { 56 | 57 | clearInterval(this.interval); 58 | 59 | this.setState((prevState) => { 60 | return ({ 61 | status: 'stopped', 62 | seconds: Math.floor(prevState.time / 1000) 63 | }); 64 | }); 65 | } 66 | } 67 | 68 | resetTimer() { 69 | clearInterval(this.interval); 70 | 71 | this.setState(() => ({ seconds: 0, status: null, time: 0 })); 72 | } 73 | 74 | render() { 75 | return ( 76 |
77 | 81 |
82 | 0} /> 87 |
88 |
89 |
90 | ); 91 | } 92 | } 93 | 94 | export default Timer; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Timer Basic 2 | 3 | A basic timer that will start a countdown based on an input of time in seconds. 4 | 5 | The Timer provides start, stop, and rest functionality. When the timer reaches a value <= 10, the timer color changes to red. 6 | 7 | Go **[here](http://react-timer-basic.drminnaar.me/)** for live demo. 8 | 9 | Component Diagram 10 | ![](https://user-images.githubusercontent.com/33935506/34461426-30e5547c-ee32-11e7-8889-4fd16df7a7bb.png) 11 | 12 | This project also demonstrates: 13 | 14 | * a typcial React project layout structure 15 | * babel setup and configuration 16 | * webpack setup and configuration 17 | * eslint setup and configuration 18 | * SCSS setup and configuration 19 | 20 | **Screenshots:** 21 | 22 | ... | ... 23 | --- | --- 24 | ![react-timer-basic-1](https://user-images.githubusercontent.com/33935506/33755966-f18607ba-dbfb-11e7-911e-5c55964419e0.PNG) | ![](https://user-images.githubusercontent.com/33935506/33755967-f1b96c36-dbfb-11e7-985b-94c4946979f5.PNG) 25 | ![](https://user-images.githubusercontent.com/33935506/33755969-f22a7962-dbfb-11e7-9d6a-22be03ee7b77.PNG) | ![](https://user-images.githubusercontent.com/33935506/33755970-f25d7326-dbfb-11e7-8ec1-c1e2cc52189d.PNG) 26 | 27 | --- 28 | 29 | ## Developed With 30 | 31 | * [Node.js](https://nodejs.org/en/) - Javascript runtime 32 | * [React](https://reactjs.org/) - A javascript library for building user interfaces 33 | * [Babel](https://babeljs.io/) - A transpiler for javascript 34 | * [Webpack](https://webpack.js.org/) - A module bundler 35 | * [SCSS](http://sass-lang.com/) - A css metalanguage 36 | * [Bootstrap 4](https://getbootstrap.com/) - Bootstrap is an open source toolkit for developing with HTML, CSS, and JS 37 | * [Surge] - Static web publishing for Front-End Developers 38 | 39 | --- 40 | 41 | ## Related Projects 42 | 43 | * [react-starter] 44 | 45 | A basic template that consists of the essential elements that are required to start building a React application 46 | 47 | * [react-clicker] 48 | 49 | A basic React app that allows one to increase, decrease, or reset a counter 50 | 51 | * [react-clock-basic] 52 | 53 | A basic clock that displays the current date and time 54 | 55 | * [react-timer-advanced] 56 | 57 | A basic countdown timer that offers an advanced UI experience 58 | 59 | * [react-masterminds] 60 | 61 | A basic game of guessing a number with varying degrees of difficulty 62 | 63 | * [react-movie-cards] 64 | 65 | A basic application that displays a list of movies as a list of cards 66 | 67 | * [react-calculator-standard] 68 | 69 | A calculator that provides the essential arithmetic operations, an expression builder, and a complete history of all expressions 70 | 71 | * [react-bitcoin-monitor] 72 | 73 | An app that monitors changes in the Bitcoin Price Index (BPI) 74 | 75 | * [react-weather-standard] 76 | 77 | A weather application that displays the current weather, daily forecasts, and hourly forecasts based on your current geolocation 78 | 79 | --- 80 | 81 | ## Getting Started 82 | 83 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 84 | 85 | ### Prerequisites 86 | 87 | The following software is required to be installed on your system: 88 | 89 | * Node 8.x 90 | * Npm 3.x 91 | 92 | Type the following commands in the terminal to verify your node and npm versions 93 | 94 | ```bash 95 | node -v 96 | npm -v 97 | ``` 98 | 99 | ### Install 100 | 101 | Follow the following steps to get development environment running. 102 | 103 | * Clone _'react-timer-basic'_ repository from GitHub 104 | 105 | ```bash 106 | git clone https://github.com/drminnaar/react-timer-basic.git 107 | ``` 108 | 109 | _OR USING SSH_ 110 | 111 | ```bash 112 | git clone git@github.com:drminnaar/react-timer-basic.git 113 | ``` 114 | 115 | * Install node modules 116 | 117 | ```bash 118 | cd react-timer-basic 119 | npm install 120 | npm dedupe 121 | ``` 122 | 123 | ### Build 124 | 125 | * Build application 126 | 127 | This command will also run ESLint as part of build process. 128 | 129 | ```bash 130 | npm run build 131 | ``` 132 | 133 | * Build application and start watching for changes 134 | 135 | This command will also run ESLint as part of build process. 136 | 137 | ```bash 138 | npm run build:watch 139 | ``` 140 | 141 | ### Run ESlint 142 | 143 | * Lint project using ESLint 144 | 145 | ```bash 146 | npm run lint 147 | ``` 148 | 149 | * Lint project using ESLint, and autofix 150 | 151 | ```bash 152 | npm run lint:fix 153 | ``` 154 | 155 | ### Run 156 | 157 | * Run start 158 | 159 | This will run the _'serve'_ npm task 160 | 161 | ```bash 162 | npm start 163 | ``` 164 | 165 | * Run webpack dev server 166 | 167 | ```bash 168 | npm run serve:dev 169 | ``` 170 | 171 | * Alternatively run live-server (simple development http server with live reload capability) 172 | 173 | ```bash 174 | npm run serve 175 | ``` 176 | 177 | --- 178 | 179 | ## Versioning 180 | 181 | I use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/drminnaar/react-timer-basic/tags). 182 | 183 | ## Authors 184 | 185 | * **Douglas Minnaar** - *Initial work* - [drminnaar](https://github.com/drminnaar) 186 | 187 | [Surge]: https://surge.sh/ 188 | [react-starter]: https://github.com/drminnaar/react-starter 189 | [react-clicker]: https://github.com/drminnaar/react-clicker 190 | [react-clock-basic]: https://github.com/drminnaar/react-clock-basic 191 | [react-timer-basic]: https://github.com/drminnaar/react-timer-basic 192 | [react-timer-advanced]: https://github.com/drminnaar/react-timer-advanced 193 | [react-masterminds]: https://github.com/drminnaar/react-masterminds 194 | [react-movie-cards]: https://github.com/drminnaar/react-movie-cards 195 | [react-calculator-standard]: https://github.com/drminnaar/react-calculator-standard 196 | [react-bitcoin-monitor]: https://github.com/drminnaar/react-bitcoin-monitor 197 | [react-weather-standard]: https://github.com/drminnaar/react-weather-standard --------------------------------------------------------------------------------