├── .babelrc
├── .gitignore
├── README.md
├── example
├── index.html
├── index.js
└── webpack.config.js
├── lib
├── Countdown.js
├── DateBetween.js
└── __tests__
│ ├── Countdown.spec.js
│ └── DateBetween.spec.js
├── package.json
├── src
├── Countdown.js
├── DateBetween.js
└── __tests__
│ ├── Countdown.spec.js
│ └── DateBetween.spec.js
└── 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-count-down
2 |
3 | [](https://badge.fury.io/js/react-count-down)
4 |
5 | react-count-down is a simple count down component using react.
6 |
7 | ## Installation
8 |
9 | `yarn add react-count-down`
10 |
11 | ## Usage
12 |
13 | ```jsx
14 | import Countdown from 'react-count-down'
15 | import ReactDOM from 'react-dom'
16 | import React, { Component, PropTypes } from 'react'
17 |
18 | const cb = () => {
19 | console.log('expired callback')
20 | }
21 |
22 | const OPTIONS = {
23 | endDate: '03/01/2020 10:55 AM',
24 | prefix: 'until my birthday!',
25 | cb
26 | }
27 |
28 | const TestComponent = () => (
29 |
30 |
31 |
32 | )
33 |
34 | ReactDOM.render(, document.getElementById('root'))
35 | ```
36 |
37 | ## Development
38 | yarn
39 | yarn dev
40 |
41 | ## Test
42 | yarn test
43 |
44 | ## Build
45 | yarn
46 | yarn build
47 |
48 | ## Publish
49 | npm login
50 | npm version patch
51 | git add -A
52 | git push origin master
53 | npm publish
54 |
55 | ## License
56 |
57 | [MIT](http://isekivacenz.mit-license.org/)
58 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Example
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import Countdown from '../lib/Countdown' // 'react-count-down'
2 | import ReactDOM from 'react-dom'
3 | import React from 'react'
4 |
5 | const cb = () => {
6 | console.log('expired callback')
7 | }
8 |
9 | const OPTIONS = {
10 | endDate: '03/01/2020 10:55 AM',
11 | prefix: 'until my birthday!',
12 | cb
13 | }
14 |
15 | const TestComponent = () => (
16 |
17 |
18 |
19 | )
20 |
21 | ReactDOM.render(, document.getElementById('root'))
22 |
--------------------------------------------------------------------------------
/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/Countdown.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.default = void 0;
7 |
8 | var _DateBetween = _interopRequireDefault(require("./DateBetween"));
9 |
10 | var _react = _interopRequireWildcard(require("react"));
11 |
12 | var _propTypes = _interopRequireDefault(require("prop-types"));
13 |
14 | 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; } }
15 |
16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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 | /**
37 | * Count down module
38 | * A simple count down component.
39 | **/
40 | var Countdown =
41 | /*#__PURE__*/
42 | function (_Component) {
43 | _inherits(Countdown, _Component);
44 |
45 | _createClass(Countdown, null, [{
46 | key: "propTypes",
47 | get: function get() {
48 | return {
49 | options: _propTypes.default.object
50 | };
51 | }
52 | }]);
53 |
54 | function Countdown(props) {
55 | var _this;
56 |
57 | _classCallCheck(this, Countdown);
58 |
59 | _this = _possibleConstructorReturn(this, _getPrototypeOf(Countdown).call(this, props));
60 | _this.state = {
61 | remaining: null
62 | };
63 | return _this;
64 | }
65 |
66 | _createClass(Countdown, [{
67 | key: "componentDidMount",
68 | value: function componentDidMount() {
69 | this.tick();
70 | this.interval = setInterval(this.tick.bind(this), 1000);
71 | }
72 | }, {
73 | key: "componentWillUnmount",
74 | value: function componentWillUnmount() {
75 | clearInterval(this.interval);
76 | }
77 | }, {
78 | key: "tick",
79 | value: function tick() {
80 | var startDate = new Date();
81 | var endDate = new Date(this.props.options.endDate);
82 | var remaining = (0, _DateBetween.default)(startDate, endDate);
83 |
84 | if (remaining === false) {
85 | window.clearInterval(this.interval);
86 | this.props.options['cb'] ? this.props.options.cb() : false;
87 | }
88 |
89 | this.setState({
90 | remaining: remaining ? remaining : 'time expired'
91 | });
92 | }
93 | }, {
94 | key: "render",
95 | value: function render() {
96 | var countDownWrapper = {
97 | margin: 0,
98 | padding: '10px'
99 | };
100 | var date = {
101 | fontSize: '150%',
102 | fontWeight: '200',
103 | lineHeight: 1.5,
104 | color: '#666'
105 | };
106 | var prefix = {
107 | fontSize: '150%',
108 | fontWeight: '200',
109 | lineHeight: 1.5,
110 | color: '#03CC85'
111 | };
112 | return _react.default.createElement("div", {
113 | style: countDownWrapper
114 | }, _react.default.createElement("span", {
115 | style: date
116 | }, " ", this.state.remaining), _react.default.createElement("span", {
117 | style: prefix
118 | }, " ", this.props.options.prefix));
119 | }
120 | }]);
121 |
122 | return Countdown;
123 | }(_react.Component);
124 |
125 | exports.default = Countdown;
--------------------------------------------------------------------------------
/lib/DateBetween.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var DateBetween = function DateBetween(startDate, endDate) {
4 | var second = 1000;
5 | var minute = second * 60;
6 | var hour = minute * 60;
7 | var day = hour * 24;
8 | var distance = endDate - startDate;
9 |
10 | if (distance < 0) {
11 | return false;
12 | }
13 |
14 | var days = Math.floor(distance / day);
15 | var hours = Math.floor(distance % day / hour);
16 | var minutes = Math.floor(distance % hour / minute);
17 | var seconds = Math.floor(distance % minute / second);
18 | var between = [];
19 | days > 0 ? between.push("".concat(days, " day").concat(days > 1 ? 's' : '')) : false;
20 | hours > 0 ? between.push("".concat(hours, " hour").concat(hours > 1 ? 's' : '')) : false;
21 | minutes > 0 ? between.push("".concat(minutes, " minute").concat(minutes > 1 ? 's' : '')) : false;
22 | seconds > 0 ? between.push("".concat(seconds, " second").concat(seconds > 1 ? 's' : '')) : false;
23 | return between.join(' ');
24 | };
25 |
26 | module.exports = DateBetween;
--------------------------------------------------------------------------------
/lib/__tests__/Countdown.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 _Countdown = _interopRequireDefault(require("../Countdown"));
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('Countdown renders the correct elements and props', function () {
28 | var OPTIONS = {
29 | endDate: '03/01/2020 10:55 AM',
30 | prefix: 'until my birthday!',
31 | cb: jest.fn()
32 | };
33 | var wrapper = (0, _enzyme.shallow)(_react.default.createElement(_Countdown.default, {
34 | options: OPTIONS
35 | }));
36 | expect(wrapper.instance().props.options).toEqual(OPTIONS);
37 | expect(wrapper.find('span').length).toEqual(2);
38 | expect(wrapper.find('span').first().text()).toContain('days');
39 | expect(wrapper.find('span').last().text()).toContain('until my birthday!');
40 | setTimeout(function () {
41 | expect(wrapper.instance().props.options.cb).toBeCalled();
42 | }, 0); // console.log(wrapper.debug())
43 | });
--------------------------------------------------------------------------------
/lib/__tests__/DateBetween.spec.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var _DateBetween = _interopRequireDefault(require("../DateBetween"));
4 |
5 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6 |
7 | test('difference between 2 dates is equal to 1 day', function () {
8 | var date1 = new Date("Fri Feb 06 2015 18:07:30");
9 | var date2 = new Date("Fri Feb 07 2015 18:07:30");
10 | var difference = (0, _DateBetween.default)(date1, date2);
11 | expect(difference).toEqual("1 day");
12 | });
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-count-down",
3 | "version": "1.2.1",
4 | "description": "A simple count down clock component",
5 | "main": "lib/Countdown.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/StevenIseki/react-count-down.git"
9 | },
10 | "author": "Steven Iseki ",
11 | "license": "MIT",
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 count down",
44 | "react-count-down",
45 | "react count-down",
46 | "react-component",
47 | "timer",
48 | "count down",
49 | "counter",
50 | "clock",
51 | "count-down",
52 | "pure css"
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/src/Countdown.js:
--------------------------------------------------------------------------------
1 | import DateBetween from './DateBetween'
2 | import React, { Component } from 'react'
3 | import PropTypes from 'prop-types'
4 |
5 | /**
6 | * Count down module
7 | * A simple count down component.
8 | **/
9 | export default class Countdown extends Component {
10 | static get propTypes() {
11 | return {
12 | options: PropTypes.object
13 | }
14 | }
15 |
16 | constructor(props) {
17 | super(props)
18 | this.state = { remaining: null }
19 | }
20 |
21 | componentDidMount() {
22 | this.tick()
23 | this.interval = setInterval(this.tick.bind(this), 1000)
24 | }
25 |
26 | componentWillUnmount() {
27 | clearInterval(this.interval)
28 | }
29 |
30 | tick() {
31 | let startDate = new Date()
32 | let endDate = new Date(this.props.options.endDate)
33 | let remaining = DateBetween(startDate, endDate)
34 |
35 | if (remaining === false) {
36 | window.clearInterval(this.interval)
37 | this.props.options['cb'] ? this.props.options.cb() : false
38 | }
39 |
40 | this.setState({
41 | remaining: remaining ? remaining : 'time expired'
42 | })
43 | }
44 |
45 | render() {
46 | const countDownWrapper = {
47 | margin: 0,
48 | padding: '10px'
49 | }
50 |
51 | const date = {
52 | fontSize: '150%',
53 | fontWeight: '200',
54 | lineHeight: 1.5,
55 | color: '#666'
56 | }
57 |
58 | const prefix = {
59 | fontSize: '150%',
60 | fontWeight: '200',
61 | lineHeight: 1.5,
62 | color: '#03CC85'
63 | }
64 |
65 | return (
66 |
67 | {this.state.remaining}
68 | {this.props.options.prefix}
69 |
70 | )
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/DateBetween.js:
--------------------------------------------------------------------------------
1 | const DateBetween = (startDate, endDate) => {
2 | let second = 1000
3 | let minute = second * 60
4 | let hour = minute * 60
5 | let day = hour * 24
6 | let distance = endDate - startDate
7 |
8 | if (distance < 0) {
9 | return false
10 | }
11 |
12 | let days = Math.floor(distance / day)
13 | let hours = Math.floor((distance % day) / hour)
14 | let minutes = Math.floor((distance % hour) / minute)
15 | let seconds = Math.floor((distance % minute) / second)
16 |
17 | let between = []
18 |
19 | days > 0 ? between.push(`${days} day${days > 1 ? 's' : ''}`) : false
20 | hours > 0 ? between.push(`${hours} hour${hours > 1 ? 's' : ''}`) : false
21 | minutes > 0
22 | ? between.push(`${minutes} minute${minutes > 1 ? 's' : ''}`)
23 | : false
24 | seconds > 0
25 | ? between.push(`${seconds} second${seconds > 1 ? 's' : ''}`)
26 | : false
27 |
28 | return between.join(' ')
29 | }
30 |
31 | module.exports = DateBetween
32 |
--------------------------------------------------------------------------------
/src/__tests__/Countdown.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 Countdown from '../Countdown'
17 |
18 | test('Countdown renders the correct elements and props', () => {
19 | const OPTIONS = {
20 | endDate: '03/01/2020 10:55 AM',
21 | prefix: 'until my birthday!',
22 | cb: jest.fn(),
23 | }
24 |
25 | const wrapper = shallow(
26 |
27 | )
28 |
29 | expect(wrapper.instance().props.options).toEqual(OPTIONS)
30 |
31 | expect(wrapper.find('span').length).toEqual(2)
32 |
33 | expect(
34 | wrapper
35 | .find('span')
36 | .first()
37 | .text()
38 | ).toContain('days')
39 |
40 | expect(
41 | wrapper
42 | .find('span')
43 | .last()
44 | .text()
45 | ).toContain('until my birthday!')
46 |
47 | setTimeout(() => {
48 | expect(wrapper.instance().props.options.cb).toBeCalled();
49 | }, 0)
50 |
51 | // console.log(wrapper.debug())
52 | })
53 |
--------------------------------------------------------------------------------
/src/__tests__/DateBetween.spec.js:
--------------------------------------------------------------------------------
1 | import DateBetween from '../DateBetween'
2 |
3 | test('difference between 2 dates is equal to 1 day', () => {
4 | var date1 = new Date("Fri Feb 06 2015 18:07:30");
5 | var date2 = new Date("Fri Feb 07 2015 18:07:30");
6 | var difference = DateBetween(date1, date2);
7 |
8 | expect(difference).toEqual("1 day");
9 | })
10 |
--------------------------------------------------------------------------------