├── .babelrc ├── .gitignore ├── README.md ├── example ├── index.html ├── index.js ├── screenshot.gif └── webpack.config.js ├── lib ├── SecondsTohhmmss.js ├── Timer.js └── __tests__ │ ├── SecondsTohhmmss.spec.js │ └── Timer.spec.js ├── package.json ├── src ├── SecondsTohhmmss.js ├── Timer.js └── __tests__ │ ├── SecondsTohhmmss.spec.js │ ├── Timer.spec.js │ └── __snapshots__ │ └── Timer.spec.js.snap └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ], 6 | "@babel/preset-react" 7 | ], 8 | "plugins": [ 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .node-version 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-timer 2 | 3 | [![npm version](https://badge.fury.io/js/react-timer.svg)](https://badge.fury.io/js/react-timer) 4 | 5 | react-timer is a simple timer component using react. 6 | 7 | ![](https://raw.githubusercontent.com/StevenIseki/react-timer/master/example/screenshot.gif) 8 | 9 | 10 | [Click here for a demo](https://whispering-shelf-76879.herokuapp.com) 11 | 12 | ## Install 13 | 14 | ``` js 15 | yarn add react-timer --save 16 | ``` 17 | ## Versions 18 | 19 | #### `1.0.0` uses React `^0.13.0` 20 | 21 | #### `1.0.1` uses React `^0.15.1` 22 | 23 | #### `1.1.0` uses React `^16.0.0` 24 | 25 | ## Use 26 | 27 | ```javascript 28 | import Timer from 'react-timer' 29 | import ReactDOM from 'react-dom' 30 | import React, { Component, PropTypes } from 'react' 31 | 32 | class TestComponent extends Component { 33 | /* delay is just the delay on showing the update of the timer */ 34 | const OPTIONS = { prefix: 'seconds elapsed!', delay: 100} 35 | render () { 36 | return ( 37 |
38 | 39 |
40 | ) 41 | } 42 | } 43 | 44 | ReactDOM.render( , document.getElementById('root') ) 45 | 46 | ``` 47 | 48 | ## Development 49 | yarn 50 | yarn dev 51 | 52 | ## Test 53 | yarn test 54 | 55 | ## Build 56 | yarn 57 | yarn build 58 | 59 | ## Publish 60 | npm login 61 | npm version patch 62 | git add -A 63 | git push origin master 64 | npm publish 65 | 66 | ## License 67 | 68 | [MIT](http://isekivacenz.mit-license.org/) 69 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import Timer from '../lib/Timer' // 'react-timer' 2 | import ReactDOM from 'react-dom' 3 | import React, { Component, PropTypes } from 'react' 4 | 5 | class TestComponent extends Component { 6 | 7 | render () { 8 | /* delay is just the delay on showing the update of the timer */ 9 | let OPTIONS = { prefix: 'seconds elapsed!', delay: 100} 10 | return ( 11 |
12 | 13 |
14 | ) 15 | } 16 | } 17 | 18 | ReactDOM.render( , document.getElementById('root') ) 19 | -------------------------------------------------------------------------------- /example/screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/react-z/react-timer/5df7ca2888b55453d87ac9969f1aa0f37f4ca01c/example/screenshot.gif -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | 3 | module.exports = { 4 | entry: ['./example/index.js'], 5 | devtool: 'inline-source-map', 6 | output: { filename: 'bundle.js', publicPath: '' }, 7 | module: { 8 | rules: [ 9 | { 10 | test: /\.js$/, 11 | use: [ { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/react'] } } ], 12 | exclude: /node_modules/, 13 | } 14 | ] 15 | }, 16 | plugins: [ 17 | new HtmlWebpackPlugin({ title: 'react infinite loader example', template: './example/index.html' }) 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /lib/SecondsTohhmmss.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var SecondsTohhmmss = function SecondsTohhmmss(totalSeconds) { 4 | var hours = Math.floor(totalSeconds / 3600); 5 | var minutes = Math.floor((totalSeconds - hours * 3600) / 60); 6 | var seconds = totalSeconds - hours * 3600 - minutes * 60; // round seconds 7 | 8 | seconds = Math.round(seconds * 100) / 100; 9 | var result = hours < 10 ? "0" + hours : hours; 10 | result += ":" + (minutes < 10 ? "0" + minutes : minutes); 11 | result += ":" + (seconds < 10 ? "0" + seconds : seconds); 12 | return result; 13 | }; 14 | 15 | module.exports = SecondsTohhmmss; -------------------------------------------------------------------------------- /lib/Timer.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = void 0; 7 | 8 | var _react = _interopRequireWildcard(require("react")); 9 | 10 | var _SecondsTohhmmss = _interopRequireDefault(require("./SecondsTohhmmss")); 11 | 12 | var _propTypes = _interopRequireDefault(require("prop-types")); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 15 | 16 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } 17 | 18 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 19 | 20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 21 | 22 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } 23 | 24 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 25 | 26 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 27 | 28 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 29 | 30 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } 31 | 32 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } 33 | 34 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 35 | 36 | var offset = null, 37 | interval = null; 38 | /** 39 | * Timer module 40 | * A simple timer component. 41 | **/ 42 | 43 | var Timer = 44 | /*#__PURE__*/ 45 | function (_Component) { 46 | _inherits(Timer, _Component); 47 | 48 | _createClass(Timer, null, [{ 49 | key: "propTypes", 50 | get: function get() { 51 | return { 52 | options: _propTypes.default.object 53 | }; 54 | } 55 | }]); 56 | 57 | function Timer(props) { 58 | var _this; 59 | 60 | _classCallCheck(this, Timer); 61 | 62 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Timer).call(this, props)); 63 | _this.state = { 64 | clock: 0, 65 | time: '' 66 | }; 67 | return _this; 68 | } 69 | 70 | _createClass(Timer, [{ 71 | key: "componentDidMount", 72 | value: function componentDidMount() { 73 | this.play(); 74 | } 75 | }, { 76 | key: "componentWillUnmount", 77 | value: function componentWillUnmount() { 78 | this.pause(); 79 | } 80 | }, { 81 | key: "pause", 82 | value: function pause() { 83 | if (interval) { 84 | clearInterval(interval); 85 | interval = null; 86 | } 87 | } 88 | }, { 89 | key: "play", 90 | value: function play() { 91 | if (!interval) { 92 | offset = Date.now(); 93 | interval = setInterval(this.update.bind(this), this.props.options.delay); 94 | } 95 | } 96 | }, { 97 | key: "reset", 98 | value: function reset() { 99 | var clockReset = 0; 100 | this.setState({ 101 | clock: clockReset 102 | }); 103 | var time = (0, _SecondsTohhmmss.default)(clockReset / 1000); 104 | this.setState({ 105 | time: time 106 | }); 107 | } 108 | }, { 109 | key: "update", 110 | value: function update() { 111 | var clock = this.state.clock; 112 | clock += this.calculateOffset(); 113 | this.setState({ 114 | clock: clock 115 | }); 116 | var time = (0, _SecondsTohhmmss.default)(clock / 1000); 117 | this.setState({ 118 | time: time 119 | }); 120 | } 121 | }, { 122 | key: "calculateOffset", 123 | value: function calculateOffset() { 124 | var now = Date.now(); 125 | var newOffset = now - offset; 126 | offset = now; 127 | return newOffset; 128 | } 129 | }, { 130 | key: "render", 131 | value: function render() { 132 | var timerStyle = { 133 | margin: "0px", 134 | padding: "2em" 135 | }; 136 | var buttonStyle = { 137 | background: "#fff", 138 | color: "#666", 139 | border: "1px solid #ddd", 140 | marginRight: "5px", 141 | padding: "10px", 142 | fontWeight: "200" 143 | }; 144 | var secondsStyles = { 145 | fontSize: "200%", 146 | fontWeight: "200", 147 | lineHeight: "1.5", 148 | margin: "0", 149 | color: "#666" 150 | }; 151 | return _react.default.createElement("div", { 152 | style: timerStyle, 153 | className: "react-timer" 154 | }, _react.default.createElement("h3", { 155 | style: secondsStyles, 156 | className: "seconds" 157 | }, " ", this.state.time, " ", this.props.prefix), _react.default.createElement("br", null), _react.default.createElement("button", { 158 | onClick: this.reset.bind(this), 159 | style: buttonStyle 160 | }, "reset"), _react.default.createElement("button", { 161 | onClick: this.play.bind(this), 162 | style: buttonStyle 163 | }, "play"), _react.default.createElement("button", { 164 | onClick: this.pause.bind(this), 165 | style: buttonStyle 166 | }, "pause")); 167 | } 168 | }]); 169 | 170 | return Timer; 171 | }(_react.Component); 172 | 173 | exports.default = Timer; -------------------------------------------------------------------------------- /lib/__tests__/SecondsTohhmmss.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _SecondsTohhmmss = _interopRequireDefault(require("../SecondsTohhmmss")); 4 | 5 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 6 | 7 | test('2 seconds is parsed correctly', function () { 8 | expect((0, _SecondsTohhmmss.default)(2)).toEqual('00:00:02'); 9 | }); 10 | test('70 seconds is parsed correctly', function () { 11 | expect((0, _SecondsTohhmmss.default)(70)).toEqual('00:01:10'); 12 | }); -------------------------------------------------------------------------------- /lib/__tests__/Timer.spec.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _enzyme = require("enzyme"); 4 | 5 | var _enzymeAdapterReact = _interopRequireDefault(require("enzyme-adapter-react-16")); 6 | 7 | var _react = _interopRequireDefault(require("react")); 8 | 9 | var _reactTestRenderer = _interopRequireDefault(require("react-test-renderer")); 10 | 11 | var _Timer = _interopRequireDefault(require("../Timer")); 12 | 13 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 14 | 15 | /* setup enzyme */ 16 | (0, _enzyme.configure)({ 17 | adapter: new _enzymeAdapterReact.default() 18 | }); 19 | /* setup jsdom */ 20 | 21 | var jsdom = require('jsdom'); 22 | 23 | var JSDOM = jsdom.JSDOM; 24 | var window = new JSDOM('').window; 25 | global.window = window; 26 | global.document = window.document; 27 | test('Timer renders correctly and matches snapshot', function () { 28 | var OPTIONS = { 29 | prefix: 'seconds elapsed!', 30 | delay: 100 31 | }; 32 | 33 | var component = _reactTestRenderer.default.create(_react.default.createElement(_Timer.default, { 34 | options: OPTIONS 35 | })); 36 | 37 | var tree = component.toJSON(); 38 | expect(tree).toMatchSnapshot(); 39 | }); 40 | test('Timer renders the correct elements and props', function () { 41 | var OPTIONS = { 42 | prefix: 'seconds elapsed!', 43 | delay: 100 44 | }; 45 | var wrapper = (0, _enzyme.shallow)(_react.default.createElement(_Timer.default, { 46 | options: OPTIONS 47 | })); 48 | expect(wrapper.instance().props.options).toEqual(OPTIONS); 49 | wrapper.find('button').first().props().onClick(); 50 | expect(wrapper.find('button').length).toEqual(3); 51 | expect(wrapper.find('h3').length).toEqual(1); 52 | expect(wrapper.find('button').first().text()).toContain('reset'); // console.log(wrapper.debug()) 53 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-timer", 3 | "version": "1.1.1", 4 | "description": "A simple timer component", 5 | "main": "lib/Timer.js", 6 | "author": "Steven Iseki ", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/StevenIseki/react-timer.git" 11 | }, 12 | "peerDependencies": { 13 | "react": "^16.0.0" 14 | }, 15 | "dependencies": { 16 | "prop-types": "^15.6.0" 17 | }, 18 | "devDependencies": { 19 | "@babel/cli": "^7.2.3", 20 | "@babel/core": "7.3.3", 21 | "@babel/preset-env": "7.3.1", 22 | "@babel/preset-react": "7.0.0", 23 | "babel-jest": "^24.7.1", 24 | "babel-loader": "8.0.5", 25 | "enzyme": "3.9.0", 26 | "enzyme-adapter-react-16": "^1.9.1", 27 | "html-webpack-plugin": "3.2.0", 28 | "jest": "^24.7.1", 29 | "jsdom": "13.2.0", 30 | "react": "^16.0.0", 31 | "react-dom": "^16.0.0", 32 | "webpack": "4.29.4", 33 | "webpack-cli": "3.2.3", 34 | "webpack-dev-server": "3.1.14" 35 | }, 36 | "scripts": { 37 | "build": "babel src --out-dir lib", 38 | "dev": "webpack-dev-server --mode=development --port 3000 --inline --hot --config example/webpack.config", 39 | "test": "jest" 40 | }, 41 | "keywords": [ 42 | "react", 43 | "react-component", 44 | "timer", 45 | "react timer", 46 | "react-timer", 47 | "timer component", 48 | "stopwatch", 49 | "reacttimer" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /src/SecondsTohhmmss.js: -------------------------------------------------------------------------------- 1 | let SecondsTohhmmss = function(totalSeconds) { 2 | let hours = Math.floor(totalSeconds / 3600); 3 | let minutes = Math.floor((totalSeconds - (hours * 3600)) / 60); 4 | let seconds = totalSeconds - (hours * 3600) - (minutes * 60); 5 | 6 | // round seconds 7 | seconds = Math.round(seconds * 100) / 100 8 | 9 | let result = (hours < 10 ? "0" + hours : hours); 10 | result += ":" + (minutes < 10 ? "0" + minutes : minutes); 11 | result += ":" + (seconds < 10 ? "0" + seconds : seconds); 12 | return result; 13 | } 14 | 15 | module.exports = SecondsTohhmmss; -------------------------------------------------------------------------------- /src/Timer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import SecondsTohhmmss from './SecondsTohhmmss' 3 | import PropTypes from 'prop-types' 4 | 5 | let offset = null, interval = null 6 | 7 | /** 8 | * Timer module 9 | * A simple timer component. 10 | **/ 11 | export default class Timer extends Component { 12 | static get propTypes () { 13 | return { 14 | options: PropTypes.object 15 | } 16 | } 17 | 18 | constructor(props) { 19 | super(props) 20 | this.state = { clock: 0, time: '' } 21 | } 22 | 23 | componentDidMount() { 24 | this.play() 25 | } 26 | 27 | componentWillUnmount() { 28 | this.pause() 29 | } 30 | 31 | pause() { 32 | if (interval) { 33 | clearInterval(interval) 34 | interval = null 35 | } 36 | } 37 | 38 | play() { 39 | if (!interval) { 40 | offset = Date.now() 41 | interval = setInterval(this.update.bind(this), this.props.options.delay) 42 | } 43 | } 44 | 45 | reset() { 46 | let clockReset = 0 47 | this.setState({clock: clockReset }) 48 | let time = SecondsTohhmmss(clockReset / 1000) 49 | this.setState({time: time }) 50 | } 51 | 52 | update() { 53 | let clock = this.state.clock 54 | clock += this.calculateOffset() 55 | this.setState({clock: clock }) 56 | let time = SecondsTohhmmss(clock / 1000) 57 | this.setState({time: time }) 58 | } 59 | 60 | calculateOffset() { 61 | let now = Date.now() 62 | let newOffset = now - offset 63 | offset = now 64 | return newOffset 65 | } 66 | 67 | render() { 68 | const timerStyle = { 69 | margin: "0px", 70 | padding: "2em" 71 | }; 72 | 73 | const buttonStyle = { 74 | background: "#fff", 75 | color: "#666", 76 | border: "1px solid #ddd", 77 | marginRight: "5px", 78 | padding: "10px", 79 | fontWeight: "200" 80 | }; 81 | 82 | const secondsStyles = { 83 | fontSize: "200%", 84 | fontWeight: "200", 85 | lineHeight: "1.5", 86 | margin: "0", 87 | color: "#666" 88 | }; 89 | 90 | return ( 91 |
92 |

{this.state.time} {this.props.prefix}

93 |
94 | 95 | 96 | 97 |
98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/__tests__/SecondsTohhmmss.spec.js: -------------------------------------------------------------------------------- 1 | import SecondsTohhmmss from '../SecondsTohhmmss' 2 | 3 | test('2 seconds is parsed correctly', () => { 4 | expect(SecondsTohhmmss(2)).toEqual('00:00:02'); 5 | }) 6 | 7 | test('70 seconds is parsed correctly', () => { 8 | expect(SecondsTohhmmss(70)).toEqual('00:01:10'); 9 | }) 10 | -------------------------------------------------------------------------------- /src/__tests__/Timer.spec.js: -------------------------------------------------------------------------------- 1 | /* setup enzyme */ 2 | import { configure } from 'enzyme' 3 | import Adapter from 'enzyme-adapter-react-16' 4 | configure({ adapter: new Adapter() }) 5 | 6 | /* setup jsdom */ 7 | var jsdom = require('jsdom') 8 | const { JSDOM } = jsdom 9 | const window = new JSDOM('').window 10 | global.window = window 11 | global.document = window.document 12 | 13 | import React from 'react' 14 | import renderer from 'react-test-renderer' 15 | import { shallow } from 'enzyme' 16 | import Timer from '../Timer' 17 | 18 | test('Timer renders correctly and matches snapshot', () => { 19 | const OPTIONS = { prefix: 'seconds elapsed!', delay: 100} 20 | 21 | const component = renderer.create( 22 | 23 | ) 24 | 25 | let tree = component.toJSON() 26 | expect(tree).toMatchSnapshot() 27 | }) 28 | 29 | test('Timer renders the correct elements and props', () => { 30 | const OPTIONS = { prefix: 'seconds elapsed!', delay: 100} 31 | 32 | const wrapper = shallow( 33 | 34 | ) 35 | 36 | expect(wrapper.instance().props.options).toEqual(OPTIONS) 37 | 38 | wrapper 39 | .find('button') 40 | .first() 41 | .props() 42 | .onClick() 43 | 44 | expect(wrapper.find('button').length).toEqual(3) 45 | expect(wrapper.find('h3').length).toEqual(1) 46 | 47 | expect( 48 | wrapper 49 | .find('button') 50 | .first() 51 | .text() 52 | ).toContain('reset') 53 | 54 | // console.log(wrapper.debug()) 55 | }) 56 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/Timer.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Timer renders correctly and matches snapshot 1`] = ` 4 |
13 |

25 | 26 | 27 | 28 |

29 |
30 | 45 | 60 | 75 |
76 | `; 77 | --------------------------------------------------------------------------------