├── demo
├── babel.config.js
├── public
│ ├── favicon.ico
│ └── index.html
├── README.md
├── src
│ ├── main.js
│ └── App.vue
├── .eslintrc.js
└── package.json
├── screenshot.png
├── .prettierrc
├── .npmignore
├── .babelrc
├── LICENSE
├── .eslintrc.js
├── webpack.config.js
├── .gitignore
├── webpack4.config.js
├── test
└── FlipCountdown.test.js
├── README.md
├── package.json
└── src
└── FlipCountdown.vue
/demo/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philipjkim/vue2-flip-countdown/HEAD/screenshot.png
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/philipjkim/vue2-flip-countdown/HEAD/demo/public/favicon.ico
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 120
7 | }
8 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | ## Demo for [vue2-flip-countdown](https://github.com/philipjkim/vue2-flip-countdown)
2 |
3 | ```
4 | $ npm run serve
5 | ```
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .*.swp
2 | ._*
3 | .DS_Store
4 | .git
5 | .hg
6 | .npmrc
7 | .lock-wscript
8 | .svn
9 | .wafpickle-*
10 | config.gypi
11 | CVS
12 | npm-debug.log
13 | demo/
--------------------------------------------------------------------------------
/demo/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | render: h => h(App)
8 | }).$mount('#app')
9 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["env", { "modules": false }]
4 | ],
5 | "env": {
6 | "test": {
7 | "presets": [
8 | ["env", { "targets": { "node": "current" }}]
9 | ]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Demo: vue2-flip-countdown
9 |
10 |
11 |
12 | We're sorry but demo doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 philipjkim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@babel/eslint-parser',
4 | env: {
5 | browser: true,
6 | jest: true,
7 | es6: true,
8 | },
9 | settings: {
10 | 'import/resolver': {
11 | node: {
12 | extensions: ['.js', '.jsx', '.vue'],
13 | },
14 | },
15 | 'import/extensions': ['.js', '.jsx', '.vue'],
16 | },
17 | extends: [
18 | 'airbnb-base',
19 | 'plugin:vue/recommended',
20 | 'prettier',
21 | ],
22 |
23 | plugins: ['vue'],
24 |
25 | rules: {
26 | // allow debugger during development
27 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
28 | 'no-console': ['error', { allow: ['warn', 'error'] }],
29 | 'no-plusplus': 'off',
30 | 'no-underscore-dangle': 'off',
31 | 'no-param-reassign': 'off',
32 | 'no-restricted-globals': 'off',
33 | 'import/prefer-default-export': 'off',
34 | 'import/no-unresolved': [2, { ignore: ['vue2-datepicker'] }],
35 | 'import/no-extraneous-dependencies': 'off',
36 | 'vue/require-default-prop': 'off',
37 | 'vue/require-prop-types': 'off',
38 | 'vue/no-v-html': 'off',
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/demo/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // parser: '@babel/eslint-parser',
4 | parser: "vue-eslint-parser",
5 | env: {
6 | browser: true,
7 | jest: true,
8 | es6: true,
9 | },
10 | settings: {
11 | 'import/resolver': {
12 | node: {
13 | extensions: ['.js', '.jsx', '.vue'],
14 | },
15 | },
16 | 'import/extensions': ['.js', '.jsx', '.vue'],
17 | },
18 | extends: [
19 | 'plugin:vue/base',
20 | ],
21 |
22 | plugins: ['vue'],
23 |
24 | rules: {
25 | // allow debugger during development
26 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
27 | 'no-console': ['error', { allow: ['warn', 'error'] }],
28 | 'no-plusplus': 'off',
29 | 'no-underscore-dangle': 'off',
30 | 'no-param-reassign': 'off',
31 | 'no-restricted-globals': 'off',
32 | 'import/prefer-default-export': 'off',
33 | 'import/no-extraneous-dependencies': 'off',
34 | 'import/no-named-as-default': 'off',
35 | 'import/no-named-as-default-member': 'off',
36 | 'vue/require-default-prop': 'off',
37 | 'vue/require-prop-types': 'off',
38 | 'vue/no-v-html': 'off',
39 | },
40 | };
41 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | // config for webpack v3
2 |
3 | const webpack = require('webpack')
4 | const path = require('path')
5 | const TerserPlugin = require("terser-webpack-plugin")
6 | const { VueLoaderPlugin } = require('vue-loader');
7 | const PROD = process.env.NODE_ENV === 'production'
8 |
9 | module.exports = {
10 | entry: path.join(__dirname, '/src/FlipCountdown.vue'),
11 | output: {
12 | path: path.join(__dirname, '/dist/'),
13 | filename: 'vue2-flip-countdown.js',
14 |
15 | libraryTarget: 'umd',
16 | library: 'vue2-flip-countdown',
17 | umdNamedDefine: true
18 | },
19 | module: {
20 | rules: [
21 | {
22 | test: /\.vue$/,
23 | loader: 'vue-loader',
24 | options: {
25 | loaders: {
26 | js: {
27 | loader: 'babel-loader',
28 | options: { presets: ['env'] }
29 | },
30 | less: 'vue-style-loader!css-loader!less-loader'
31 | }
32 | }
33 | },
34 | {
35 | test: /\.less$/i,
36 | use: [
37 | // compiles Less to CSS
38 | "vue-style-loader",
39 | "css-loader",
40 | "less-loader",
41 | ],
42 | },
43 | ]
44 | },
45 | optimization: PROD ? {
46 | minimize: true,
47 | minimizer: [new TerserPlugin()],
48 | } : {
49 | minimize: false
50 | },
51 | plugins: [
52 | new VueLoaderPlugin()
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "moment": "^2.29.4",
12 | "vue": "^2.6.6",
13 | "vue2-flip-countdown": "^1.0.0",
14 | "@babel/eslint-parser": "^7.19.1"
15 | },
16 | "devDependencies": {
17 | "@vue/cli-plugin-babel": "^5.0.8",
18 | "@vue/cli-plugin-eslint": "^5.0.8",
19 | "@vue/cli-service": "^5.0.8",
20 | "eslint": "^8.32.0",
21 | "eslint-config-airbnb": "^19.0.4",
22 | "eslint-config-airbnb-base": "^15.0.0",
23 | "eslint-config-prettier": "^8.6.0",
24 | "eslint-plugin-import": "^2.27.4",
25 | "eslint-plugin-jsx-a11y": "^6.7.1",
26 | "eslint-plugin-react": "^7.32.0",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "lodash": "^4.17.21",
29 | "lodash.defaultsdeep": ">=4.6.1",
30 | "vue-template-compiler": "^2.6.10"
31 | },
32 | "eslintConfig": {
33 | "root": true,
34 | "env": {
35 | "node": true
36 | },
37 | "extends": [
38 | "plugin:vue/essential",
39 | "eslint:recommended"
40 | ],
41 | "rules": {},
42 | "parserOptions": {
43 | "parser": "@babel/eslint-parser"
44 | }
45 | },
46 | "postcss": {
47 | "plugins": {
48 | "autoprefixer": {}
49 | }
50 | },
51 | "browserslist": [
52 | "> 1%",
53 | "last 2 versions",
54 | "not ie <= 8"
55 | ]
56 | }
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/node,visualstudiocode
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (http://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # Typescript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 |
64 | ### VisualStudioCode ###
65 | .vscode/*
66 | !.vscode/settings.json
67 | !.vscode/tasks.json
68 | !.vscode/launch.json
69 | !.vscode/extensions.json
70 | .history
71 |
72 |
73 | # End of https://www.gitignore.io/api/node,visualstudiocode
74 |
75 | # dist
76 | dist/
77 |
78 | # asdf
79 | .tool-versions
80 |
81 | .idea
82 |
--------------------------------------------------------------------------------
/webpack4.config.js:
--------------------------------------------------------------------------------
1 | // config for webpack 4.x
2 |
3 | const path = require('path')
4 | const { VueLoaderPlugin } = require('vue-loader')
5 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
6 | const PROD = process.env.NODE_ENV === 'production'
7 | const MODE = PROD ? 'production' : 'development'
8 | const optimizationConf = PROD ? {
9 | minimizer: [
10 | new UglifyJSPlugin({
11 | uglifyOptions: {
12 | warning: 'verbose',
13 | ecma: 6,
14 | beautify: false,
15 | compress: false,
16 | comments: false,
17 | mangle: false,
18 | toplevel: false,
19 | keep_classnames: true,
20 | keep_fnames: true
21 | }
22 | })
23 | ]
24 | } : {}
25 |
26 | module.exports = {
27 | mode: MODE,
28 | entry: path.join(__dirname, '/src/FlipCountdown.vue'),
29 | output: {
30 | path: path.join(__dirname, '/dist/'),
31 | filename: 'vue2-flip-countdown.js',
32 | libraryTarget: 'umd',
33 | library: 'vue2-flip-countdown',
34 | umdNamedDefine: true,
35 | globalObject: 'typeof self !== \'undefined\' ? self : this'
36 | },
37 | optimization: optimizationConf,
38 | module: {
39 | rules: [
40 | {
41 | test: /\.vue$/,
42 | loader: 'vue-loader',
43 | options: {
44 | loaders: {
45 | js: {
46 | loader: 'babel-loader',
47 | options: { presets: ['env'] }
48 | }
49 | }
50 | }
51 | },
52 | {
53 | test: /\.less$/,
54 | use: [
55 | 'vue-style-loader',
56 | 'css-loader',
57 | 'less-loader'
58 | ]
59 | }
60 | ]
61 | },
62 | plugins: [
63 | new VueLoaderPlugin()
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/test/FlipCountdown.test.js:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils'
2 | import FlipCountdown from '@/FlipCountdown'
3 | import dateFormat from 'dateformat'
4 |
5 | const defaultLabels = {
6 | days: 'Days',
7 | hours: 'Hours',
8 | minutes: 'Minutes',
9 | seconds: 'Seconds'
10 | }
11 |
12 | const customLabels = {
13 | days: 'Dagen',
14 | hours: 'Uren',
15 | minutes: 'Minuten',
16 | seconds: 'Seconden'
17 | }
18 |
19 | function getDeadline () {
20 | const days = 5
21 | let result = new Date()
22 | result.setDate(result.getDate() + days)
23 | return dateFormat(result, 'yyyy-mm-dd 00:00:00')
24 | }
25 |
26 | describe('FlipCountdown', () => {
27 | let cmp
28 |
29 | describe('Use default labels', () => {
30 | beforeAll(() => {
31 | cmp = mount(FlipCountdown, {
32 | // Be aware that props is overridden using `propsData`
33 | propsData: {
34 | deadline: getDeadline()
35 | }
36 | })
37 | })
38 |
39 | it('Default labels are used', () => {
40 | expect(cmp.vm.labels).toEqual(defaultLabels)
41 | })
42 |
43 | it('Renders the correct markup', () => {
44 | Object.keys(defaultLabels).forEach(function (key) {
45 | const contains = '' + defaultLabels[key] + ' '
46 | expect(cmp.html()).toEqual(
47 | expect.stringContaining(contains)
48 | )
49 | })
50 | })
51 | })
52 |
53 | describe('Use custom labels', () => {
54 | beforeAll(() => {
55 | cmp = mount(FlipCountdown, {
56 | // Be aware that props is overridden using `propsData`
57 | propsData: {
58 | deadline: getDeadline(),
59 | labels: customLabels
60 | }
61 | })
62 | })
63 |
64 | it('Has received the custom labels', () => {
65 | expect(cmp.vm.labels).toEqual(customLabels)
66 | })
67 |
68 | it('Renders the correct markup', () => {
69 | Object.keys(customLabels).forEach(function (key) {
70 | const contains = '' + customLabels[key] + ' '
71 | expect(cmp.html()).toEqual(
72 | expect.stringContaining(contains)
73 | )
74 | })
75 | })
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue2-flip-countdown
2 |
3 | [](https://badge.fury.io/js/vue2-flip-countdown) [](https://github.com/facebook/jest)
4 |
5 | A simple flip countdown timer component for Vue 2.x
6 |
7 | 
8 |
9 | [Demo](https://philipjkim.github.io/vue2-flip-countdown/index.html)
10 |
11 | ## Notes on v1.0.0+
12 |
13 | `1.x.x` versions are based on Webpack 5.x, whereas `0.x.x` versions are based on Webpack 3.x.
14 | If you have any trouble when upgrading to `1.x.x`, please roll back version to `0.x.x`.
15 |
16 | ## Installation
17 |
18 | ```
19 | npm i vue2-flip-countdown --save
20 | ```
21 |
22 | ### Running Demo on Local Machine
23 |
24 | ```
25 | cd demo
26 | npm i
27 | npm run serve
28 | ```
29 |
30 | Then open on a browser.
31 |
32 | ## Usage
33 |
34 | ```vue
35 |
36 |
37 |
38 |
39 |
40 |
41 |
48 | ```
49 |
50 | - If you want to remove days section, set `showDays` prop to `false` (available since v0.10.0)
51 | - If you want to remove hours/minutes/seconds section, set `showHours`/`showMinutes`/`showSeconds` prop to `false` (available since v0.11.0)
52 |
53 | ```vue
54 |
55 | ```
56 |
57 | - To notify if timer has elapsed, bind a handler to `timeElapsed` event emitted by component
58 |
59 | ```vue
60 |
61 | ```
62 |
63 | Please refer to `/demo` directory for examples.
64 |
65 | If you're using [Nuxt.js](https://nuxtjs.org/), use [``](https://nuxtjs.org/api/components-no-ssr#the-lt-no-ssr-gt-component) to avoid server-side rendering. (You will get error if you don't use ``)
66 |
67 | ```vue
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | ```
76 |
77 | # References
78 |
79 | - [vuejs-countdown](https://github.com/getanwar/vuejs-countdown)
80 | - [Demo for 'Flip clock & countdown, Vue'](https://codepen.io/shshaw/pen/BzObXp)
81 |
--------------------------------------------------------------------------------
/demo/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Deadline1: {{ deadline1 }} (showDays = false)
5 |
10 |
Extend Deadline1
11 |
12 |
Deadline2: {{ deadline2 }}
13 |
14 |
15 |
Deadline3: {{ deadline3 }}
16 |
17 |
Set Deadline3 to previous date
18 |
19 |
Deadline4: {{ deadline4 }} (showDays = false, showHours = false)
20 |
26 |
27 |
Deadline5: {{ deadline5 }} (showDays = false, showSeconds = false)
28 |
34 |
35 |
38 |
39 |
40 |
41 |
82 |
83 |
101 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue2-flip-countdown",
3 | "version": "1.0.0",
4 | "description": "A simple flip countdown timer component for Vue 2.x",
5 | "main": "dist/vue2-flip-countdown.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/philipjkim/vue2-flip-countdown.git"
9 | },
10 | "keywords": [
11 | "countdown",
12 | "timer",
13 | "vue"
14 | ],
15 | "author": "Soo Philip Jason Kim",
16 | "contributors": [
17 | {
18 | "name": "nidkil"
19 | }
20 | ],
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/philipjkim/vue2-flip-countdown/issues"
24 | },
25 | "homepage": "https://github.com/philipjkim/vue2-flip-countdown#readme",
26 | "scripts": {
27 | "dev": "cross-env NODE_ENV=development webpack --progress --watch",
28 | "test": "jest",
29 | "build": "cross-env NODE_ENV=production webpack --progress",
30 | "prepare": "npm run build"
31 | },
32 | "devDependencies": {
33 | "@babel/eslint-parser": "^7.19.1",
34 | "@vue/test-utils": "^1.0.0-beta.29",
35 | "acorn": "^6.1.1",
36 | "acorn-dynamic-import": "^4.0.0",
37 | "acorn-jsx": "^5.0.1",
38 | "babel-core": "^6.26.3",
39 | "babel-eslint": "^10.0.1",
40 | "babel-jest": "^24.8.0",
41 | "babel-loader": "^9.1.2",
42 | "babel-preset-env": "^1.7.0",
43 | "cross-env": "^5.2.0",
44 | "css-loader": "^3.0.0",
45 | "dateformat": "^3.0.3",
46 | "eslint": "^8.32.0",
47 | "eslint-config-airbnb": "^19.0.4",
48 | "eslint-config-airbnb-base": "^15.0.0",
49 | "eslint-config-prettier": "^8.6.0",
50 | "eslint-config-standard": "^12.0.0",
51 | "eslint-plugin-html": "^6.0.0",
52 | "eslint-plugin-import": "^2.17.3",
53 | "eslint-plugin-jest": "^23.13.2",
54 | "eslint-plugin-node": "^9.1.0",
55 | "eslint-plugin-promise": "^4.1.1",
56 | "eslint-plugin-standard": "^4.0.0",
57 | "eslint-plugin-vue": "^9.9.0",
58 | "eslint-webpack-plugin": "^3.2.0",
59 | "jest": "^29.3.1",
60 | "jest-serializer-vue": "^2.0.2",
61 | "less": "^3.9.0",
62 | "less-loader": "^11.1.0",
63 | "lodash": "^4.17.14",
64 | "terser-webpack-plugin": "^5.3.6",
65 | "vue": "^2.6.6",
66 | "vue-loader": "^15.10.1",
67 | "vue-style-loader": "^4.1.2",
68 | "vue-template-compiler": "^2.6.6",
69 | "webpack": "^5.75.0",
70 | "webpack-cli": "^5.0.1"
71 | },
72 | "jest": {
73 | "moduleNameMapper": {
74 | "^@/(.*)$": "/src/$1"
75 | },
76 | "snapshotSerializers": [
77 | "/node_modules/jest-serializer-vue"
78 | ],
79 | "moduleFileExtensions": [
80 | "js",
81 | "json",
82 | "vue"
83 | ],
84 | "transform": {
85 | "^.+\\.js$": "/node_modules/babel-jest",
86 | ".*\\.(vue)$": "/node_modules/vue-jest"
87 | }
88 | },
89 | "dependencies": {
90 | "uuid": "^3.3.2"
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/FlipCountdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ data.current | twoDigits }}
7 |
8 |
9 |
10 |
11 | {{ data.label }}
12 |
13 |
14 |
15 |
16 |
17 |
237 |
238 |
429 |
--------------------------------------------------------------------------------