├── .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 | [![npm version](https://badge.fury.io/js/react-count-down.svg)](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 | --------------------------------------------------------------------------------