├── .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 | [](https://badge.fury.io/js/react-timer)
4 |
5 | react-timer is a simple timer component using react.
6 |
7 | 
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 |
--------------------------------------------------------------------------------